# 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
- Lato codice: aggiungere header `Cache-Control: public, s-maxage=30` e `CDN-Cache-Control: public, max-age=30` all'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/stream` WebSocket 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

1. Crea un VPS Hetzner in **Ashburn (us-east)** (stesse specs del nodo EU)
2. Setup identico: Docker, Docker Compose, git
3. 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):**

1. Creare un utente replication:
   ```sql
   CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD '<strong_password>';
   ```
2. Creare un replication slot:
   ```sql
   SELECT pg_create_physical_replication_slot('replica_us_east');
   ```
3. Configurare `pg_hba.conf` per permettere connessioni dal VPS US:
   ```
   host replication replicator <US_VPS_WIREGUARD_IP>/32 scram-sha-256
   ```
4. Esporre la porta PG solo sull'IP WireGuard nel `docker-compose.yml`:
   ```yaml
   services:
     db:
       ports:
         - "10.0.0.1:5432:5432"  # solo interfaccia WireGuard
   ```

**Sul REPLICA (US):**

1. Base backup dal primary:
   ```bash
   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 `-R` crea automaticamente `standby.signal` e scrive `primary_conninfo` in `postgresql.auto.conf`.

2. Avviare PG in modalita replica (legge `standby.signal` e si connette al primary)

3. Verificare:
   - Sul primary: `SELECT * FROM pg_stat_replication;` (deve mostrare il replica)
   - Sul replica: `SELECT pg_is_in_recovery();` (deve restituire `t`)

#### 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_engine` che usa `DATABASE_READ_URL` (fallback a `DATABASE_URL` se vuoto)
- Esporre un `get_read_session()` dependency da usare nelle query read-only

Modifiche in `app/main.py`:
- L'health endpoint deve restituire `region` nel payload
- Aggiungere header `Cache-Control` / `CDN-Cache-Control` per il caching all'edge

Nelle route, per le query di sola lettura pesanti (es. ricerca, listing):
- Usare `db: AsyncSession = Depends(get_read_session)` invece di `get_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_URL` verso il DB locale e `DATABASE_URL` verso il primary EU
- Imposta `REGION=us-east`
- Avvia PG in modalita replica (con `primary_conninfo` che punta al primary EU via WireGuard)

Il `.env` sul nodo US:
```env
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 `deploy` ma 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

1. **Dashboard → Traffic → Load Balancing** (piano Pro, ~$5/mese per pool)
2. Creare due **Origin Pools**:
   - `eu-pool`: origin = IP del VPS EU, health check = `GET /api/v1/health`
   - `us-pool`: origin = IP del VPS US, health check = `GET /api/v1/health`
3. Creare un **Load Balancer** su `api.adiuvai.com`:
   - Steering policy: **Geo**
   - EU/Africa → `eu-pool`
   - Americas → `us-pool`
   - Fallback: `eu-pool`
4. 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:

1. Crea un `fly.toml` nella root del progetto API
2. `fly launch` — Fly rileva il Dockerfile e deploya
3. `fly regions add iad` — aggiunge US East (Ashburn)
4. Fly gestisce: routing anycast, health checks, TLS, auto-scaling
5. Il DB resta su Hetzner EU — Fly non risolve il problema del database, ma elimina tutta la gestione infrastrutturale dell'app layer
6. Costo: ~$5-15/mese per region (dipende dalle risorse)
7. 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:
1. VPS Hetzner Singapore (o AWS ap-southeast-1)
2. Secondo PG replica con slot `replica_asia`
3. Terzo pool in Cloudflare Load Balancing con geo steering per Asia-Pacific
4. Terzo job `deploy-asia` nel 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_states` in-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** |
