Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions control-plane/src/provision.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,23 @@ import { defaultOrgId } from "./orgs";

const SLUG = /^[a-z][a-z0-9_]{1,40}$/;

/**
* Per-tenant resource governance (ADR 0007, Phase 0) — bound one tenant's blast
* radius on the shared cluster. Applied to the authenticator role on every
* provision, so re-provisioning also backfills existing projects.
* - connection limit: a ceiling far above real peak (~7) yet well under the
* cluster's max_connections (100), so one runaway tenant can't drain the pool.
* - statement_timeout: kill a runaway query instead of pinning a backend.
* - idle_in_transaction_session_timeout: reap a leaked open transaction that
* would otherwise hold a connection (and its locks) open indefinitely.
* GoTrue connects as `postgres` (not the authenticator), so these bound the data
* plane without touching auth; migrations run as superuser, so statement_timeout
* never blocks a schema apply.
*/
const TENANT_CONNECTION_LIMIT = 20;
const TENANT_STATEMENT_TIMEOUT = "60s";
const TENANT_IDLE_TX_TIMEOUT = "60s";

export type Project = {
name: string;
database: string;
Expand Down Expand Up @@ -103,6 +120,18 @@ export async function provisionDatabase(name: string): Promise<Project> {
await admin.query(`alter role ${ident(role)} password ${lit(password)}`);
}

// Per-tenant resource governance (ADR 0007, Phase 0). Idempotent; runs on
// every provision so existing roles get backfilled too.
await admin.query(
`alter role ${ident(role)} connection limit ${TENANT_CONNECTION_LIMIT}`,
);
await admin.query(
`alter role ${ident(role)} set statement_timeout = ${lit(TENANT_STATEMENT_TIMEOUT)}`,
);
await admin.query(
`alter role ${ident(role)} set idle_in_transaction_session_timeout = ${lit(TENANT_IDLE_TX_TIMEOUT)}`,
);

const d = await admin.query(
"select 1 from pg_database where datname = $1",
[database],
Expand Down
Loading