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¶
Binary — apps/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¶
Binary — apps/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¶
Binary — apps/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.toml을apps/{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같은 세분화 검토 - 팀 규모 확장으로 도메인별 소유 분리가 필요하면 마이크로서비스 전환 검토