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
org_home_regiontable maps org_id → home_region + residency_pinned flag- Row-Level Security on
request_logs,messages,sessionstables:- Each connection sets
app.regionGUC via AfterConnect hook - RLS policy:
home_region = current_setting('app.region', true) - Cross-region queries return zero rows — data never leaves home region
- Each connection sets
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:
- Auth header → API key → org_id lookup
- Internal API call → get home_region + residency_pinned
- If home_region ≠ current colo → 307 redirect to peer gateway
- 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:
localprovider: in-process AES-GCM (dev/air-gap)awsprovider: 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.