CONCEPT Cited by 1 source
Prepared statement memory accumulation¶
Definition¶
Prepared statement memory accumulation is the per-session
growth of cached query plans and prepared statements that is
not released until the session ends or the statement is
explicitly DEALLOCATEd. It is RSS growth driver #5 of 5
that Simeon Griggs names in the 2026-03-30 memory post:
"Cached plans and prepared statements accumulate per- session memory that is not released until the session ends or the statement is explicitly deallocated."
(Source: sources/2026-04-21-planetscale-high-memory-usage-in-postgres-is-good-actually.)
Mechanism¶
When a client sends PREPARE stmt_name AS SELECT ..., Postgres:
- Parses and plans the query.
- Stores the parsed query tree + plan in the session's prepared-statement cache.
- Returns a handle for subsequent
EXECUTE stmt_name(params)calls.
Cached plans live in the backend's memory for the session's lifetime. They are:
- Non-reclaimable by the kernel (they're in process heap).
- Not shared across backends (each backend has its own cache).
- Additive over session lifetime — without explicit
DEALLOCATE, they only release on session end.
Why this compounds with long-lived connections¶
The interaction chain:
long-lived session + many unique query shapes
→ prepared-statement cache grows per session
→ per-backend RSS climbs
→ × active_backend_count
→ cluster RSS rises
Applications that use session-mode pooling (where connections persist for the app's lifetime) accumulate prepared statements indefinitely. Applications that use PgBouncer in transaction mode don't observe this directly because transaction-mode pooling doesn't honour session state — but they can't use prepared statements at all in the traditional sense, a trade-off named in the sibling PgBouncer post's unsupported-features list.
ORM drivers that amplify¶
Some ORMs auto-create prepared statements for every parameterised query:
- Rails ActiveRecord historically prepared every query;
recent versions allow opting out via
prepared_statements: falseindatabase.yml. - Python psycopg2 prepares explicitly; psycopg3 has pipeline mode with more aggressive caching.
- Node.js pg does not prepare by default;
pg-nativecan.
If the ORM auto-prepares and the app churns through thousands of distinct query shapes (dynamic WHERE clauses with many column combinations), the prepared-statement cache grows per backend until memory exhaustion.
Mitigations¶
- Explicit
DEALLOCATEof prepared statements the app will not reuse. plan_cache_mode = force_custom_planto skip caching entirely (loses plan-reuse benefit).- ORM configuration to disable auto-prepare, or to cap the statement cache at a bounded size.
- Transaction-mode pooling via PgBouncer — structurally avoids the problem by rotating server-side connections per transaction.
- Periodic backend recycling via
idle_session_timeoutor connection-lifetime limits.
Seen in¶
- sources/2026-04-21-planetscale-high-memory-usage-in-postgres-is-good-actually — Simeon Griggs names prepared-statement accumulation as RSS growth driver #5 of 5 with the explicit "not released until the session ends or the statement is explicitly deallocated" semantics.