콘텐츠로 이동

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 수신 시 다음 순서로 종료합니다.

  1. 새 작업 수신 중단 (asynq, Temporal 모두)
  2. 진행 중 작업 완료 대기 (최대 30초)
  3. Outbox poller 중단
  4. 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 가 자동으로 다음을 수행합니다.

  1. Neon 에 PR 브랜치 DB 생성
  2. 현재 main DB 의 snapshot 을 PR 브랜치에 복제
  3. atlas migrate apply 로 마이그레이션 적용
  4. 적용 결과와 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 배포 결정