느린 프록시: 속도 저하의 7가지 원인과 가속화 방법
프록시 연결 속도는 데이터 파싱, 자동화 및 대량 요청과 관련된 모든 작업의 효율성에 직접적인 영향을 미칩니다. 프록시가 느리게 작동하면 스크립트 실행 시간이 증가하고, 타임아웃 및 데이터 손실이 발생합니다. 이 기사에서는 낮은 속도의 기술적 원인을 분석하고 코드 예제와 테스트 결과를 통해 구체적인 최적화 방법을 보여줍니다.
서버의 지리적 거리
프록시와 대상 리소스 간의 물리적 거리(지연 시간)는 지연의 주요 요인입니다. 체인에 추가되는 각 노드는 밀리초를 추가하며, 이는 대량 요청 시 누적됩니다.
프록시를 통한 요청의 전형적인 흐름은 다음과 같습니다: 귀하의 서버 → 프록시 서버 → 대상 웹사이트 → 프록시 서버 → 귀하의 서버. 귀하의 파서가 독일에 있고, 프록시가 미국에 있으며, 대상 웹사이트가 일본에 있다면 데이터는 수만 킬로미터를 이동합니다.
실제 예: 유럽 웹사이트에 대한 1000개의 요청 테스트 결과 응답 시간의 차이를 보여주었습니다: 유럽의 프록시를 통해서는 180ms, 아시아의 프록시를 통해서는 520ms. 각 요청당 340ms의 차이는 1000개의 요청에 대해 340초(5.6분)를 제공합니다.
해결책: 대상 리소스에 지리적으로 가까운 프록시를 선택하십시오. 러시아 웹사이트를 파싱하는 경우 러시아 IP가 있는 프록시를 사용하십시오. 글로벌 서비스(Google, Amazon)와 작업할 경우 미국이나 서유럽에 있는 프록시가 최적입니다.
주거용 프록시의 경우 특정 도시나 지역을 선택할 수 있는 가능성에 주목하십시오. 모스크바와 블라디보스토크의 프록시 간의 핑 차이는 모스크바 서버에 접근할 때 150-200ms에 이를 수 있습니다.
속도에 대한 프로토콜의 영향
프록시 프로토콜의 선택은 속도에 큰 영향을 미칩니다. 주요 옵션은 HTTP/HTTPS, SOCKS4, SOCKS5입니다. 각 프로토콜은 데이터 처리 및 오버헤드에 대한 고유한 특성을 가지고 있습니다.
| 프로토콜 | 속도 | 오버헤드 | 적용 |
|---|---|---|---|
| HTTP | 높음 | 최소 | 웹 파싱, API |
| HTTPS | 중간 | +15-25% SSL | 보안 연결 |
| SOCKS4 | 높음 | 낮음 | TCP 트래픽 |
| SOCKS5 | 중간-높음 | +5-10% 인증 | 범용 트래픽, UDP |
HTTP 프록시는 웹 스크래핑에 최적이며, 애플리케이션 레벨에서 작동하고 데이터를 캐시할 수 있습니다. SOCKS5는 더 범용적이지만 추가적인 처리 계층을 추가합니다. HTML 파싱의 경우 HTTP와 SOCKS5 간의 속도 차이는 10-15%에 이를 수 있습니다.
Python에서의 구성 예 (requests):
import requests
# HTTP 프록시 - 웹 요청에 더 빠름
proxies_http = {
'http': 'http://user:pass@proxy.example.com:8080',
'https': 'http://user:pass@proxy.example.com:8080'
}
# SOCKS5 - 더 범용적이지만 느림
proxies_socks = {
'http': 'socks5://user:pass@proxy.example.com:1080',
'https': 'socks5://user:pass@proxy.example.com:1080'
}
# 웹 파싱을 위해 HTTP 사용
response = requests.get('https://example.com', proxies=proxies_http, timeout=10)
귀하의 제공자가 두 가지 옵션을 제공하는 경우 실제 작업에서 테스트하십시오. 데이터 센터 프록시의 경우, HTTP 프로토콜은 동일한 부하에서 SOCKS5보다 12-18% 더 높은 속도를 보여줍니다.
프록시 서버의 과부하 및 IP 풀
하나의 프록시 서버가 너무 많은 동시 연결을 처리할 때, 대역폭과 컴퓨팅 자원의 제한으로 인해 속도가 떨어집니다. 이는 여러 클라이언트가 하나의 IP를 사용하는 공유 프록시에서 특히 심각합니다.
과부하의 전형적인 모습은 다음과 같습니다: 스크립트 시작 시 속도가 정상(분당 50-100 요청)이다가 갑자기 10-15 요청으로 떨어집니다. 이는 서버가 열린 연결 수 또는 대역폭의 한계에 도달했을 때 발생합니다.
과부하의 징후: 응답 시간이 200% 이상 증가, 주기적인 타임아웃, "Connection reset by peer" 오류, 급격한 속도 변화.
해결책:
- 하나의 IP 대신 프록시 풀을 사용하십시오. 10-20개의 프록시 간의 회전은 부하를 분산시키고 차단될 가능성을 줄입니다.
- 하나의 프록시를 통해 동시 연결 수를 제한하십시오 (5-10개의 병렬 스레드가 권장됩니다).
- 고부하 작업의 경우, 다른 사용자와 자원을 공유하지 않는 전용 프록시를 선택하십시오.
- 실시간으로 속도를 모니터링하고 느린 프록시를 자동으로 회전에서 제외하십시오.
속도 모니터링이 포함된 풀 구현 예:
import time
import requests
from collections import deque
class ProxyPool:
def __init__(self, proxies, max_response_time=5.0):
self.proxies = deque(proxies)
self.max_response_time = max_response_time
self.stats = {p: {'total': 0, 'slow': 0} for p in proxies}
def get_proxy(self):
"""풀에서 다음 프록시를 가져옵니다."""
proxy = self.proxies[0]
self.proxies.rotate(-1) # 끝으로 이동
return proxy
def test_and_remove_slow(self, url='http://httpbin.org/ip'):
"""느린 프록시를 테스트하고 제거합니다."""
for proxy in list(self.proxies):
try:
start = time.time()
requests.get(url, proxies={'http': proxy}, timeout=10)
response_time = time.time() - start
self.stats[proxy]['total'] += 1
if response_time > self.max_response_time:
self.stats[proxy]['slow'] += 1
# 요청의 50% 이상이 느린 경우 제거
slow_ratio = self.stats[proxy]['slow'] / self.stats[proxy]['total']
if slow_ratio > 0.5 and self.stats[proxy]['total'] > 10:
self.proxies.remove(proxy)
print(f"느린 프록시 제거됨: {proxy}")
except:
self.proxies.remove(proxy)
# 사용 예
proxies = [
'http://proxy1.example.com:8080',
'http://proxy2.example.com:8080',
'http://proxy3.example.com:8080'
]
pool = ProxyPool(proxies, max_response_time=3.0)
pool.test_and_remove_slow()
# 풀과 함께 작업
for i in range(100):
proxy = pool.get_proxy()
# 프록시를 통해 요청 실행
연결 설정 및 타임아웃
잘못 설정된 연결 매개변수는 프록시의 느린 작동의 일반적인 원인입니다. 너무 긴 타임아웃은 스크립트가 사용 불가능한 프록시를 기다리게 하고, 너무 짧은 타임아웃은 정상적인 연결을 끊게 만듭니다.
속도에 영향을 미치는 주요 매개변수:
- Connection timeout — 연결 설정 대기 시간. 최적: 주거용 프록시의 경우 5-10초, 데이터 센터 프록시의 경우 3-5초.
- Read timeout — 연결 설정 후 응답 대기 시간. 작업에 따라 다름: 파싱의 경우 10-15초, 대용량 파일 다운로드의 경우 30초 이상.
- Keep-Alive — TCP 연결 재사용. 동일한 도메인에 대한 후속 요청에서 최대 200-300ms를 절약합니다.
- Connection pooling — 열린 연결 풀. 대량 요청 시 높은 성능에 중요합니다.
requests에 대한 최적화된 구성:
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
# 최적화된 설정으로 세션 생성
session = requests.Session()
# 재시도 설정
retry_strategy = Retry(
total=3, # 최대 3회 시도
backoff_factor=0.5, # 시도 간 대기 시간: 0.5, 1, 2초
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["GET", "POST"]
)
# 연결 풀을 가진 어댑터
adapter = HTTPAdapter(
max_retries=retry_strategy,
pool_connections=10, # 10개의 호스트에 대한 풀
pool_maxsize=20 # 최대 20개의 연결
)
session.mount("http://", adapter)
session.mount("https://", adapter)
# 프록시 설정
session.proxies = {
'http': 'http://user:pass@proxy.example.com:8080',
'https': 'http://user:pass@proxy.example.com:8080'
}
# 최적의 타임아웃으로 요청
# (connection_timeout, read_timeout)
response = session.get(
'https://example.com',
timeout=(5, 15), # 연결에 5초, 읽기에 15초
headers={'Connection': 'keep-alive'} # 연결 재사용
)
1000페이지를 파싱할 때 Keep-Alive 세션을 사용하면 각 요청에 대해 새로운 연결을 생성하는 것보다 30-40% 더 빠르게 작업을 수행할 수 있습니다. TCP 연결 설정 및 SSL 핸드쉐이크에서의 시간 절약은 대량 작업에서 중요합니다.
암호화 및 SSL/TLS 오버헤드
HTTPS 연결은 데이터 암호화/복호화 및 SSL/TLS 핸드쉐이크 수행에 추가적인 계산 자원을 요구합니다. 프록시를 통해 작업할 때 이는 두 번 발생합니다: 귀하와 프록시 간, 프록시와 대상 서버 간.
일반적인 SSL/TLS 오버헤드:
- 초기 핸드쉐이크: 150-300ms (알고리즘 및 거리 의존)
- 데이터 암호화/복호화: 전송 시간에 +10-20%
- 높은 트래픽 시 프록시 서버의 CPU 추가 부하
최적화 방법:
1. TLS 세션 재개 사용
SSL 세션 매개변수를 재사용하고 전체 핸드쉐이크를 건너뛸 수 있습니다. 후속 연결마다 최대 200ms를 절약할 수 있습니다.
Python에서는 requests.Session()를 사용할 때 자동으로 작동하지만, 각 요청에 대해 새로운 세션을 생성하지 않도록 하십시오.
2. TLS 1.3을 선호하십시오
TLS 1.3은 핸드쉐이크를 위해 두 번의 왕복 대신 한 번만 필요합니다. 이는 연결 설정 시간을 30-50% 단축합니다.
귀하의 라이브러리(OpenSSL, urllib3)가 TLS 1.3을 지원하고 설정에서 비활성화되지 않았는지 확인하십시오.
3. 내부 작업에는 HTTP를 고려하십시오
기밀 정보가 포함되지 않은 공개 데이터를 파싱하고 사이트가 HTTP로 접근 가능하다면 암호화되지 않은 연결을 사용하십시오. 이는 속도를 15-25% 증가시킵니다.
모바일 프록시와 작업할 때, 통신 채널이 느릴 수 있으며, SSL 오버헤드가 더욱 두드러질 수 있습니다. 테스트에서 4G 프록시를 통한 HTTP와 HTTPS 요청 간의 차이는 평균 280ms에 달했습니다.
DNS 해상도 및 캐싱
새로운 도메인에 대한 요청은 DNS 해상도, 즉 도메인 이름을 IP 주소로 변환하는 작업을 필요로 합니다. 캐싱이 없으면 각 요청에 20-100ms가 추가되며, 느린 DNS 서버를 사용할 경우 지연 시간이 500ms 이상에 이를 수 있습니다.
프록시를 통해 작업할 때 DNS 요청은 세 곳에서 수행될 수 있습니다:
- 귀하의 측면(클라이언트가 도메인을 해상하고 프록시의 IP를 전달)
- 프록시 서버에서(SOCKS5, HTTP CONNECT — 프록시가 도메인을 받아 스스로 해상)
- 대상 서버에서(드물게, 특정 구성에서)
SOCKS5 프록시의 경우 DNS 해상도는 일반적으로 프록시 서버 측에서 수행되며, 제공자의 프록시가 나쁜 DNS 서버를 사용할 경우 느릴 수 있습니다. HTTP 프록시는 클라이언트 측에서 해상하는 경우가 더 많습니다.
DNS 속도 향상 방법:
import socket
from functools import lru_cache
# 클라이언트 측에서 DNS 해상도 캐싱
@lru_cache(maxsize=256)
def cached_resolve(hostname):
"""DNS 요청 결과 캐시"""
try:
return socket.gethostbyname(hostname)
except socket.gaierror:
return None
# 사용 예
hostname = 'example.com'
ip = cached_resolve(hostname)
if ip:
# 요청에서 IP를 직접 사용
url = f'http://{ip}/path'
headers = {'Host': hostname} # 원래 호스트를 헤더에 지정
대안적인 접근법은 시스템 수준에서 빠른 공개 DNS 서버를 사용하는 것입니다:
- Google DNS: 8.8.8.8, 8.8.4.4
- Cloudflare DNS: 1.1.1.1, 1.0.0.1
- Quad9: 9.9.9.9
Linux에서는 /etc/resolv.conf를 통해 설정합니다:
nameserver 1.1.1.1
nameserver 8.8.8.8
options timeout:2 attempts:2
많은 도메인을 가진 Python 스크립트의 경우 DNS 캐시를 미리 워밍업하는 것이 좋습니다:
import concurrent.futures
import socket
def warmup_dns_cache(domains):
"""도메인 목록을 미리 해상합니다."""
def resolve(domain):
try:
socket.gethostbyname(domain)
except:
pass
with concurrent.futures.ThreadPoolExecutor(max_workers=20) as executor:
executor.map(resolve, domains)
# 파싱할 도메인 목록
domains = ['site1.com', 'site2.com', 'site3.com']
warmup_dns_cache(domains)
# 이제 DNS가 캐시에 있으므로 요청이 더 빨라질 것입니다.
제공자의 인프라 품질
프록시 속도는 제공자의 장비 및 통신 채널의 품질에 직접적으로 의존합니다. 저렴한 프록시는 종종 과부하가 걸린 서버에서 느린 네트워크 인터페이스와 구식 하드웨어로 작동합니다.
인프라의 중요한 매개변수:
| 매개변수 | 나쁨 | 좋음 | 속도에 미치는 영향 |
|---|---|---|---|
| 채널 대역폭 | 100Mbps | 1Gbps 이상 | 파일 다운로드 시 중요 |
| 서버 프로세서 | 2-4 코어 | 8코어 이상 | SSL/TLS 처리에 영향 |
| RAM | 4-8GB | 16GB 이상 | 캐싱 및 버퍼링 |
| 가동 시간 | <95% | 99% 이상 | 연결의 안정성 |
| 라우팅 | 표준 | 최적화된 BGP | 지연 및 패킷 손실 |
자체 인프라를 가진 제공자(재판매자가 아님)는 일반적으로 안정적으로 높은 속도를 제공합니다. 그들은 하드웨어에서 네트워크 장비 설정까지 모든 스택을 제어합니다.
고품질 인프라의 징후:
- 하루 동안 안정적인 속도(평균에서 15-20% 이내의 편차)
- 낮은 지터(지연 변동) — 10ms 미만
- 최소한의 패킷 손실 (<0.1%)
- 문제에 대한 기술 지원의 빠른 반응 (비즈니스 작업에 중요)
- 서버 위치 및 채널 특성에 대한 투명한 정보
중요한 작업의 경우, 실제 전투 조건에 최대한 가까운 환경에서 프록시를 테스트하는 것이 좋습니다. 1-3일 동안 테스트 액세스를 구매하고 모든 메트릭을 모니터링하며 실제 스크립트를 실행하십시오.
프록시 속도 테스트 방법론
올바른 테스트는 병목 현상을 식별하고 다양한 제공자를 객관적으로 비교하는 데 도움이 됩니다. 간단한 속도 테스트는 충분하지 않으며, 귀하의 작업에 중요한 매개변수를 측정해야 합니다.
측정해야 할 주요 메트릭:
- Latency (지연) — 패킷이 왕복하는 데 걸리는 시간. 많은 소규모 요청이 있는 작업에 중요합니다.
- Throughput (처리량) — 단위 시간당 데이터 양. 파일 및 이미지 다운로드에 중요합니다.
- Connection time — 연결 설정 시간. 단일 요청의 효율성을 보여줍니다.
- Success rate — 성공적인 요청의 비율. 95% 미만은 나쁜 지표입니다.
- Jitter — 지연의 변동. 높은 지터(>50ms)는 채널의 불안정성을 나타냅니다.
종합 테스트 스크립트:
import time
import requests
import statistics
from concurrent.futures import ThreadPoolExecutor, as_completed
def test_proxy_performance(proxy, test_url='https://httpbin.org/get', requests_count=50):
"""
프록시 종합 테스트
Args:
proxy: 프록시 URL
test_url: 테스트할 URL
requests_count: 테스트 요청 수
Returns:
메트릭이 포함된 dict
"""
results = {
'latencies': [],
'connection_times': [],
'total_times': [],
'successes': 0,
'failures': 0,
'errors': []
}
session = requests.Session()
session.proxies = {'http': proxy, 'https': proxy}
def single_request():
try:
start = time.time()
response = session.get(
test_url,
timeout=(5, 15),
headers={'Connection': 'keep-alive'}
)
total_time = time.time() - start
if response.status_code == 200:
results['successes'] += 1
results['total_times'].append(total_time)
# 대략적인 지연 시간 추정
results['latencies'].append(total_time / 2)
else:
results['failures'] += 1
except Exception as e:
results['failures'] += 1
results['errors'].append(str(e))
# 요청을 병렬로 실행
with ThreadPoolExecutor(max_workers=10) as executor:
futures = [executor.submit(single_request) for _ in range(requests_count)]
for future in as_completed(futures):
future.result()
# 통계 계산
if results['total_times']:
metrics = {
'proxy': proxy,
'total_requests': requests_count,
'success_rate': (results['successes'] / requests_count) * 100,
'avg_response_time': statistics.mean(results['total_times']),
'median_response_time': statistics.median(results['total_times']),
'min_response_time': min(results['total_times']),
'max_response_time': max(results['total_times']),
'stdev_response_time': statistics.stdev(results['total_times']) if len(results['total_times']) > 1 else 0,
'jitter': statistics.stdev(results['latencies']) if len(results['latencies']) > 1 else 0,
'failures': results['failures']
}
return metrics
else:
return {'proxy': proxy, 'error': '모든 요청 실패'}
# 테스트
proxy = 'http://user:pass@proxy.example.com:8080'
metrics = test_proxy_performance(proxy, requests_count=100)
print(f"프록시: {metrics['proxy']}")
print(f"성공률: {metrics['success_rate']:.1f}%")
print(f"평균 응답 시간: {metrics['avg_response_time']*1000:.0f} ms")
print(f"중앙값: {metrics['median_response_time']*1000:.0f} ms")
print(f"지터: {metrics['jitter']*1000:.0f} ms")
print(f"표준 편차: {metrics['stdev_response_time']*1000:.0f} ms")
보다 정확한 결과를 위해 하루 중 다양한 시간(아침, 낮, 저녁)과 다양한 대상 사이트에서 테스트하십시오. 속도는 지리적 위치 및 네트워크 부하에 따라 크게 달라질 수 있습니다.
팁: 기본 기준선(baseline)을 설정하십시오 — 프록시 없이 직접 연결을 테스트하십시오. 이는 프록시 오버헤드를 평가하기 위한 기준점을 제공합니다. 정상적인 오버헤드는 품질 좋은 프록시의 경우 50-150ms입니다.
종합 최적화: 체크리스트
설명된 모든 방법을 종합적으로 적용하면 누적 효과를 얻을 수 있습니다. 다음은 프록시를 통한 속도 최적화를 위한 단계별 계획입니다:
1단계: 프록시 선택 및 설정
- 대상 리소스에 지리적으로 가까운 프록시 선택
- 웹 파싱을 위해 SOCKS5 대신 HTTP 프로토콜 사용
- 고부하 작업을 위해 전용 프록시 선호
- 제공자가 TLS 1.3을 지원하는지 확인
2단계: 코드 최적화
requests.Session()을 사용하여 Keep-Alive 설정- 연결 풀을 설정하십시오 (10-20 연결)
- 최적의 타임아웃 설정: 연결에 5-10초, 읽기에 15-30초
- 지수 백오프를 사용하는 재시도 로직 구현
- DNS 해상도 캐싱 구현
3단계: 프록시 풀 관리
- 회전을 위한 10-50개의 프록시 풀 생성
- 하나의 프록시를 통해 동시 요청 수 제한 (5-10 스레드)
- 속도를 모니터링하고 느린 프록시를 자동으로 제외
- IP를 유지해야 하는 작업을 위해 스티키 세션 사용
4단계: 시스템 최적화
- 빠른 DNS 서버 설정 (1.1.1.1, 8.8.8.8)
- OS에서 열린 파일 수 제한 늘리기 (ulimit -n 65535)
- Linux의 경우: 커널 TCP 매개변수 최적화
- 대량 데이터 작업 시 SSD를 캐싱에 사용
5단계: 모니터링 및 테스트
- 정기적으로 프록시 속도 테스트 (최소 주 1회)
- 메트릭 기록: 응답 시간, 성공률, 오류
- 다양한 제공자의 성능 비교
- 속도가 임계값 이하로 떨어질 경우 알림 설정
생산을 위한 최적화된 구성 예:
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
from collections import deque
import time
class OptimizedProxyPool:
def __init__(self, proxies_list):
self.proxies = deque(proxies_list)
self.session = self._create_optimized_session()
self.stats = {p: {'requests': 0, 'avg_time': 0} for p in proxies_list}
def _create_optimized_session(self):
"""최적화된 세션 생성"""
session = requests.Session()
# 재시도 전략
retry = Retry(
total=3,
backoff_factor=0.3,
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["GET", "POST", "PUT"]
)
# 연결 풀을 가진 어댑터
adapter = HTTPAdapter(
max_retries=retry,
pool_connections=20,
pool_maxsize=50,
pool_block=False
)
session.mount("http://", adapter)
session.mount("https://", adapter)
# Keep-Alive 헤더
session.headers.update({
'Connection': 'keep-alive',
'Keep-Alive': 'timeout=60, max=100'
})
return session
def get_best_proxy(self):
"""최고 성능의 프록시 가져오기"""
# 평균 속도에 따라 정렬
sorted_proxies = sorted(
self.stats.items(),
key=lambda x: x[1]['avg_time'] if x[1]['requests'] > 0 else float('inf')
)
return sorted_proxies[0][0] if sorted_proxies else self.proxies[0]
def request(self, url, method='GET', **kwargs):
"""최적 프록시를 통해 요청 실행"""
proxy = self.get_best_proxy()
self.session.proxies = {'http': proxy, 'https': proxy}
start = time.time()
try:
response = self.session.request(
method,
url,
timeout=(5, 15), # 연결, 읽기
**kwargs
)
# 통계 업데이트
elapsed = time.time() - start
stats = self.stats[proxy]
stats['avg_time'] = (
(stats['avg_time'] * stats['requests'] + elapsed) /
(stats['requests'] + 1)
)
stats['requests'] += 1
return response
except Exception as e:
# 오류 발생 시 프록시를 큐의 끝으로 이동
self.proxies.remove(proxy)
self.proxies.append(proxy)
raise e
# 사용 예
proxies = [
'http://user:pass@proxy1.example.com:8080',
'http://user:pass@proxy2.example.com:8080',
'http://user:pass@proxy3.example.com:8080'
]
pool = OptimizedProxyPool(proxies)
# 요청 실행
for url in ['https://example.com', 'https://example.org']:
try:
response = pool.request(url)
print(f"성공: {url}, 상태: {response.status_code}")
except Exception as e:
print(f"오류: {url}, {e}")
이 체크리스트를 적용하면 기본 설정에 비해 프록시를 통한 작업 속도를 2-3배 증가시킬 수 있습니다. 실제 파싱 프로젝트에서 이는 작업 시간을 몇 시간에서 몇 분으로 단축시킵니다.
결론
느린 프록시 작동은 기술적 원인을 이해하고 올바른 최적화 방법을 적용하면 해결할 수 있는 문제입니다. 속도의 주요 요인은 지리적 근접성, 프로토콜 선택, 제공자의 인프라 품질 및 클라이언트 코드의 올바른 설정입니다.
최적화에 대한 종합적인 접근 방식은...