ADR-12: Worker 중심 분석 라이프사이클
| 날짜 | 작성자 | 리포지토리 |
|---|---|---|
| 2024-12-16 | @KubrickCode | web, worker |
배경
기존 아키텍처
ADR-03에서 API(Web)와 Worker 서비스 분리 확립. ADR-04에서 큐 기반 비동기 처리 도입. 초기 구현의 이중 소유권 문제 발생:
기존 흐름:
사용자 요청 → Web이 "pending" 레코드 생성 → Enqueue → Worker 처리 → 레코드 업데이트문제: 두 서비스가 동일한 데이터베이스 레코드 조작
이중 소유권의 문제점
| 문제 | 영향 |
|---|---|
| 중복 레코드 | Web이 pending 레코드 생성, Worker도 별도 레코드 생성 가능 |
| 상태 불일치 | Web의 DB 상태와 실제 큐 상태 불일치 가능성 |
| 복잡한 에러 복구 | 실패 시 두 서비스 간 조율 필요 |
| 경쟁 조건 | 재시도 요청으로 다수 pending 레코드 생성 가능 |
| 불명확한 책임 | 레코드 상태의 권위 있는 소스 모호 |
근본 원인
핵심 문제: 분석 레코드 라이프사이클에 대한 단일 진실 공급원(Single Source of Truth) 부재. Web과 Worker 모두 동일 레코드에 쓰기 권한 보유로 동기화 복잡성 야기.
결정
Worker가 레코드 생성, 처리, 완료를 독점적으로 소유하는 Worker 중심 분석 라이프사이클 채택.
새로운 아키텍처
사용자 요청 → Web (enqueue만) → Queue → Worker (생성 → 처리 → 완료)
↓ ↓
UUID 생성 레코드 라이프사이클 단일 소유
큐에서 상태 확인 작업 시작 시 레코드 생성
완료/실패 시 업데이트핵심 원칙:
- Web: Enqueue만 - 분석 UUID 생성, 작업 enqueue, 분석 테이블에 쓰기 금지
- Worker: 전체 소유 - 작업 시작 시 레코드 생성, 완료 시 업데이트
- Queue가 상태 소스 - Web은 진행 중 분석을 DB가 아닌 큐에서 확인
- 단일 Writer - Worker만 분석 레코드에 쓰기
검토한 옵션
옵션 A: Worker 중심 소유권 (선택)
Web이 생성된 UUID와 함께 분석 요청 enqueue. Worker가 처리 시작 시 레코드 생성, 완료 시 갱신.
장점:
- 단일 진실 공급원: 하나의 서비스가 전체 라이프사이클 소유
- 중복 레코드 없음: Worker만 분석 항목 생성
- 명확한 서비스 경계: Web은 HTTP, Worker는 분석 담당
- 단순한 에러 처리: 모든 실패 상태를 한 곳에서 관리
- 트랜잭션 일관성 향상: 동일 서비스 컨텍스트에서 생성과 업데이트
- 독립적 확장: Worker가 Web에 영향 없이 확장 가능
단점:
- Web이 상태 조회를 위해 큐에 의존
- 큐 시스템이 중요 인프라로 격상
- 상태 확인 로직 복잡성 증가
옵션 B: Web 중심 소유권
Web이 모든 레코드 생성 및 관리. Worker는 기존 레코드만 업데이트.
장점:
- 추적을 위한 즉각적인 DB 레코드
- 단순한 상태 조회 (항상 DB에서)
단점:
- Worker가 "레코드 없음" 케이스 처리 필요
- DB 커밋 전 큐 처리 시 타이밍 문제 발생
- 재시도 로직 복잡화 (레코드 존재 여부 확인 필요)
- Web이 레코드 생성 병목으로 작용
옵션 C: 이중 소유권 (기존)
두 서비스가 조율 로직으로 레코드 생성 및 수정 가능.
장점:
- 구현의 유연성
단점:
- 중복 레코드 위험
- 복잡한 조율 필요
- 분산 트랜잭션과 유사한 복잡성
- 진실 공급원 모호성
구현 상세
Queue Payload
json
{
"owner": "github-org",
"repo": "repo-name",
"commit_sha": "abc123",
"user_id": "uuid",
"analysis_id": "uuid"
}analysis_id: Web에서 미리 생성
상태 조회 흐름 (Web)
- owner/repo와 일치하는 활성 작업이 큐에 있는지 확인
- 활성 작업 발견 → "pending" 상태 반환
- 활성 작업 없음 → DB에서 완료/실패 분석 조회
중복 제거
CommitSHA 기반 고유성으로 중복 분석 방지:
- 동일 repo+commit에 대한 다중 요청 → 단일 큐 작업
- River의 (owner, repo, commit_sha) 유니크 제약
- 동시 요청 시 불필요한 연산 방지
결과
긍정적
데이터 무결성:
- 중복 분석 레코드 없음
- 일관된 라이프사이클 상태 머신
- 명확한 소유권으로 경쟁 조건 제거
운영 단순성:
- 분석 문제 디버깅을 위한 단일 서비스
- 명확한 로그와 트레이스
- 단순한 모니터링 (추적할 writer가 하나)
확장성:
- Worker가 큐 깊이에 따라 독립적으로 확장
- Web은 가벼움 유지 (분석용 무거운 DB 쓰기 없음)
- 큐가 자연스럽게 부하 버퍼링
미래 호환성:
- 예약 재분석과 정렬 (Worker ADR-01)
- 사용자 시작이든 예약이든 동일한 라이프사이클
- 일관된 소유권 모델
부정적
큐 의존성:
- Web이 상태를 위해 큐 조회 필요
- 큐 불가용 시 상태 조회에 영향
- 완화책: 큐가 PostgreSQL 백엔드 공유 (River), DB와 동일한 가용성
지연된 가시성:
- Worker가 처리 시작할 때까지 분석이 DB에 미노출
- enqueue와 레코드 생성 사이 짧은 지연
- 완화책: 큐 상태가 즉각적인 피드백 제공
복잡성 이동:
- 상태 로직이 단순 DB 조회에서 큐 검사로 이동
- 완화책: repository/adapter 레이어에 캡슐화
기술적 함의
| 측면 | 함의 |
|---|---|
| 트랜잭션 범위 | 레코드 생성 + 초기 상태가 단일 Worker 트랜잭션에서 |
| 실패 처리 | 모든 재시도가 Worker에서 관리 |
| 큐 스키마 | 상태를 위한 owner/repo 조회 지원 필요 |
| 모니터링 | 큐 메트릭이 분석 상태 표시 |
| 예약 분석 | 사용자 시작과 예약 분석이 동일한 라이프사이클 |
