| # Database Connection Pooling |
|
|
| Technical documentation for the database connection setup across Supabase and Railway. |
|
|
| ## Overview |
|
|
| The application connects to Supabase Postgres through **Supavisor** (Supabase's shared connection pooler) in **transaction mode**. Each Railway region's API instance reads from the closest Supabase read replica and writes to the primary database in EU. |
|
|
| ## Connection Modes |
|
|
| Supabase offers two pooling modes via `pooler.supabase.com`: |
|
|
| | Mode | Port | Behavior | |
| |------|------|----------| |
| | **Session mode** | `5432` | 1:1 client-to-backend mapping. No real pooling β each app connection holds a dedicated Postgres connection for its entire lifetime. | |
| | **Transaction mode** | `6543` | Real connection pooling. Backend connections are shared between clients and only held during a transaction, then returned to the pool. | |
|
|
| **We use transaction mode (port 6543).** This is critical β session mode on port 5432 provides zero pooling benefit despite routing through `pooler.supabase.com`. |
|
|
| ### Connection String Format |
|
|
| ``` |
| postgresql://postgres.<project-ref>:<password>@aws-0-<region>.pooler.supabase.com:6543/postgres |
| ``` |
|
|
| ## Dedicated Pooler (PgBouncer) |
|
|
| Supabase also offers a dedicated PgBouncer co-located on the database machine at `db.<ref>.supabase.co:6543`. This has lower latency (no network hop to a separate server) but requires IPv6 connectivity or the Supabase IPv4 add-on ($4/mo per database). |
|
|
| Railway does **not** support IPv6 to Supabase's direct endpoints, so the shared Supavisor pooler is the correct choice for our infrastructure. |
|
|
| ## Multi-Region Replica Mapping |
|
|
| The API runs in 3 Railway regions. Each instance reads from the closest Supabase read replica via the `RAILWAY_REPLICA_REGION` environment variable: |
|
|
| | Railway Region | Env Var | Supabase Region | Role | |
| |----------------|---------|-----------------|------| |
| | `europe-west4-drams3a` | `DATABASE_FRA_URL` | `eu-central-1` | Primary (reads + writes) | |
| | `us-east4-eqdc4a` | `DATABASE_IAD_URL` | `us-east-1` | Read replica | |
| | `us-west2` | `DATABASE_SJC_URL` | `us-west-1` | Read replica | |
|
|
| - **Reads** are routed to the regional replica via `executeOnReplica` and the `withReplicas` wrapper in `packages/db/src/replicas.ts`. |
| - **Writes** always go to `DATABASE_PRIMARY_URL` (the primary in `eu-central-1`). |
|
|
| ## Pool Configuration |
|
|
| Defined in `packages/db/src/client.ts`: |
|
|
| | Setting | Development | Production | |
| |---------|-------------|------------| |
| | `max` | 8 | 12 | |
| | `idleTimeoutMillis` | 5,000ms | 60,000ms | |
| | `connectionTimeoutMillis` | 5,000ms | 5,000ms | |
| | `maxUses` | 100 | 0 (unlimited) | |
| | `ssl` | disabled | enabled (rejectUnauthorized: false) | |
|
|
| Each API instance creates up to 2 pools (primary + 1 regional replica), so the maximum backend connections per instance is `12 Γ 2 = 24`. With Supavisor transaction mode, these are multiplexed into a much smaller number of actual Postgres backend connections. |
|
|
| ## Prepared Statements |
|
|
| Transaction mode does **not** support named prepared statements. The `node-postgres` (`pg`) package with Drizzle ORM uses unnamed parameterized queries (extended query protocol without a `name` field), which are compatible with transaction mode. No special configuration is needed. |
|
|
| **Do not** add named queries like `pool.query({ name: 'my-query', text: '...' })` β these will fail through the pooler. |
|
|
| ## Environment Variables |
|
|
| ### API Service |
| - `DATABASE_PRIMARY_URL` β Primary database (EU, writes + reads) |
| - `DATABASE_FRA_URL` β EU read replica (same as primary) |
| - `DATABASE_IAD_URL` β US East read replica |
| - `DATABASE_SJC_URL` β US West read replica |
|
|
| ### Worker Service |
| - `DATABASE_PRIMARY_POOLER_URL` β Primary database (EU) |
|
|
| ### Dashboard Service |
| - No database variables β connects to the API via tRPC, not directly to Postgres. |
|
|
| ## Railway Deploy Configuration |
|
|
| - **API**: 3 regions Γ 1 replica each (production), 3 regions Γ 1 replica each (staging) |
| - **Dashboard**: 3 regions Γ 2 replicas each (production), 3 regions Γ 1 replica each (staging, with serverless/sleep enabled) |
| - **Worker**: 1 region (EU) Γ 3 replicas (production) |
|
|