콘텐츠로 이동

ADR-0017: Process Separation: Bot / API / Worker

Umbra 의 Go 백엔드는 단일 모놀리스가 아닌 세 개의 독립 프로세스로 분리한다: umbra-bot (Discord Gateway), umbra-api (HTTP), umbra-worker (백그라운드 작업). 각 프로세스는 Fly.io 에서 독립 머신으로 배포된다.

Status

Accepted

  • Decided at — 2026-04-13
  • Decided by — Pablo

Context

Umbra 의 Go 백엔드는 세 가지 본질적으로 다른 작업을 수행해야 한다.

  • Discord Gateway 연결 유지 — WebSocket 상시 연결, 슬래시 커맨드와 이벤트 처리
  • HTTP API 제공 — 대시보드 요청, OAuth2 콜백, Toss 웹훅 수신
  • 백그라운드 작업 실행 — 결제 cron, Recovery 워크플로우, 알림, Live Sync 배치

세 작업은 lifecycle, 스케일 특성, 장애 영향이 다르다.

  • Gateway 는 연결 유지 (long-running WebSocket, 재시작 시 Discord 재연결 비용)
  • HTTP 는 stateless (자유로운 수평 확장)
  • Worker 는 처리량 중심 (작업 큐 깊이에 따라 확장)

Decision

세 가지를 독립 프로세스로 분리 한다.

  • umbra-bot — Discord Gateway 연결 프로세스, 1대 고정, auto-stop disabled
  • umbra-api — HTTP 서버 프로세스, 트래픽 기반 auto-scale
  • umbra-worker — 백그라운드 작업 프로세스 (asynq + Temporal + Outbox poller), 작업량 기반 auto-scale

각 프로세스는 Fly.io 의 별도 머신으로 배포되며, 셋 다 같은 engine/* 도메인 패키지를 공유한다 (ADR-0018).

선택 근거:

  • 장애 격리 — Bot 이 죽어도 결제 cron 은 계속 돌고, API 가 죽어도 진행 중 Recovery 는 완주
  • 독립 스케일링 — 트래픽 또는 작업량에 따라 각각 수평 확장
  • 프로세스 특성 최적화 — Bot 은 상시 1대, API/Worker 는 auto-scale 로 비용 효율
  • 배포 위험 감소 — API 만 변경되면 Bot 재시작 불필요 (Gateway 재연결 비용 회피)

Consequences

Positive

  • 한 영역의 장애가 다른 영역에 전파되지 않음
  • 리소스 효율 (각자 필요한 만큼만)
  • 배포 시 영향 범위 제한
  • 메모리/CPU 프로파일을 프로세스별로 분리 관측

Negative

  • 배포 단위 3개 (단일 모놀리스 대비)
  • Docker 이미지 3개 빌드 (빌드 시간 ↑)
  • 프로세스 간 조정이 필요한 경우 DB/Outbox 경유
  • 디버깅 시 프로세스 경계 인지 필요

Neutral

  • 세 프로세스가 같은 코드베이스를 공유하므로 마이크로서비스 수준의 복잡도는 아님
  • Bot 프로세스는 샤딩 필요 시 별도 설계 (ADR 재작성)

Process responsibilities

umbra-bot

Binaryapps/bot/cmd/main.go Deploy — Fly.io nrt, 1 instance, auto-stop disabled Load — Discord Gateway WebSocket + slash command 처리

책임:

  • Discord Gateway 연결 및 이벤트 수신 (GUILD_CREATE, GUILD_DELETE, MEMBER_JOIN 등)
  • 슬래시 커맨드 핸들러 (Discord 3초 응답 제한 내 ACK)
  • Live Sync 이벤트를 Redis/asynq 에 enqueue
  • Temporal workflow 시작 트리거 (/restore 커맨드)

umbra-api

Binaryapps/api/cmd/main.go Deploy — Fly.io nrt, auto-scale (HTTP 기반) Load — 대시보드 요청, 웹훅

책임:

  • 대시보드 API (app.umbra.ink)
  • OAuth2 콜백 (/oauth/discord/callback)
  • Web Join 핸들러 (join.umbra.ink/{slug})
  • Toss 웹훅 수신 (/webhooks/toss)
  • 세션 관리 (Redis)

umbra-worker

Binaryapps/worker/cmd/main.go Deploy — Fly.io nrt, auto-scale (큐 깊이 기반) Load — asynq + Temporal + Outbox

책임:

  • asynq 워커 (결제 cron, 재시도, Live Sync 배치, 알림)
  • Temporal 워커 (Restore, AntiNuke)
  • Outbox poller (2초 주기)
  • Snapshot 자동 생성, 이벤트 archive cron

Failure isolation examples

Scenario Bot API Worker
Bot 프로세스 OOM 재시작 중 영향 없음 영향 없음
API 프로세스 크래시 영향 없음 재시작 중 영향 없음
Worker 프로세스 종료 영향 없음 영향 없음 진행 중 workflow 는 Temporal 이 다른 워커로 재할당
Discord Gateway 장애 재연결 시도 영향 없음 영향 없음
Toss 장애 영향 없음 웹훅 수신 못함 결제 재시도 대기
Neon 장애 전체 영향 (공통 의존) 전체 영향 전체 영향

단, 공통 의존성(Neon, Redis, Temporal Server)의 장애는 전체 영향을 준다 — 이는 Fly.io 리전 장애와 동급으로 간주.

Alternatives considered

Alternative 1: 단일 모놀리스 (Bot + API + Worker 통합)

Pros

  • 배포 단위 1개
  • Docker 이미지 1개
  • 단순함

Cons

  • Bot 의 OOM 이 API 와 Worker 도 중단
  • 독립 스케일링 불가
  • Gateway 재연결 비용이 배포마다 발생

Why rejected — 장애 격리와 독립 스케일링 이점이 배포 단순성 대비 큼.

Alternative 2: 2프로세스 (Bot 만 분리, API + Worker 통합)

Pros

  • Bot 만 분리하면 Gateway 재연결 이슈 해결

Cons

  • API 와 Worker 가 같이 있으면 API 의 트래픽 폭증이 Worker 실행에 영향
  • Worker 의 장기 작업이 API 메모리를 잠식
  • 일부 이점만 얻고 일부 복잡도만 감수

Why rejected — 중간 해법의 가치가 3-process 대비 낮음.

Alternative 3: 마이크로서비스 (도메인별 독립 서비스)

Pros

  • 도메인 독립 배포

Cons

  • 서비스 간 RPC 오버헤드 (Discord 3초 제한 내 완결 어려움)
  • 학습 부담, 운영 복잡도
  • MVP 스코프 초과

Why rejected — MVP 에 과함. 프로세스 분리 + 코드 공유로 이점 대부분 확보(ADR-0018).

Compliance

  • apps/bot/, apps/api/, apps/worker/ 각각 독립 cmd/main.go
  • 각 프로세스의 fly.tomlapps/{bot,api,worker}/fly.toml 에 배치
  • 프로세스별 Docker 이미지 분리 (Dockerfile.bot / .api / .worker 또는 build target)
  • 프로세스 간 직접 RPC 금지 (ADR-0018)
  • 각 프로세스의 graceful shutdown 구현 필수

Revisit triggers

  • 길드 수 2,500 초과 → Bot 샤딩 재설계 (별도 ADR)
  • Worker 의 특정 도메인(Recovery 등)이 독립 스케일이 필요해지면 umbra-recovery-worker 같은 세분화 검토
  • 팀 규모 확장으로 도메인별 소유 분리가 필요하면 마이크로서비스 전환 검토

References

  • ADR-0010 — Fly.io 배포
  • ADR-0018 — 도메인 코드 공유 (RPC 대체)
  • ADR-0014 — Temporal worker (Worker 프로세스 내)
  • ADR-0015 — asynq worker (Worker 프로세스 내)
  • ADR-0016 — Outbox poller (Worker 프로세스 내)