Skip to content

Commit 590bfcb

Browse files
perf(db): drive Postgres pool size + application_name from per-role profiles
Replace ad-hoc DB_APP_NAME sizing with a per-role profile map keyed by SIM_DB_ROLE (web/trigger/realtime), defaulting to web. Trigger machines open a small pool instead of 15 to avoid PgBouncer connection exhaustion. Also size realtime's separate socketDb pool down to 10.
1 parent 35acc42 commit 590bfcb

4 files changed

Lines changed: 32 additions & 6 deletions

File tree

apps/realtime/src/database/operations.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,14 @@ import { env } from '@/env'
3232
const logger = createLogger('SocketDatabase')
3333

3434
const connectionString = env.DATABASE_URL
35+
// Realtime process footprint = this socketDb pool + the shared @sim/db pool.
3536
const socketDb = drizzle(
3637
instrumentPoolClient(
3738
postgres(connectionString, {
3839
prepare: false,
3940
idle_timeout: 10,
4041
connect_timeout: 20,
41-
max: 15,
42+
max: 10,
4243
onnotice: () => {},
4344
connection: { application_name: process.env.DB_APP_NAME ?? 'sim-realtime' },
4445
}),

apps/realtime/src/env.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const EnvSchema = z.object({
1313
NEXT_PUBLIC_APP_URL: z.string().url(),
1414
ALLOWED_ORIGINS: z.string().optional(),
1515
PORT: z.coerce.number().int().positive().default(3002),
16+
SIM_DB_ROLE: z.enum(['web', 'trigger', 'realtime']).optional(),
1617
DISABLE_AUTH: z
1718
.string()
1819
.optional()

apps/sim/lib/core/config/env.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export const env = createEnv({
2121
DATABASE_URL: z.string().url(), // Primary database connection string
2222
DATABASE_REPLICA_URL: z.string().url().optional(), // Read-replica connection string; opt-in reads fall back to the primary when unset
2323
DB_APP_NAME: z.string().optional(), // Postgres application_name for query attribution (sim-app/sim-trigger/sim-realtime)
24+
SIM_DB_ROLE: z.enum(['web', 'trigger', 'realtime']).optional(), // Per-process pool profile selector (read directly by @sim/db)
2425
BETTER_AUTH_URL: z.string().url(), // Base URL for Better Auth service
2526
BETTER_AUTH_SECRET: z.string().min(32), // Secret key for Better Auth JWT signing
2627
DISABLE_REGISTRATION: z.boolean().optional(), // Flag to disable new user registration

packages/db/db.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,33 @@ if (!connectionString) {
88
throw new Error('Missing DATABASE_URL environment variable')
99
}
1010

11+
/**
12+
* Per-role pool profiles. Starting numbers — validate against real per-role
13+
* process counts (PgBouncer transaction mode, max_connections=200).
14+
*/
15+
export const DB_POOL_PROFILES = {
16+
web: { primaryMax: 10, replicaMax: 4, appName: 'sim-app' },
17+
// 5, not 3 — one run can need 3+ simultaneous connections (parallel queries +
18+
// overlapping logging writes); 3 risks intra-run deadlock.
19+
trigger: { primaryMax: 5, replicaMax: 2, appName: 'sim-trigger' },
20+
realtime: { primaryMax: 5, replicaMax: 3, appName: 'sim-realtime' },
21+
} as const
22+
23+
type DbRole = keyof typeof DB_POOL_PROFILES
24+
25+
const role = process.env.SIM_DB_ROLE as DbRole | undefined
26+
const profile = (role && DB_POOL_PROFILES[role]) || DB_POOL_PROFILES.web
27+
1128
const poolOptions = {
1229
prepare: false,
1330
idle_timeout: 20,
1431
connect_timeout: 30,
1532
onnotice: () => {},
16-
connection: { application_name: process.env.DB_APP_NAME ?? 'sim-app' },
33+
connection: { application_name: process.env.DB_APP_NAME ?? profile.appName },
1734
}
1835

1936
const postgresClient = instrumentPoolClient(
20-
postgres(connectionString, { ...poolOptions, max: 15 }),
37+
postgres(connectionString, { ...poolOptions, max: profile.primaryMax }),
2138
'db'
2239
)
2340

@@ -37,7 +54,13 @@ if (replicaUrl && !/^postgres(ql)?:\/\//.test(replicaUrl)) {
3754
}
3855

3956
export const dbReplica: typeof db = replicaUrl
40-
? drizzle(instrumentPoolClient(postgres(replicaUrl, { ...poolOptions, max: 10 }), 'dbReplica'), {
41-
schema,
42-
})
57+
? drizzle(
58+
instrumentPoolClient(
59+
postgres(replicaUrl, { ...poolOptions, max: profile.replicaMax }),
60+
'dbReplica'
61+
),
62+
{
63+
schema,
64+
}
65+
)
4366
: db

0 commit comments

Comments
 (0)