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 5 commits into
Conversation
|
@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 ( |
|
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! |
|
A note on the Codecov report: the two remaining uncovered lines in
Everything else in the patch, including |
102fb3e to
a8bd0dd
Compare
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.
… 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.
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().
configure() took over both call sites in databases.rs, leaving resize() without callers outside its own test. The test now sets the same count limit through configure().
a8bd0dd to
3ee58d8
Compare
Renders the two [general] options added in pgdogdev/pgdog#1266 into pgdog.toml, so operators can set them via values instead of the PGDOG_QUERY_CACHE_MEMORY_LIMIT / PGDOG_QUERY_CACHE_IDLE_TIMEOUT env variables. Both are optional and omitted from the config unless set.
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.