Skip to main content

Multi-region deployment

Architecture​

Three regions deployed active-active: SG (Singapore, primary), IN (India), EU (Frankfurt). Each region has its own Postgres+Redis+KMS. Org data is pinned to home region (enforced by RLS).

Region pinning contract​

  1. org_home_region table maps org_id → home_region + residency_pinned flag
  2. Row-Level Security on request_logs, messages, sessions tables:
    • Each connection sets app.region GUC via AfterConnect hook
    • RLS policy: home_region = current_setting('app.region', true)
    • Cross-region queries return zero rows — data never leaves home region

Failover rules​

  • Non-pinned orgs: When home region is unhealthy, route to next best healthy peer (by priority)
  • Pinned orgs: Return 503 if home region is down — no cross-region data movement

Edge routing​

Cloudflare Worker (deploy/cloudflare/edge-router.js) resolves org_id → home_region before upstream call:

  1. Auth header → API key → org_id lookup
  2. Internal API call → get home_region + residency_pinned
  3. If home_region ≠ current colo → 307 redirect to peer gateway
  4. If pinned + region down → 503

Federated analytics​

Daily rollups ship from each region to central analytics:

  • org_id hashed to 8-char SHA-256 prefix (k-anonymity)
  • No PII, no message content — only counts and token metrics
  • Shipper retries 3x with exponential backoff

Per-region KMS​

Each region has its own KMS key for envelope encryption:

  • local provider: in-process AES-GCM (dev/air-gap)
  • aws provider: AWS KMS (production)
  • Keys are never shared across regions

Deployment​

Terraform​

cd deploy/terraform/regions/sg && terraform init -backend=false && terraform validate
# Repeat for in/ and eu/ with region-specific data sources

Post-create: enable pgvector on each RDS instance:

PGHOST=<pg_address> PGUSER=yabanin PGPASSWORD=<pw> psql -d yabanin -c "CREATE EXTENSION IF NOT EXISTS vector;"

Helm​

helm upgrade --install yabanin deploy/helm/yabanin/ -f deploy/helm/yabanin/values-sg.yaml
# Repeat for in/eu with their respective values files

Migration runner​

YABANIN_REGION=sg YABANIN_PG_URI="postgres://..." go run cmd/yabanin-migrate/main.go migrations/
# Sets app.region GUC before applying each migration for RLS backfill

Configuration (config.yaml)​

gateway:
region: sg
home_region: SG
kms_provider: local
regions:
sg: { name: sg, country: SG, pg_uri: "...", redis_addr: "...", priority: 1 }
in: { name: in, country: IN, pg_uri: "...", redis_addr: "...", priority: 2 }
eu: { name: eu, country: DE, pg_uri: "...", redis_addr: "...", priority: 3 }

Chaos drill​

./scripts/chaos_drill.sh sg
# Kills SG region, verifies non-pinned orgs failover to IN/EU, pinned orgs get 503

See docs/runbooks/region-down.md for operational procedures.