11 KiB
Guida Multi-Region — adiuvAI API
Stato attuale: FastAPI containerizzata (docker-compose) su singolo VPS Hetzner in Europa. Obiettivo: ridurre la latenza per utenti fuori dall'Europa.
Fase 1 — Ottimizzare Cloudflare (già in uso)
1.1 Argo Smart Routing
- Dashboard → Traffic → Argo — attivalo (~$5/mese + $0.10/GB)
- Usa i backbone privati Cloudflare invece dell'internet pubblico
- Riduce la latenza del 30-40% senza toccare nulla lato server
- Singolo cambiamento con miglior rapporto costo/beneficio
1.2 SSL/TLS
- Dashboard → SSL/TLS → Overview → mode Full (Strict) (non "Flexible", causa redirect loop)
- Abilita TLS 1.3 (meno round-trip nell'handshake)
- Abilita Early Hints (103) in Speed → Optimization
1.3 Cache Rules
Di default Cloudflare non cachea le risposte API (Content-Type application/json). Per gli endpoint pubblici (es. /api/v1/health):
- Dashboard → Caching → Cache Rules → crea regola:
- Match:
URI Path starts with /api/v1/health - Action: Cache, Edge TTL 30s, Browser TTL 10s
- Match:
- Lato codice: aggiungere header
Cache-Control: public, s-maxage=30eCDN-Cache-Control: public, max-age=30all'health endpoint - NON cacheate endpoint autenticati (il JWT rende ogni richiesta unica)
1.4 Response Compression
- Dashboard → Speed → Optimization → Content Optimization
- Abilita Brotli (più efficiente di gzip per payload JSON)
- Le risposte JSON vengono compresse automaticamente al transit
1.5 WebSocket
- Dashboard → Network → verifica che WebSockets sia ON (default nel piano Free)
- Il
/chat/streamWebSocket viene proxato ma non cacheato - Il keepalive di 30s che già avete mantiene la connessione viva attraverso Cloudflare
1.6 Tiered Cache (piano Pro+)
- Dashboard → Caching → Tiered Cache → attiva Smart Tiered Caching
- Cloudflare usa data center "upper-tier" come cache intermedia
- Riduce le hit al tuo origin server
1.7 Timeout
- Dashboard → Network → WebSocket timeout: aumenta se gli utenti hanno sessioni chat lunghe
- Proxy Read Timeout: default 100s, sufficiente per le LLM call (il tool loop ha cap 5 iterazioni)
Fase 2 — Secondo nodo in US East
Architettura target
┌─── Cloudflare (Geo Steering) ───┐
│ │
utenti EU/Africa utenti Americas
│ │
┌────────▼─────────┐ ┌──────────▼─────────┐
│ VPS EU (attuale) │ │ VPS US (nuovo) │
│ docker-compose │ │ docker-compose │
│ app + PG primary │ │ app + PG replica │
└────────┬──────────┘ └──────────┬──────────┘
│ │
└── PG streaming replication ────────┘
(async, read-only replica)
Opzione A: Secondo VPS Hetzner (Ashburn) + Cloudflare Load Balancing
Estensione naturale del setup attuale — minimo cambiamento architetturale.
Step 1 — Provisioning del VPS US
- Crea un VPS Hetzner in Ashburn (us-east) (stesse specs del nodo EU)
- Setup identico: Docker, Docker Compose, git
- Configura un tunnel WireGuard tra EU e US per il traffico DB (mai esporre PG sulla rete pubblica)
Step 2 — PostgreSQL Streaming Replication
Sul PRIMARY (EU):
- Creare un utente replication:
CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD '<strong_password>'; - Creare un replication slot:
SELECT pg_create_physical_replication_slot('replica_us_east'); - Configurare
pg_hba.confper permettere connessioni dal VPS US:host replication replicator <US_VPS_WIREGUARD_IP>/32 scram-sha-256 - Esporre la porta PG solo sull'IP WireGuard nel
docker-compose.yml:services: db: ports: - "10.0.0.1:5432:5432" # solo interfaccia WireGuard
Sul REPLICA (US):
-
Base backup dal primary:
docker run --rm \ -v postgres_data:/var/lib/postgresql/data \ pgvector/pgvector:pg16 \ bash -c "pg_basebackup -h <PRIMARY_WG_IP> -U replicator \ -D /var/lib/postgresql/data -Fp -Xs -P -R"Il flag
-Rcrea automaticamentestandby.signale scriveprimary_conninfoinpostgresql.auto.conf. -
Avviare PG in modalita replica (legge
standby.signale si connette al primary) -
Verificare:
- Sul primary:
SELECT * FROM pg_stat_replication;(deve mostrare il replica) - Sul replica:
SELECT pg_is_in_recovery();(deve restituiret)
- Sul primary:
Step 3 — Modifiche al codice FastAPI
Modifiche necessarie in app/config/settings.py:
- Aggiungere
DATABASE_READ_URL: str = ""— URL del replica locale per le letture - Aggiungere
REGION: str = "eu"— identificativo regione per health check e observability
Modifiche in app/db.py:
- Creare un secondo engine
read_engineche usaDATABASE_READ_URL(fallback aDATABASE_URLse vuoto) - Esporre un
get_read_session()dependency da usare nelle query read-only
Modifiche in app/main.py:
- L'health endpoint deve restituire
regionnel payload - Aggiungere header
Cache-Control/CDN-Cache-Controlper il caching all'edge
Nelle route, per le query di sola lettura pesanti (es. ricerca, listing):
- Usare
db: AsyncSession = Depends(get_read_session)invece diget_session - Le scritture (auth, billing, update) continuano a usare
get_session(→ primary EU)
Step 4 — Docker Compose per il nodo US
Creare un docker-compose.replica.yml (override) che:
- Sovrascrive le env dell'app con
DATABASE_READ_URLverso il DB locale eDATABASE_URLverso il primary EU - Imposta
REGION=us-east - Avvia PG in modalita replica (con
primary_conninfoche punta al primary EU via WireGuard)
Il .env sul nodo US:
DATABASE_URL=postgresql+asyncpg://postgres:<pass>@<PRIMARY_WG_IP>:5432/adiuvai
DATABASE_READ_URL=postgresql+asyncpg://postgres:postgres@db:5432/adiuvai
REGION=us-east
PRIMARY_DB_HOST=<PRIMARY_WG_IP>
REPLICATOR_PASSWORD=<strong_password>
# ... resto delle variabili (JWT_SECRET, STRIPE, LLM keys, etc.) identiche al nodo EU
Avvio: docker compose -f docker-compose.yml -f docker-compose.replica.yml up -d
Step 5 — Deploy CI multi-region
Estendere il workflow .gitea/workflows/deploy.yaml con un secondo job deploy-us:
- Identico a
deployma con SSH verso il VPS US - Usa
secrets.SSH_HOST_US,secrets.SSH_USER_US,secrets.SSH_KEY_US - Il comando di deploy usa
-f docker-compose.yml -f docker-compose.replica.yml - NON esegue
alembic upgrade head— le migrazioni girano solo sul primary (il replica riceve le DDL via replication)
I due job deploy e deploy-us possono girare in parallelo (entrambi dipendono solo da test).
Step 6 — Cloudflare Geo Steering
- Dashboard → Traffic → Load Balancing (piano Pro, ~$5/mese per pool)
- Creare due Origin Pools:
eu-pool: origin = IP del VPS EU, health check =GET /api/v1/healthus-pool: origin = IP del VPS US, health check =GET /api/v1/health
- Creare un Load Balancer su
api.adiuvai.com:- Steering policy: Geo
- EU/Africa →
eu-pool - Americas →
us-pool - Fallback:
eu-pool
- Impostare health monitor:
GET /api/v1/health, interval 60s, timeout 5s- Se un nodo va giù, tutto il traffico va al nodo sano (automatic failover)
Opzione B: Fly.io (alternativa più semplice, meno controllo)
Se preferisci evitare la gestione manuale di un secondo VPS:
- Crea un
fly.tomlnella root del progetto API fly launch— Fly rileva il Dockerfile e deployafly regions add iad— aggiunge US East (Ashburn)- Fly gestisce: routing anycast, health checks, TLS, auto-scaling
- Il DB resta su Hetzner EU — Fly non risolve il problema del database, ma elimina tutta la gestione infrastrutturale dell'app layer
- Costo: ~$5-15/mese per region (dipende dalle risorse)
- Contro: meno controllo, vendor lock-in, il DB non ha replica locale
Opzione C: Hetzner Cloud Load Balancer + geo DNS esterno
- Hetzner offre load balancer nativi, ma sono single-region (non cross-region)
- Non adatto per geo-routing, utile solo per HA nella stessa region
Fase 3 — Terzo nodo in Asia (futuro)
Stessa procedura della Fase 2:
- VPS Hetzner Singapore (o AWS ap-southeast-1)
- Secondo PG replica con slot
replica_asia - Terzo pool in Cloudflare Load Balancing con geo steering per Asia-Pacific
- Terzo job
deploy-asianel CI
Da valutare solo quando il traffico dall'Asia lo giustifica.
Sicurezza della rete tra i nodi
| Metodo | Pro | Contro |
|---|---|---|
| WireGuard | Semplice, veloce, <1ms overhead, kernel-level | Setup manuale per nodo |
| Hetzner vSwitch | Zero config se entrambi su Hetzner | Solo stessa region |
| Tailscale | WireGuard gestito, zero config rete | Dipendenza esterna |
| SSH tunnel | Nessun software extra | Overhead maggiore, meno stabile |
Raccomandazione: WireGuard (o Tailscale per semplicita) tra tutti i nodi. Mai esporre PostgreSQL 5432 sull'IP pubblico.
Considerazioni specifiche per adiuvAI
- L'app e local-first: la maggior parte delle operazioni (tasks, notes, projects) avviene in SQLite locale nell'Electron app. Il backend serve solo auth, chat streaming, cloud storage e billing. Questo significa che la latenza del backend impatta meno di quanto sembrerebbe.
- WebSocket
/chat/stream: il geo steering porta l'utente al nodo piu vicino, ma la risposta LLM dipende dalla latenza verso OpenAI/Anthropic (non verso il tuo server). Il beneficio principale e nel tempo di handshake e nel primo token. _pending_statesin-memory per OAuth: gia documentato come non scalabile su multi-worker. Con multi-region diventa critico — servira Redis condiviso o spostare lo state su DB.- JWT_SECRET deve essere identico su tutti i nodi — un token emesso dal nodo EU deve essere validato dal nodo US.
- Alembic migrations: eseguire SOLO sul primary. Il replica riceve le DDL via streaming replication.
Stima costi
| Componente | Costo mensile |
|---|---|
| Argo Smart Routing | ~$5 + $0.10/GB |
| Cloudflare Load Balancing | ~$5/pool |
| VPS Hetzner US (CX22) | ~$5-10 |
| WireGuard | Gratis |
| Totale Fase 1 | ~$5 |
| Totale Fase 2 | ~$15-20 |