mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-07 04:12:04 +00:00
fix(db): let a PgBouncer host turn the session startup options off
The statement timeout and, since v1.42, work_mem reach Postgres as the libpq options startup parameter, which PgBouncer refuses unless it is in ignore_startup_parameters. A host behind it could drop the string by setting the timeout to 0; work_mem is now always sent, so that no longer worked. DATABASE_SESSION_OPTIONS_DISABLED sends no session settings, documented in the example env, the env manifest, the compose whitelist and the scaling guide.
This commit is contained in:
1 parent
f950dffad4
commit
7c6510eba9
6 files changed
+54
-2
No files matched your search
@@ -456,6 +456,13 @@ API_TOKEN_HMAC_KEY="<openssl rand -hex 32>"
|
||||
# sort step of each pooled connection may use this much memory, so raise it
|
||||
# with the pool size in mind.
|
||||
# DATABASE_WORK_MEM="16MB"
|
||||
#
|
||||
# Both settings above reach Postgres as the libpq `options` startup
|
||||
# parameter. PgBouncer refuses that parameter ("unsupported startup
|
||||
# parameter: options") unless it is listed in its ignore_startup_parameters.
|
||||
# Behind such a pooler, set this to 1, true or yes to send no session
|
||||
# settings at all; the pooler's or the database's own defaults then apply.
|
||||
# DATABASE_SESSION_OPTIONS_DISABLED="1"
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
@@ -74,6 +74,9 @@ x-healthlog-env: &healthlog_env
|
||||
DATABASE_STATEMENT_TIMEOUT_MS: "${DATABASE_STATEMENT_TIMEOUT_MS:-}"
|
||||
# Postgres work_mem for every app session (default 16MB).
|
||||
DATABASE_WORK_MEM: "${DATABASE_WORK_MEM:-}"
|
||||
# 1 sends neither of the two settings above (they ride the libpq `options`
|
||||
# startup parameter, which PgBouncer refuses by default).
|
||||
DATABASE_SESSION_OPTIONS_DISABLED: "${DATABASE_SESSION_OPTIONS_DISABLED:-}"
|
||||
WITHINGS_WEBHOOK_SECRET: "${WITHINGS_WEBHOOK_SECRET:-}"
|
||||
# Overrides the derived `${NEXT_PUBLIC_APP_URL}/api/withings/callback`;
|
||||
# set it when the URL registered with Withings differs from the app's own
|
||||
|
||||
@@ -188,6 +188,14 @@ already`. Either raise `max_connections` on the Postgres side, lower
|
||||
`DATABASE_POOL_MAX`, or front the database with PgBouncer in
|
||||
transaction-pooling mode.
|
||||
|
||||
The app sets its statement timeout and `work_mem` on every connection
|
||||
through the libpq `options` startup parameter. PgBouncer refuses that
|
||||
parameter (`unsupported startup parameter: options`) unless it is listed
|
||||
in `ignore_startup_parameters`. Either list it there, or set
|
||||
`DATABASE_SESSION_OPTIONS_DISABLED=1` so the app sends no session
|
||||
settings; set the timeout and `work_mem` on the database role instead
|
||||
(`ALTER ROLE healthlog SET work_mem = '16MB'`) if you still want them.
|
||||
|
||||
### Overriding the per-container pool ceiling
|
||||
|
||||
With the bundled compose, set `DB_CONNECTION_LIMIT` in `.env`; it
|
||||
|
||||
@@ -217,6 +217,10 @@
|
||||
{
|
||||
"name": "DATABASE_WORK_MEM",
|
||||
"purpose": "Postgres work_mem for every app session, as a Postgres size string. Default 16MB. Raise it when large sorts and aggregates spill to disk; each sort step of each connection may use this much."
|
||||
},
|
||||
{
|
||||
"name": "DATABASE_SESSION_OPTIONS_DISABLED",
|
||||
"purpose": "Set to 1, true or yes to send no session settings (statement timeout, work_mem) as the libpq options startup parameter. Needed behind PgBouncer, which refuses that parameter unless it is in ignore_startup_parameters."
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -146,6 +146,17 @@ describe("buildSessionOptions", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("sends no startup options at all when a connection pooler needs that", () => {
|
||||
process.env.DATABASE_SESSION_OPTIONS_DISABLED = "true";
|
||||
try {
|
||||
expect(buildSessionOptions()).toBeUndefined();
|
||||
process.env.DATABASE_SESSION_OPTIONS_DISABLED = "0";
|
||||
expect(buildSessionOptions()).toContain("-c work_mem=");
|
||||
} finally {
|
||||
delete process.env.DATABASE_SESSION_OPTIONS_DISABLED;
|
||||
}
|
||||
});
|
||||
|
||||
it("takes DATABASE_WORK_MEM and refuses a value that would inject a flag", () => {
|
||||
process.env.DATABASE_WORK_MEM = "64MB";
|
||||
expect(buildSessionOptions()).toContain("-c work_mem=64MB");
|
||||
|
||||
+21
-2
@@ -108,6 +108,22 @@ export function getWorkMem(): string {
|
||||
return raw && WORK_MEM_SHAPE.test(raw) ? raw : DEFAULT_WORK_MEM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the app may send the libpq `options` startup parameter at all.
|
||||
*
|
||||
* PgBouncer refuses a connection that carries a startup parameter it does not
|
||||
* know (`unsupported startup parameter: options`) unless the operator lists
|
||||
* it in `ignore_startup_parameters`. Up to v1.41 such a host could set the
|
||||
* statement timeout to 0, which dropped the whole string; v1.42 always sends
|
||||
* `work_mem`, so that no longer worked. `DATABASE_SESSION_OPTIONS_DISABLED`
|
||||
* (1, true or yes) sends none of the session settings; the pooler or the
|
||||
* database's own defaults then apply.
|
||||
*/
|
||||
export function sessionOptionsDisabled(): boolean {
|
||||
const raw = process.env.DATABASE_SESSION_OPTIONS_DISABLED?.trim();
|
||||
return raw !== undefined && /^(1|true|yes)$/i.test(raw);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the libpq `options` startup string applying the session settings.
|
||||
* Passed straight through `PrismaPg` to the underlying `pg.Pool`, which
|
||||
@@ -116,9 +132,12 @@ export function getWorkMem(): string {
|
||||
*
|
||||
* The timeouts are left out when disabled (timeout 0); `work_mem` is always
|
||||
* set. Up to v1.41 the whole string was `undefined` at timeout 0, which would
|
||||
* have taken `work_mem` with it.
|
||||
* have taken `work_mem` with it. `undefined` now means exactly one thing:
|
||||
* the operator turned the startup options off for a connection pooler
|
||||
* ({@link sessionOptionsDisabled}).
|
||||
*/
|
||||
export function buildSessionOptions(): string {
|
||||
export function buildSessionOptions(): string | undefined {
|
||||
if (sessionOptionsDisabled()) return undefined;
|
||||
const timeoutMs = getStatementTimeoutMs();
|
||||
const timeouts =
|
||||
timeoutMs > 0
|
||||
|
||||
Reference in new issue
Block a user