Skip to content

ADR-05: 워커-스케줄러 프로세스 분리

🇺🇸 English Version

날짜작성자리포지토리
2024-12-18@KubrickCodecollector

배경

비대칭적 스케일링 요구사항

백그라운드 작업 처리 시스템은 일반적으로 근본적으로 다른 스케일링 특성을 가진 두 개의 구분된 컴포넌트로 구성됨:

스케줄러:

  • 주기적 작업 트리거 (cron 기반 태스크 enqueue)
  • 중복 실행 방지를 위해 단일 인스턴스로 실행 필수
  • 가벼운 리소스 사용량 (CPU <5%, 메모리 <256 MB)
  • 변경 빈도 낮음 (cron 표현식 업데이트, 새 작업 유형)

워커:

  • 큐에 있는 태스크 처리 (분석, 파일 작업, API 호출)
  • 큐 깊이에 따라 수평 확장
  • 높은 리소스 소비 (CPU 집약적, 대용량 페이로드를 위한 메모리)
  • 잦은 업데이트 (비즈니스 로직 변경, 알고리즘 개선)

통합 프로세스의 문제점

스케줄러와 워커를 단일 프로세스로 실행하면 여러 문제가 발생함:

문제영향
리소스 낭비스케줄러 코드가 모든 워커 인스턴스에서 실행 (미사용)
스케일링 비효율스케줄러 복제 없이 워커만 확장 불가
장애 커플링워커 OOM이 스케줄러까지 크래시
배포 종속스케줄러 변경에 전체 재배포 필요
분산 락 오버헤드모든 인스턴스가 락 획득 시도, 하나만 성공

서로 다른 의존성 요구사항

각 프로세스 유형은 고유한 의존성 요구사항을 가짐:

  • 워커: 암호화 키 필요 (비공개 저장소를 위한 OAuth 토큰 복호화)
  • 스케줄러: 분산 락 필요 (단일 인스턴스 보장), 암호화 불필요

이를 결합하면 불필요한 보안 노출과 설정 복잡성이 발생함.

결정

Worker와 Scheduler를 전용 진입점과 DI 컨테이너를 가진 독립 프로세스로 분리

아키텍처

┌──────────────┐      ┌───────────┐      ┌──────────────┐
│  Scheduler   │─────>│PostgreSQL │<─────│   Workers    │
│ (1 인스턴스) │      │River 큐   │      │ (0-N 확장)   │
└──────────────┘      └───────────┘      └──────────────┘
       │                    │                    │
       └────────────────────┴────────────────────┘

                     ┌──────────────┐
                     │  PostgreSQL  │
                     │(데이터 저장) │
                     └──────────────┘

진입점 분리

cmd/
├── worker/main.go     # 큐 태스크 처리
└── scheduler/main.go  # 주기적 작업 스케줄링

각 진입점:

  • 필요한 설정만 검증
  • 필요한 의존성만 초기화
  • 전용 graceful shutdown 처리

DI 컨테이너 분리

WorkerContainer:
├── 암호화 어댑터 (OAuth 토큰 복호화)
├── 분석 핸들러 (큐 태스크 프로세서)
├── 큐 클라이언트 (태스크 소비)
└── 공유: 데이터베이스 풀, PostgreSQL 연결

SchedulerContainer:
├── 분산 락 (단일 인스턴스 보장)
├── 스케줄러 핸들러 (주기적 작업 실행기)
├── 큐 클라이언트 (태스크 enqueue)
└── 공유: 데이터베이스 풀, PostgreSQL 연결

핵심 원칙: 워커 컨테이너는 락을 초기화하지 않고, 스케줄러 컨테이너는 암호화를 초기화하지 않음.

검토한 옵션

옵션 A: 프로세스 분리 (선택)

설명:

전용 진입점과 DI 컨테이너를 가진 별도 바이너리.

장점:

  • 독립적 스케일링 (워커: 0-N, 스케줄러: 정확히 1)
  • 장애 격리 (워커 크래시가 스케줄링에 영향 없음)
  • 리소스 최적화 (스케줄러: 최소, 워커: 대용량)
  • 명확한 보안 경계 (암호화 키는 워커에만)
  • 빌드 시점 최적화 (더 작은 스케줄러 바이너리)

단점:

  • 두 개의 배포 파이프라인 유지 필요
  • 설정 동기화 필요
  • 더 복잡한 모니터링 설정

옵션 B: 런타임 모드 플래그가 있는 단일 바이너리

설명:

환경 변수나 플래그에 따라 동작을 전환하는 단일 바이너리.

bash
./collector --mode=worker
./collector --mode=scheduler

장점:

  • 단일 빌드 산출물
  • 공유 코드베이스 (중복 감소)
  • 더 단순한 CI/CD 파이프라인

단점:

  • 바이너리에 미사용 코드 포함 (스케줄러가 워커 의존성을 메모리에 로드)
  • 런타임 잘못된 설정 위험 (잘못된 모드 배포)
  • 코드 검사로 서비스 역할 불명확
  • 여전히 별도 배포 설정 필요

옵션 C: 통합 프로세스

설명:

단일 프로세스가 스케줄러와 워커를 다른 고루틴에서 실행함.

장점:

  • 가장 단순한 배포 (하나의 서비스)
  • 프로세스 간 통신 오버헤드 없음
  • 단일 설정 파일

단점:

  • 컴포넌트 독립 스케일링 불가
  • 리소스 낭비 (모든 워커 인스턴스에 스케줄러)
  • 장애 커플링 (워커 panic이 스케줄러 종료)
  • max(스케줄러, 워커) 리소스로 프로비저닝 필요

구현 원칙

설정 검증

각 프로세스는 자신의 요구사항만 검증함:

Worker 시작:
├── DATABASE_URL 확인 (필수)
├── ENCRYPTION_KEY 확인 (필수) ← 워커 고유
└── 누락 시 즉시 실패

Scheduler 시작:
├── DATABASE_URL 확인 (필수)
├── 분산 락 초기화 ← 스케줄러 고유
└── 연결 실패 시 즉시 실패

분산 락 전략

스케줄러는 PostgreSQL 기반 분산 락을 사용해 단일 인스턴스 실행 보장함:

인스턴스 A: 락 획득 → 스케줄된 작업 실행
인스턴스 B: 락 획득 실패 → 대기 상태 유지
인스턴스 C: 락 획득 실패 → 대기 상태 유지

이점:

  • Blue-green 배포 중 고가용성
  • 활성 인스턴스 크래시 시 자동 장애조치
  • 장시간 작업을 위한 락 heartbeat TTL 연장

큐 기반 통신

스케줄러와 워커는 메시지 큐를 통해서만 통신함:

Scheduler ──[태스크 Enqueue]──> River Queue (PostgreSQL) ──[태스크 Dequeue]──> Worker

디커플링 이점:

  • 스케줄러가 워커 완료를 기다리지 않음
  • 워커가 자체 속도로 처리
  • 큐 깊이를 통한 자연스러운 백프레셔
  • 스케줄러-워커 간 직접 네트워크 통신 없음

Graceful Shutdown 처리

각 프로세스는 맞춤형 종료 동작을 가짐:

Worker 종료:

  1. 큐에서 새 태스크 수락 중지
  2. 진행 중 태스크 대기 (설정 가능한 타임아웃)
  3. 데이터베이스/PostgreSQL 연결 종료
  4. 종료

Scheduler 종료:

  1. cron 스케줄러 중지 (새 작업 트리거 방지)
  2. 현재 작업 완료 대기 (타임아웃 포함)
  3. 분산 락 해제
  4. 데이터베이스/PostgreSQL 연결 종료
  5. 종료

결과

긍정적

독립적 스케일링:

  • 스케줄러 변경 없이 큐 깊이에 따라 워커 확장
  • 스케줄러는 최소 상태 유지 (단일 인스턴스, 낮은 리소스)
  • PaaS 자동 스케일링은 워커에만 적용

비용 최적화:

  • 스케줄러: 고정된 최소 리소스 (~0.25 vCPU, 256 MB)
  • 워커: 수요에 따라 0-N 스케일
  • N개의 통합 인스턴스 실행 대비 상당한 절감

장애 격리:

  • 워커 메모리 고갈이 스케줄링에 영향 없음
  • 스케줄러 문제가 워커 태스크 처리 방해 안 함
  • 전면 장애 대신 부분 저하

배포 독립성:

  • 스케줄러 다운타임 없이 워커 로직 업데이트
  • 워커 재배포 없이 cron 스케줄 변경
  • 컴포넌트별 다른 릴리스 케이던스

보안 경계:

  • 암호화 키가 워커 프로세스에만 제한
  • 스케줄러가 축소된 권한으로 운영
  • 프로세스 유형별 명확한 감사 추적

부정적

운영 복잡성:

  • 모니터링, 배포, 유지관리할 서비스 두 개
  • 다중 CI/CD 파이프라인
  • 서비스 간 로그 집계

설정 관리:

  • 공유 태스크 스키마 조율 필요
  • 환경 변수 동기화
  • 큐 네이밍 일관성

디버깅 오버헤드:

  • 서비스 간 요청 추적
  • 분산 로그 상관관계
  • 다중 대시보드 모니터링

기술적 함의

측면함의
인프라별도 PaaS 서비스, 공유 PostgreSQL
배포독립 릴리스 사이클, 계약 변경 시 조율
스케일링워커: 자동 스케일, 스케줄러: 고정 단일 인스턴스
모니터링서비스별 메트릭, 통합 큐 깊이 모니터링
Blue-Green 배포워커: 중첩 가능, 스케줄러: 락 기반 핸드오프

참고 자료

Open-source test coverage insights