스크럽 워커를 쪼개고, 스스로 깨어나게 만든 밤
목차
자정 무렵이었다. 스크럽 스크립트 하나가 점점 무거워지고 있었다. 처리 대상이 늘어날수록 단일 프로세스로 돌리는 건 한계가 뚜렷했고, 설상가상으로 외부 API 쪽 사용량 제한에 걸리면 그냥 죽어버렸다. 재개는 수동이었다. 그걸 이날 한 번에 고쳐야겠다고 마음먹었다.
해결 방향은 두 가지였다.
- 워커를 여러 개로 찢어서 병렬 실행
- 자율 재개 드라이버가 "지금 돌아도 되냐"를 묻는 탐침 모드
두 기능은 서로 독립적이지만, 합쳐지면 비로소 의미가 완성되는 짝이다.
샤딩: 일을 N등분하다
--shard i/N 옵션을 붙였다. 원리는 단순하다.
# 3개 워커로 쪼갤 때
python scrub_first_person.py --shard 0/3
python scrub_first_person.py --shard 1/3
python scrub_first_person.py --shard 2/3
전체 대상 목록을 인덱스로 나눠, 각 워커가 자기 담당 슬라이스만 처리한다. 중앙 조율자 없이도 겹치지 않고 분산된다.
고민했던 건 분할 방식이었다. 해시 기반이냐, 순번 기반이냐. 결국 순번(index % N == i) 방식으로 갔다. 재현성이 중요했기 때문이다. 같은 i/N은 항상 같은 슬라이스를 가리켜야 했다. 외부 상태 없이 결정론적으로 동작하는 게 자율 재개 시나리오에서 훨씬 안전하다.
프로브: "지금 달려도 돼?"
--probe 플래그는 실제 스크럽 작업을 하지 않는다. 딱 한 가지만 확인한다 — 현재 사용량 제한 상태가 재개 가능한지.
| 모드 | 동작 | 종료 코드 |
|---|---|---|
| 일반 실행 | 스크럽 작업 수행 | 0 (성공) / 1 (오류) |
--probe |
사용량 한도 체크만 | 0 (재개 가능) / 2 (한도 초과) |
자율 재개 드라이버는 이 종료 코드를 읽는다. 2가 나오면 일정 시간 후 다시 프로브, 0이 되면 그때 실제 워커를 띄운다. 드라이버가 스크럽의 내부 로직을 알 필요가 없다. 프로토콜은 종료 코드 하나로 충분하다.
이 설계가 마음에 들었던 이유는 관심사 분리다. 스크럽 스크립트는 "어떻게 스크럽하느냐"만 알면 된다. "언제 재개하느냐"는 드라이버의 몫이다.
이 두 커밋이 같은 밤에 나온 이유
샤딩만 있으면 병렬 실행은 되지만, 한도 초과 시 워커 N개가 동시에 죽고 N개를 수동으로 다시 살려야 한다. 프로브만 있으면 드라이버가 재개를 결정할 수 있지만, 재개해도 단일 프로세스라 느리다. 둘 다 있어야 비로소 루프가 닫힌다 — 드라이버가 사용량 여유를 확인하고, 샤딩된 워커들을 한꺼번에 재시동하는 흐름.
자율 운영을 목표로 하면, 기능 하나하나는 작아 보여도 조합됐을 때 전혀 다른 수준의 안정성이 나온다. 이날 밤 커밋 두 개가 그 조합이었다.
🛒 이 글과 어울리는 추천 상품
*위 링크는 쿠팡파트너스 활동의 일환이며, 일정액의 수수료를 제공받을 수 있습니다.
댓글 0
첫 댓글 달아줘.