feat(config): add query_cache_memory_limit and query_cache_idle_timeout - #1266
feat(config): add query_cache_memory_limit and query_cache_idle_timeout#1266IgorOhrimenko wants to merge 3 commits into
Conversation
The AST query cache was bounded only by entry count (query_cache_limit), not by memory, and had no idle expiry. Heavy/complex queries produce large parse trees, so a full cache can hold GBs of live RSS that only drop once 1000 newer distinct queries evict them (or on RESET/restart). Adds two [general] options (also PGDOG_QUERY_CACHE_MEMORY_LIMIT / PGDOG_QUERY_CACHE_TTL env), both default 0 (off): - query_cache_memory_limit: evict LRU until the summed entry size is under the byte budget. Entry size is measured via jemalloc per-thread allocation counters (clamped to 0, since cross-thread frees under a work-stealing runtime can make the delta negative); falls back to query length on non-jemalloc builds. - query_cache_ttl: drop entries not accessed within the window, via the existing 1s maintenance sweep. Refs pgdogdev#1261.
|
@levkk — a direction check on this one. The failure mode this PR targets: the query cache is bounded by entry count but not by bytes, and entries never expire. On a workload with large or diverse queries, RSS grows monotonically until the container hits its memory limit — at which point the kernel OOM killer SIGKILLs the process. No drain, no graceful shutdown, exit 137: every client connection is dropped mid-transaction, and it tends to happen at peak traffic, because that's when the cache churns hardest. For a connection pooler — the component that exists to make everything behind it look stable — that's the worst possible way to die. Both knobs are opt-in ( |
… use milliseconds The behaviour is time-to-idle (expiry counted from last access), not time-to-live, so the ttl name over-promised. The config already has an idle-timeout vocabulary (idle_timeout, client_idle_timeout) — reuse it, and switch the unit from seconds to milliseconds to match the other *_timeout options. Env variable becomes PGDOG_QUERY_CACHE_IDLE_TIMEOUT. Schema regenerated.
|
Renamed What the option implements is time-to-idle, not time-to-live: the clock counts from the last access, so a hot entry never expires — only entries nobody has touched for the whole window are swept. A true TTL counts from insertion and would evict hot entries too, which is the wrong tool here (a parsed AST can't go stale, so there's nothing to refresh). Calling a time-to-idle knob The precise term for this is TTI ( |
…lliseconds) Follows the rename in pgdogdev/pgdog#1266: the behaviour is idle expiry (counted from last access), so the option reuses the existing idle-timeout naming and the millisecond unit of the other *_timeout options.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
The existing tests exercise the limit logic on a standalone Inner, so the public entry points that apply configuration to the global cache were never called: configure() with a non-zero idle timeout and resize() (flagged by codecov on the patch). Cover both, including the zero-capacity floor of resize().
|
A note on the Codecov report: the two remaining uncovered lines in
Everything else in the patch, including |
Implements the memory-based cache limit and idle expiry proposed in #1261.
Adds two
[general]options (alsoPGDOG_QUERY_CACHE_MEMORY_LIMIT/PGDOG_QUERY_CACHE_IDLE_TIMEOUTenv), both default0(off):query_cache_memory_limit(bytes): evict LRU until the summed entry size is under the budget.query_cache_limitbounds the number of entries but not their memory — a parsed tree for a wide/complex query is orders of magnitude larger than forSELECT 1, so a count-based cap alone can't bound RAM. Entry size is measured via jemalloc's per-thread allocation counters (clamped to 0, since cross-thread frees under a work-stealing runtime can make the delta negative); falls back to the query text length on non-jemalloc builds.query_cache_idle_timeout(milliseconds): entries not accessed within the window are dropped by the existing 1s maintenance sweep, releasing an idle working set without waiting for LRU eviction.Both are opt-in (
0= off); default behaviour is unchanged. Unit tests cover count/byte eviction, byte accounting and the idle-expiry sweep; config parsing is covered inpgdog-config. JSON schema regenerated.Closes #1261.