Deployment Topology¶
Umbra 는 Fly.io 세 머신(bot, api, worker), Vercel 정적 호스팅, Neon / Redis / Temporal 의 외부 관리형 서비스로 구성됩니다. 모든 구성 요소는 asia-northeast1 리전(서울)에 집중되어 latency 를 최소화합니다.
Why this topology¶
배포 토폴로지의 핵심 결정 세 가지입니다.
첫째, Fly.io 선택 입니다. Discord Gateway 는 상시 WebSocket 연결을 유지해야 하므로 Cloud Run 같은 stateless 서버리스가 부적합합니다. Fly.io 는 long-running 프로세스를 지원하면서 서울 리전(nrt)과 간단한 배포 UX 를 제공합니다.
둘째, 프로세스별 독립 머신 입니다. bot / api / worker 는 lifecycle 과 스케일링 특성이 달라 같은 머신에 묶으면 효율이 떨어집니다. Fly.io 의 머신 단위 배포는 이 분리를 자연스럽게 지원합니다.
셋째, 관리형 서비스 적극 활용 입니다. Neon, Redis(Upstash), Grafana Cloud 는 모두 관리형 서비스를 사용합니다. 자체 호스팅이 주는 이점보다 MVP 단계의 운영 부담 감소 이점이 큽니다. Temporal 만 예외적으로 MVP 는 self-hosted 로 시작합니다.
Topology diagram¶
graph TB
subgraph Cloudflare
DNS[DNS
umbra.ink]
end
subgraph Vercel
Web[umbra-web
React SPA]
end
subgraph Fly.io nrt
Bot[umbra-bot
disgo gateway]
API[umbra-api
Echo HTTP]
Worker[umbra-worker
asynq + Temporal worker + Outbox]
TemporalSvc[Temporal Server
self-hosted]
end
subgraph Managed Services
Neon[(Neon Postgres
ap-northeast-2)]
Redis[(Upstash Redis
ap-northeast-1)]
Grafana[Grafana Cloud
OTel endpoint]
end
subgraph External
Discord[Discord API]
Toss[Toss Payments]
end
User[End User] -->|HTTPS| DNS
DNS -->|app.umbra.ink| Web
DNS -->|api.umbra.ink| API
DNS -->|join.umbra.ink| Web
Web -->|fetch| API
Discord -.->|Gateway WS| Bot
Bot -->|REST| Discord
API -->|REST| Discord
Worker -->|REST| Discord
API -->|REST| Toss
Worker -->|REST| Toss
Toss -.->|Webhook| API
Bot --> Neon
API --> Neon
Worker --> Neon
TemporalSvc --> Neon
Bot --> Redis
API --> Redis
Worker --> Redis
Worker --> TemporalSvc
API --> TemporalSvc
Bot -->|OTel| Grafana
API -->|OTel| Grafana
Worker -->|OTel| Grafana
Regions¶
모든 구성 요소는 한국 사용자 latency 최적화를 위해 동아시아 리전에 집중됩니다.
| Service | Region | Notes |
|---|---|---|
| Fly.io | nrt (Tokyo) | asia-northeast1, Umbra 의 주 배포 리전 |
| Neon | ap-northeast-2 (Seoul) | 서울 리전 직접 제공 |
| Upstash Redis | ap-northeast-1 (Tokyo) | Fly.io 와 동일 리전 |
| Vercel | Global edge | CDN 이라 서울 edge 가 자동 사용 |
| Grafana Cloud | ap-southeast-1 (Singapore) | 한국 리전 미제공, 관측 데이터는 수 ms 지연 허용 |
Processes¶
umbra-bot¶
Role — Discord Gateway 연결 유지, 슬래시 커맨드 및 Discord 이벤트 처리
Fly.io configuration
- Primary region:
nrt - Instance count: 1 (상시)
- Scale policy: 고정
- VM size: shared-cpu-1x / 512MB (MVP)
- Auto-stop: disabled (게이트웨이 연결 유지)
Scaling notes
Discord Gateway 는 단일 연결을 선호합니다. 길드 수 2,500 을 초과하면 샤딩이 필수가 되며, 이 시점에 샤드별 머신 분리 및 shard coordinator 설계가 필요합니다. MVP 스코프에서는 단일 머신으로 충분합니다.
Health check
Discord Gateway 연결 상태를 /health 엔드포인트로 노출합니다. 연결 끊김 감지 시 자동 재시작됩니다.
umbra-api¶
Role — HTTP API 제공, 대시보드 요청, Toss 웹훅 수신, OAuth2 콜백
Fly.io configuration
- Primary region:
nrt - Instance count: 1~3 (auto-scale)
- Scale policy: HTTP 요청 수 기반
- VM size: shared-cpu-1x / 512MB (MVP)
- Auto-stop: disabled (세션 연결성 유지)
Scaling notes
stateless HTTP 서버이므로 수평 확장 자유롭습니다. 세션은 Redis 에 저장되어 머신 간 공유됩니다. 웹훅 중복 처리는 Redis idempotency 체크로 방지됩니다.
Health check
/healthz 엔드포인트에서 DB 연결, Redis 연결 상태를 확인합니다.
umbra-worker¶
Role — asynq 워커(결제 cron, Live Sync 배치, 알림), Temporal 워커(Recovery, AntiNuke), Outbox poller
Fly.io configuration
- Primary region:
nrt - Instance count: 1~5 (auto-scale)
- Scale policy: asynq queue depth 및 Temporal pending workflow 수 기반
- VM size: shared-cpu-2x / 1GB (MVP, 복구 워크로드 고려)
- Auto-stop: disabled (진행 중 작업 보호)
Scaling notes
작업이 몰리는 시점(매월 1일 0시 결제 집중 등)에 수평 확장됩니다. jitter 가 결제 시점을 ±15분 분산시켜 부하 피크를 완화합니다. Temporal 워크플로우는 워커가 늘어나면 자동 분산 실행됩니다.
Graceful shutdown
SIGTERM 수신 시 다음 순서로 종료합니다.
- 새 작업 수신 중단 (asynq, Temporal 모두)
- 진행 중 작업 완료 대기 (최대 30초)
- Outbox poller 중단
- DB/Redis 연결 종료
umbra-web¶
Role — 사용자 대시보드 UI, 웹 조인 페이지, 정적 마케팅 페이지
Vercel configuration
- Framework: Vite (SPA)
- Build command:
bun run build - Output directory:
dist/ - Node version: 20.x (Vercel 빌드 호스트)
Preview deployments
PR 마다 자동 생성됩니다. PR 병합 전 디자인 검증과 리그레션 확인에 사용됩니다.
Environment variables
VITE_API_BASE_URL— umbra-api 엔드포인트VITE_TOSS_CLIENT_KEY— Toss Payments 클라이언트 키(공개 가능)
Managed services¶
Neon Serverless PostgreSQL¶
Plan — Scale (유료) 또는 Launch (MVP)
Regions — ap-northeast-2 (Seoul)
Configuration
- Compute size: 0.5 CU ~ 2 CU (auto-scale)
- Pooler endpoint 활성화 (umbra-api, umbra-worker 연결용)
- Direct endpoint (Temporal Server persistence 용)
- Branching: main(prod), dev, PR 브랜치 자동 생성
Backup
Neon 자체 포인트 타임 복구(PITR) 기능으로 7일 보관(Scale plan 기준).
Upstash Redis¶
Plan — Pay-as-you-go 또는 Fixed
Region — ap-northeast-1 (Tokyo) 또는 Global
Use cases
- 세션 저장 (TTL 기반)
- asynq queue backend
- idempotency 체크 (Toss 웹훅 중복 방지)
Configuration
- Max memory: 256MB (MVP)
- Eviction policy:
allkeys-lru - TLS: enabled (기본)
Grafana Cloud¶
Plan — Free tier (MVP 에 충분)
Endpoints
- OTLP traces:
tempo-*.grafana.net - OTLP metrics:
prometheus-*.grafana.net - Logs:
logs-*.grafana.net
Configuration
모든 Go 프로세스는 OTEL_EXPORTER_OTLP_ENDPOINT 환경 변수로 Grafana Cloud OTel endpoint 를 바라봅니다.
Temporal Server¶
Hosting — Self-hosted on Fly.io (MVP), Temporal Cloud 전환 검토 (Phase 2)
Persistence — Neon PostgreSQL 공유 (별도 schema temporal)
Configuration
- Single node, Fly.io nrt
- Web UI: 내부 접근만 (Fly.io wireguard 또는 SSH tunnel)
Why self-hosted first — Temporal Cloud 는 한국 리전 미제공(도쿄/싱가포르만), MVP 규모에서는 self-host 비용이 더 낮음. 운영 부담이 커지면 Cloud 전환.
DNS & domains¶
Cloudflare 가 DNS 를 관리합니다. umbra.ink 는 Luxtra(pabloqbit) 계정에서 보유.
| Subdomain | Target | Purpose |
|---|---|---|
umbra.ink | Vercel | 마케팅 랜딩 페이지 |
app.umbra.ink | Vercel | 대시보드 SPA |
join.umbra.ink | Vercel | 웹 조인 페이지 (SPA 안의 특정 route) |
api.umbra.ink | Fly.io (umbra-api) | HTTP API |
bot.umbra.ink | — | 현재 미사용, 향후 봇 관련 리소스 |
SSL 인증서는 Cloudflare 가 자동 관리합니다.
Secrets management¶
환경 변수와 시크릿은 다음과 같이 관리됩니다.
| Secret | Stored in | Access |
|---|---|---|
| Neon DATABASE_URL | Fly.io secrets | bot, api, worker |
| Redis URL | Fly.io secrets | bot, api, worker |
| Toss API key | Fly.io secrets | api, worker |
| Discord bot token | Fly.io secrets | bot |
| Discord OAuth2 secret | Fly.io secrets | api |
| Billing key encryption key | Fly.io secrets | api, worker |
| OTel endpoint credentials | Fly.io secrets | bot, api, worker |
프론트엔드 노출 가능 값(Toss client key, API base URL)은 Vercel 환경 변수로 관리됩니다.
CI/CD¶
GitHub Actions 가 빌드 및 배포를 담당합니다.
Backend pipeline¶
graph LR
PR[Pull Request] --> Lint[golangci-lint]
Lint --> Build[go build]
Build --> Test[go test]
Test --> ReviewApp[Fly.io review app
optional]
Merge[main merge] --> Deploy[fly deploy]
Deploy --> bot[umbra-bot]
Deploy --> api[umbra-api]
Deploy --> worker[umbra-worker]
Frontend pipeline¶
Vercel 이 자체 CI 를 실행합니다. GitHub Actions 와 별도로 동작하며 PR 마다 preview deployment 를 자동 생성합니다.
Database migration¶
Atlas 가 마이그레이션을 관리합니다. PR 에 schema 변경이 포함되면 CI 가 자동으로 다음을 수행합니다.
- Neon 에 PR 브랜치 DB 생성
- 현재 main DB 의 snapshot 을 PR 브랜치에 복제
atlas migrate apply로 마이그레이션 적용- 적용 결과와 schema drift 를 PR 코멘트로 게시
main merge 시 production DB 에 마이그레이션이 적용됩니다. 위험한 마이그레이션(대용량 UPDATE, ALTER TABLE 등)은 수동 승인 게이트를 둡니다.
Monitoring & alerting¶
| Signal | Source | Alert channel |
|---|---|---|
| Fly.io 머신 다운 | Fly.io | Slack / Discord webhook |
| API 5xx rate > 1% | Grafana Cloud | Slack |
| 결제 실패율 > 10% | Grafana Cloud | Slack |
| Temporal workflow 실패 | Temporal Web UI | 수동 확인 |
| outbox lag > 1분 | Grafana Cloud | Slack |
MVP 단계에서는 alerting 이 최소화되며 운영 경험이 쌓이면서 임계값을 조정합니다.
Cost estimate¶
MVP 단계의 월간 대략 비용입니다.
| Service | Cost |
|---|---|
| Fly.io (3 machines, shared-cpu, 총 2GB RAM) | ~$15 |
| Neon (Launch plan) | ~$20 |
| Upstash Redis (Fixed 10K) | ~$10 |
| Vercel (Hobby → Pro) | $0 or $20 |
| Cloudflare | $0 (Free plan) |
| Grafana Cloud | $0 (Free tier) |
| Temporal self-hosted | $0 (Fly.io 비용에 포함) |
| Toss Payments | 결제 수수료만 |
| Total | ~$45 ~ $65 / month |
프로덕션 트래픽이 본격적으로 발생하면 Neon 과 Fly.io 비용이 증가합니다.
Disaster recovery¶
Data loss¶
- Neon PITR 로 최대 7일 이전 시점 복구 가능
- 길드 상태 는 Umbra 자체의 Snapshot 으로 사용자가 직접 복구
Regional outage¶
Fly.io nrt 리전 장애 시 대응:
- Bot 은 복원 시 자동으로 다시 연결 (disgo 가 reconnect 처리)
- API 는 DNS 를 다른 리전으로 수동 전환 (MVP 에서는 single-region 허용)
- Worker 의 Temporal 워크플로우는 Temporal 서버 자체가 다른 리전에 있으면 복구 후 이어짐
Phase 2 에서 멀티 리전 배포를 검토합니다.
Service dependency failure¶
- Discord API 장애 — 외부 의존성이라 우회 불가. 알림만 발송, 자체 대시보드는 정상 동작.
- Toss 장애 — 결제 재시도 큐가 자동으로 재시도 정책 수행 (24h / 48h / 72h)
- Neon 장애 — 전체 시스템 중단. Neon 자체 SLA 에 의존.
Constraints¶
- MVP 단계에서 Single region (nrt) 운영
- Neon 무료 플랜은 프로젝트 당 1 DB 제한, Scale plan 이후 multi-project 가능
- Fly.io shared-cpu 는 burstable 성능이라 지속 고부하 시 dedicated-cpu 필요
- Temporal self-hosted 는 단일 노드 운영. HA 가 필요하면 Temporal Cloud 전환.
See also¶
architecture/overview.md— 시스템 아키텍처 전체architecture/tech-stack.md— 기술 스택architecture/process-communication.md— 프로세스 간 통신guides/deployment.md— 배포 실무 가이드adr/0010-deploy-fly-vercel.md— Fly.io / Vercel 배포 결정