본문으로 건너뛰기
Forward Engineering
Go back

브라우저 worker는 CPU worker도 I/O worker도 아니었다 — 작업 시간의 꼬리가 만든 용량 경계

- views

Table of contents

Open Table of contents

0. 시작 — timeout이 worker를 빠르게 만들지는 않았다

세션 ownership을 정한 뒤에도 작업이 빨라지는 것은 아닙니다. 같은 계정의 작업을 한 번에 하나씩 실행하면 page 충돌은 줄지만, 앞 작업이 오래 걸리는 동안 뒤 작업은 기다려야 합니다.

운영에서는 이 대기 시간이 lock force-release로 드러났습니다. 60일 동안 1,129회였습니다. 특히 일부 날짜에는 하루 100회 이상 발생했습니다.

이 숫자를 “browser가 1,129번 죽었다”라고 읽으면 안 됩니다. force-release는 긴 작업을 영구 대기열로 남기지 않기 위한 backstop이 발화한 횟수입니다. 작업은 강제 해제 뒤에도 실제 browser에서 계속 실행될 수 있습니다.

따라서 이 지표가 말하는 사실은 하나입니다.

timeout보다 오래 살아 있는 작업이 충분히 자주 있었고, slot을 점유하는 시간을 평균만으로 판단할 수 없었다.

1. browser worker의 용량은 가장 느린 자원이 결정한다

일반 HTTP worker는 요청 하나가 끝나면 socket과 CPU 시간을 비교적 예측 가능하게 돌려줍니다. browser 작업은 다릅니다.

작업 하나
 → account session lock 대기
 → browser/page 확보
 → proxy 할당
 → 로그인 또는 세션 검증
 → navigation / 외부 API 응답 대기
 → 결과 파싱
 → page 정리와 slot 반환

어느 단계 하나라도 느려지면 worker의 유효 처리율은 떨어집니다. worker 수가 W, 실제 작업 시간이 T일 때 이상적인 처리율은 대략 W / T지만, browser 작업의 T는 평균이 아니라 긴 꼬리에 지배됩니다.

자원포화 때 증상queue에 미치는 영향
CPUrendering·암호화·JS 실행 지연작업 시간이 늘어 consumer drain이 감소
메모리browser/page 누적, GC·OS 압박process 재시작 또는 새 launch 실패
page/contextnavigation 충돌·정리 지연계정 lock 보유 시간이 증가
proxy·외부 응답timeout·재시도·차단 분류같은 slot이 오래 점유됨
계정 lock같은 계정 요청의 대기전체 worker는 남아도 특정 계정은 진행 못 함

2. 그래서 fan-out을 늘리는 대신 제한했다

한 계정에서 여러 하위 대상을 조회할 때 Promise.all로 전부 실행하면 짧은 순간에는 빨라 보입니다. 하지만 같은 session과 page를 공유하는 작업이라면 동시에 열린 navigation이 서로의 상태를 바꾸고, browser slot·proxy 요청·외부 호출을 한꺼번에 잡아먹습니다.

실제 변경에서는 로그인 single-flight만으로는 부족하다는 판단 뒤, 계정별 작업 경계를 직렬화하고 하위 조회의 fan-out을 제한했습니다. 이는 처리량을 포기한 것이 아니라, 공유 상태를 가진 작업을 무제한 병렬화해 긴 꼬리를 키우는 것을 막기 위한 선택입니다.

무제한 fan-out
 → 같은 browser/page의 동시 navigation
 → 일부 작업 지연과 timeout
 → lock 보유 시간 증가
 → 같은 계정의 후속 요청 대기
 → consumer 처리율 하락
 → queue backlog 증가

3. queue depth만 보면 잘못된 처방을 하게 된다

queue가 쌓였을 때 worker를 늘리는 것은 자연스러운 반응입니다. 그러나 계정 lock, proxy, browser launch가 이미 병목이면 새 worker는 처리율을 올리기보다 같은 병목에 더 많은 경쟁자를 보냅니다.

그래서 용량 판단은 최소한 다음을 같이 봐야 합니다.

지표질문경보가 의미하는 것
queue depth와 age얼마나 많이, 얼마나 오래 기다리는가backlog가 제품 의미를 잃는지
작업 시간 분포P50이 아니라 P95/P99은 어떤가긴 꼬리가 slot을 잠식하는지
lock held time / force-release계정별 직렬화가 정상 범위인가timeout backstop이 정상 경로가 됐는지
browser launch·재시작새 slot이 안정적으로 생기는가process/page lifecycle 문제인지
proxy 결과외부 의존성이 worker 시간을 늘리는가scale-out이 아닌 proxy 정책 문제인지

이 지표를 분리하지 않으면 “queue가 쌓였다 → worker를 더 띄운다”는 처방만 남습니다. 실제로는 queue가 원인이 아니라, browser slot이 돌아오지 않는 결과일 수 있습니다.

4. timeout은 admission control이 아니다

force-release가 발생하면 다음 작업은 진행할 수 있습니다. 하지만 기존 작업을 취소하지 못한다면 두 작업이 겹칠 수 있습니다. 그래서 force-release를 처리량 향상 장치처럼 쓰면 안 됩니다.

올바른 순서는 다음에 가깝습니다.

  1. 정상 작업의 최대 시간을 관측해 timeout을 정한다.
  2. timeout 뒤에도 계속 실행되는 작업은 별도 지표·로그로 조사한다.
  3. browser 종료·page 정리·외부 timeout을 먼저 고친다.
  4. 그 뒤에도 유입이 drain rate를 넘으면 bounded queue에서 새 publish를 거절하거나 재예약한다.

이것이 1편의 bounded queue와 이어집니다. queue 상한은 browser worker를 빠르게 만드는 기능이 아니라, 느린 slot 때문에 생기는 미래의 작업 약속을 무한히 늘리지 않는 장치입니다.

5. 이 글의 한계

이 기록은 특정 host의 CPU 사용률만으로 “browser는 몇 개까지”라는 절대 숫자를 만들지 않습니다. browser 버전, 플랫폼, proxy, 로그인 조건에 따라 작업 시간 분포가 달라지기 때문입니다.

대신 다음 판단을 남깁니다.

browser worker의 capacity는 프로세스 개수가 아니라, 가장 느린 세션 작업이 slot을 붙잡는 시간과 그 긴 꼬리를 감당할 수 있는 운영 정책으로 결정된다.

다음 글에서는 broker가 같은 메시지를 다시 전달할 수 있는 환경에서, 외부 쓰기와 결과 상태를 어떤 별도 계약으로 다룰지 정리합니다.

References

구현과 운영 기록

참고 자료


Share this post on:

Previous Post
테스트 메타데이터는 왜 Map 하나로 끝나지 않았는가 — 공개 API의 가변성 경계
Next Post
세션을 잃지 않으려면 요청을 어디로 보내야 하는가 — affinity와 계정 단위 직렬화