24시간 동안 420만 개의 request 처리. 평균 response: 774 ms. 성공률: 99.91%. 리빌드 이전 7일 동안 동일한 워크로드의 평균은 2.5초였습니다. 동일한 하드웨어, 동일한 대상 사이트, 동일한 proxy 풀에서 평균 response time이 70% 감소한 것입니다. 우리는 고객들의 저녁 시간을 조용히 갉아먹고 있던 request path의 구성 요소들을 리빌드했습니다.
이 글은 무엇이 변경되었는지, 실제 수치는 어떻게 나타났는지, 그리고 Single과 Proxy Finder가 서로 다르게 설계된 이유가 무엇인지에 대한 기록입니다.
먼저 데이터에 대해 말씀드립니다. 이 글에 사용된 수치는 특정 대상 호스트 하나를 제외한 프로덕션 트래픽에서 수집된 것입니다. 해당 호스트는 이번 주 내내 적극적으로 rate limit을 적용하고 request에 챌린지를 보냈으며, 이는 모든 메트릭을 왜곡합니다. 이 호스트를 포함하면 성공률은 약 95%로 떨어집니다. 하지만 이 5%의 격차는 우리 인프라의 실패가 아닙니다. 우리의 picker가 얼마나 뛰어나든 상관없이, 특정 사이트가 request를 거부한 결과일 뿐입니다. 시스템이 협조적인 대상에서 실제로 어떻게 작동하는지 보여드리기 위해 해당 호스트는 제외했습니다.
데이터가 보여주는 것
지난 일주일 동안의 프로덕션 트래픽 일일 평균은 다음과 같습니다.

4월 22일부터 4월 27일까지: 평균 2,318 ms에서 3,116 ms 사이, 일일 트래픽 280만에서 770만 request 사이. 4월 25일은 770만 request에서 3,116 ms로 두드러지는데, 이는 토요일 피크입니다 (주말 트래픽 볼륨이 눈에 띄게 높아 평균을 끌어올립니다). 4월 28일: 380만 request에서 672 ms. 4월 29일 (09:00 UTC까지의 일부 데이터): 현재까지 170만 request에서 880 ms.
동일한 아키텍처. 동일한 proxy. 동일한 대상 사이트. 달라진 것은 request path입니다.
percentile 지표는 평균보다 더 명확한 그림을 보여줍니다. 평균은 꼬리 부분(tail)의 나쁜 지표를 감출 수 있지만, percentile은 감출 수 없습니다.

이 차트는 리빌드 후 24시간과 리빌드 전 24시간을 비교한 것입니다. p50은 거의 움직이지 않았습니다 (오히려 4% 상승했는데, 이는 정직한 데이터입니다. 빠른 경로는 이미 잘 작동하고 있었고, 새로운 picker는 꼬리 부분의 지연을 크게 줄이기 위해 초반에 약간의 리소스를 더 소모합니다). 이 기간 동안 평균은 48% 감소했습니다. p95는 8.5초에서 3.9초로 54% 단축되었습니다. p99는 20초에서 7.8초로 61% 단축되었습니다. 이 구간이 바로 고통이 존재하는 지점이며, 스크래퍼가 가장 느린 5%의 request가 돌아오기를 기다릴 때 실제로 체감하는 지연입니다. 리빌드 전 7일 동안의 평균(약 2.5초)과 비교하면, 오늘의 평균 774 ms는 70% 감소한 수치입니다.
성공률도 동일한 기간 동안 98.85%에서 99.91%로 상승했습니다. 특히 Proxy Finder의 경우 성공률이 99.89%에 달했습니다. Single은 99.96%였습니다. 이 수치는 우리가 가장 자랑스럽게 생각하는 부분입니다. 재시도할 필요 없이 첫 번째 시도에서 유용한 데이터를 얼마나 자주 반환하는지 보여주기 때문입니다.
두 개의 제품, 두 개의 지연 시간 프로필
우리는 Single과 Proxy Finder를 서로 비교하지 않습니다. 두 제품은 서로 다른 문제를 해결하며, 허용되는 지연 시간 예산(latency budget)도 다릅니다. 한 제품을 사용하면서 다른 제품의 수치를 보고 있다면, 잘못된 점수판을 보고 있는 것입니다.

Single은 당사 인프라를 통한 단발성 HTTP request입니다. URL을 제공하면, 실제 프로덕션 호출의 약 99%에서 이미 작동하는 것으로 확인된 proxy id를 함께 전달하게 됩니다. 당사는 해당 proxy를 통해 URL을 가져오고, 귀하는 response를 받습니다. 로테이션 로직도, 연속적인 재시도(retry cascade)도, proxy 교체도 없습니다. 지난 24시간 기준: p50은 100 ms, p95는 371 ms, p99는 581 ms입니다. 대부분의 호출이 0.2초 미만에 완료됩니다. 가장 느린 1%조차도 600 ms 미만에 완료됩니다.
Proxy Finder는 탐색 레이어입니다. 공개 proxy 풀에서 request를 로테이션하고, 각 후보를 검증하며, 실패 시 재시도하고, 실제로 작동한 첫 번째 response와 이를 수행한 proxy의 id를 함께 반환합니다. p50은 445 ms, p95는 5.4초, p99는 10.2초입니다. 구조상 느릴 수밖에 없습니다. 핵심은 스크래퍼가 대상 사이트에 접속할 때 사용하는 IP가 로테이션되는 일회용 IP라는 점입니다. 차단 목록과 rate limit에 대한 회복 탄력성을 얻는 대신 몇 초의 오버헤드를 감수하는 것입니다.
Single이 이토록 빠른 이유는 마법이 아닙니다. Proxy Finder가 탐색해야 했던 정보를 Single은 신뢰하기 때문입니다. Proxy Finder를 한 번 호출하여 "이 proxy가 작동했습니다, 여기 id입니다"라는 결과를 받으면, Single을 통한 동일한 대상에 대한 후속 fetch는 전체 탐색 단계를 건너뜁니다. 이미 검증을 통과한 proxy로 바로 연결되는 것입니다.
팀들이 두 제품을 함께 사용하는 방법
대상 사이트에 자주 접속하는 대부분의 팀은 2단계 패턴을 따릅니다. 첫 번째 호출은 탐색용이고, 나머지는 확정된 호출입니다.
첫 번째 호출은 Proxy Finder로 전송됩니다. 풀을 통해 로테이션하고, 수락 규칙에 따라 각 후보를 검증한 후, 작동한 proxy의 id와 함께 response를 반환합니다. 이 id는 향후 동일한 대상에 접속하고자 할 때 사용하는 핸들이 됩니다.
그 이후의 모든 호출은 proxy id를 첨부하여 Single로 전송됩니다. 탐색도, 검증도, 연속적인 재시도도 없습니다. 지정하신 proxy를 통해 request를 라우팅하고 response를 다시 스트리밍합니다. 이것이 p50에서 100 ms를 기록하는 경로입니다.
어제 작동했던 proxy가 오늘 차단되면, Proxy Finder로 한 번의 호출을 보내 재탐색을 수행한 후, 새로운 id로 Single fetch를 재개합니다. 우리는 만료된 상태를 숨기지 않습니다. 전달해주신 proxy id가 작동하지 않으면 빠르게 알려드려 로테이션할 수 있도록 합니다.
99대 1의 비율(볼륨 면에서는 Single이 지배적이고, 탐색 면에서는 Proxy Finder가 지배적임)이 바로 Single의 수치가 이렇게 나타나는 이유입니다. Single이 본질적으로 더 빠른 제품이라서가 아닙니다. Single은 안정 상태(steady state)이고 Proxy Finder는 보정(calibration) 단계이기 때문입니다. 대부분의 워크로드는 주로 안정 상태에 머무릅니다.
수행하려는 작업이 "호출자가 누구인지 신경 쓰지 않는 깨끗한 공개 API에서 이 JSON을 가져오는 것"이라면, proxy를 완전히 건너뛰고 Single만 단독으로 사용하십시오. 수행하려는 작업이 "차단되지 않고 일회용 IP 풀을 통해 이 보호된 페이지에 접속하는 것"이라면, 두 제품을 조합하여 사용하십시오. 이들은 대체재가 아닙니다. 동일한 워크플로우의 서로 다른 단계입니다.
변경된 사항
대부분의 성과는 proxy 로테이션 경로에서 세 가지 요소를 리빌드한 덕분이었습니다. 새로운 아이디어는 없었습니다. 단지 우리가 미뤄왔던 일들이었을 뿐입니다.
풀이 잘못된 데이터를 더 이상 신뢰하지 않도록 했습니다. 리빌드 이전에는 proxy 디렉토리가 항목을 필요 이상으로 오래 유지했습니다. 그중 일부 항목은 더 이상 연결할 수 없는 상태였습니다. 이러한 항목을 선택하면 연결이 되지 않는다는 사실을 알아내는 데만 15초에서 30초를 허비해야 했습니다. 우리는 풀이 실제 활성 상태를 거의 실시간으로 반영하고, picker가 최근에 성공한 것으로 확인된 IP를 선호하도록 모델을 전환했습니다.
단순히 작동 여부만 따지는 것이 아니라, proxy별 품질 점수를 도입했습니다. 이전에는 결국 성공하기만 한다면, response에 5초가 걸리는 proxy와 300 ms가 걸리는 proxy가 동일하게 취급되었습니다. 이제 picker는 지연 시간도 추적합니다. 느리지만 작동은 하는 proxy는 큐의 하단으로 밀려납니다. 빠른 proxy는 활성화되어 있는 동안 재사용됩니다. 이는 생각보다 중요한 변화입니다. 지연 시간 분포의 긴 꼬리(long tail) 부분은 대부분 이전 picker가 계속해서 선택했던 '느리지만 작동은 하는' proxy들이 차지하고 있었기 때문입니다.
대상별 품질 점수. 특정 도메인에서 안정적인 proxy가 다른 도메인에서는 불안정할 수 있습니다. 한 사이트에서 깔끔하게 작동하는 IP가 다른 사이트에서는 차단되거나 rate limit에 걸릴 수 있습니다. 이제 당사의 picker는 전역적 기준뿐만 아니라 대상 호스트별로 성공률과 지연 시간을 추적합니다. 특정 도메인에 대한 fetch를 요청하면, 최근 해당 도메인에서 실제로 우수한 성능을 보인 proxy 중에서 선택합니다. 전역적으로 우수한 proxy도 후보군에 유지되지만, 정확한 대상 도메인에서 우수한 이력을 가진 proxy가 전반적으로 우수한 이력을 가진 proxy보다 우선순위를 갖습니다.
더 스마트해진 재시도 에스컬레이션. 이전에는 request가 실패하면 즉시 병렬로 시도를 분산(fan out)시켰습니다. 이는 낭비였을 뿐만 아니라, 하나의 불량 proxy가 연쇄적인 후속 실패를 유발하는 최악의 상황을 만들었습니다. 이제 재시도는 시도 사이에 짧은 백오프(backoff)를 두고 순차적으로 에스컬레이션되므로, 실패는 실패로 끝나고 재시도는 단순한 재시도로 작동하며 실패가 증폭되지 않습니다.
사용자에게는 덜 보이지만 언급할 가치가 있는 두 번째 변경 사항 카테고리가 있습니다.
재시작 회복 탄력성. 이전에는 request 인프라를 재배포할 때마다 proxy 품질 점수 맵을 처음부터 다시 구축해야 하는 5~15분의 공백이 발생했습니다. 이 기간 동안 picker는 사실상 임의로 선택을 내렸습니다. 고객들은 이를 배포 직후의 지연 시간 급증(latency spike)으로 체감했습니다. 이제 우리는 재시작 시에도 해당 품질 맵을 유지합니다. 시스템이 준비된 상태(warm)로 시작되는 것입니다. 오늘부터는 지연 시간 급증 없이 대낮에도 인프라 변경 사항을 배포할 수 있습니다. 오늘 아침에 실시한 재시작 테스트에서는 15초의 일시적인 연결 끊김만 발생했으며, 이후 복구 시간은 전혀 필요하지 않았습니다. 이는 배포 주기에 있어 조용하지만 의미 있는 변화입니다. 더 이상 고객의 트래픽을 피해 재배포 일정을 잡을 필요가 없습니다.
더 명확해진 실패 시그널. 당사 인프라 내부에서 문제가 발생할 경우, 귀하의 재시도 로직이 적절한 HTTP 상태 코드를 받아 대응할 수 있게 되었습니다. 일시적으로 사용할 수 없는 백엔드는 503을 반환합니다. 잘못된 형식의 JSON을 반환한 백엔드는 502를 반환합니다. 실제 내부 오류는 500을 반환합니다. 이전에는 이 세 가지가 모두 500으로 보여 귀하의 재시도 로직이 "대기 후 재시도"해야 하는 상황과 "오류가 발생했으니 에스컬레이션"해야 하는 상황을 구분할 수 없었습니다. 이제는 구분이 가능합니다.
사용자에게 미치는 영향
로테이션 proxy를 대상으로 스크래퍼를 실행 중인 경우, 실질적인 변화는 p95와 p99가 일주일 전보다 각각 약 절반으로 줄어든다는 점입니다. 평균 request 시간이 감소합니다. 느리지만 결국 작동하는 proxy로 인한 재시도가 감소합니다. 1,000개의 request 작업이 20분 대신 1시간이 걸리게 만드는 주범인 꼬리 지연 시간(tail latency)도 함께 감소합니다.
Single을 실행 중인 경우, picker 개선의 효과를 주로 꼬리 부분에서 체감하실 수 있습니다. p99가 600 ms 미만으로 떨어졌으며, 이는 운이 나쁜 1%의 request조차도 이제 빠르게 반환됨을 의미합니다. Single은 이미 빨랐습니다. 이제는 일관성까지 갖추게 되었습니다.
솔직한 한계점
모든 문제를 해결한 것은 아닙니다. 여전히 적용되는 몇 가지 구체적인 한계점은 다음과 같습니다.
Proxy Finder의 p99는 여전히 약 10초입니다. 절반으로 줄이기는 했지만, 긴 꼬리는 실재합니다. 일부는 당사의 책임(풀 깊이, 드문 대상에서의 picker 노이즈)이지만, 상당 부분은 그렇지 않습니다. 대상 사이트 자체가 느릴 수 있고, 특정 경로에 rate limit을 적용할 수 있으며, 검사에 시간이 걸리는 챌린지 페이지를 제공하거나 단순히 타임아웃이 발생할 수 있습니다. 당사 인프라가 사용 가능한 최선의 IP를 선택할 수는 있지만, 응답 여부를 결정하느라 지체되는 대상 서버의 속도를 높일 수는 없습니다. 모든 request가 5초 이내에 반환되어야 하는 작업이라면, 허용 오차에 맞는 request별 타임아웃을 설정하고 재시도 레이어를 신뢰하십시오.
일부 대상은 적대적이며, 이는 수치에 반영됩니다. 서두에서 언급했듯이, 99.91%의 성공률은 이번 주에 당사를 적극적으로 차단하고 있는 특정 호스트 하나를 제외한 것입니다. 이를 포함하면 성공률은 약 95%로 떨어집니다. 이러한 호스트는 유일하지 않습니다. 공개 웹 데이터를 다루는 애그리게이터라면 누구나 겪는 일입니다. 대상은 매주 바뀝니다. picker 리빌드를 통해 시스템이 적대적인 대상을 우회하여 라우팅할 수 있는 가능성이 훨씬 높아졌지만, 트래픽을 완전히 거부하기로 결정한 사이트를 강제로 뚫을 수는 없습니다. 우리의 역할은 협조적인 케이스에서 가장 깨끗한 데이터를 제공하고, 적대적인 케이스에서는 빠르게 실패하는 것입니다.
동일 요일 비교에는 노이즈가 있습니다. percentile 차트에서 보여주는 24시간 기간은 어제와 그저께를 비교한 것입니다. 요일 효과, 대상 사이트의 변동성, 풀 구성 등은 두 기간 사이에 모두 달라집니다. 우리는 방향성(7일 차트는 4월 28일에 명확한 변곡점을 보여줍니다)에 확신이 있지만, 귀하의 자체 워크로드로 벤치마크를 수행하려는 경우 최소 일주일 동안 비교를 실행해 보십시오.
향후 계획
picker 재작성은 기초 작업입니다. 그 뒤를 이어 준비 중인 몇 가지 사항은 다음과 같습니다.
인스턴스 간에 품질 점수 맵을 공유하여, 독립적인 맵을 구축하는 대신 서로 학습할 수 있도록 합니다. 현재는 각 인스턴스가 어떤 proxy가 좋은지에 대한 자체적인 그림을 가지고 있습니다. 작동은 하지만 낭비입니다. 모든 인스턴스가 동일한 학습 비용을 병렬로 지불하고 있기 때문입니다.
신뢰도 기반 선택(Confidence-based picking)을 도입하여, 추측에 의존하는 대신 최근 몇 분 동안 실제로 테스트한 proxy를 선호하도록 합니다. 트래픽이 적어 picker를 자체적으로 활성화된 상태(warm)로 유지하기 어려운 소규모 고객에게 유용합니다.
더 장기적으로는, 대상별 품질 점수가 여러 신호(대상 지연 시간 프로필, 시간대별 패턴, 풀 세그먼트 상태) 중 하나가 되도록 더 많은 결정을 picker에 위임할 예정입니다. 이제 빠른 경로(hot path)를 늦추지 않고도 이를 수행할 수 있는 인프라가 마련되었습니다.
가장 비용이 적게 드는 request는 재시도할 필요가 없는 request입니다. picker 리빌드는 선택의 순간에 더 나은 데이터를 확보하는 것이 사후에 더 많이 시도하는 것보다 낫다는 가설에 베팅한 결과입니다. 첫 24시간 동안의 증거는 이것이 옳은 베팅이었음을 보여줍니다.