Skip to content

Allow mid-session SET of duckgres.worker_ttl - #1054

Open
fuziontech wants to merge 1 commit into
mainfrom
worker-ttl-session-guc
Open

Allow mid-session SET of duckgres.worker_ttl#1054
fuziontech wants to merge 1 commit into
mainfrom
worker-ttl-session-guc

Conversation

@fuziontech

Copy link
Copy Markdown
Member

Why

On the remote/K8s backend, a worker whose last session ends is parked hot_idle (warm, quickly reusable) and retired once its TTL expires — 1 minute by default. The only way to extend that was the -c duckgres.worker_ttl=... connect-time startup option, which many clients (BI tools, poolers) can't set. Their workers are reaped at the default TTL whenever a connection goes idle, so every return visit pays a cold worker spawn.

What

SET duckgres.worker_ttl = '20m' / SHOW / RESET now work mid-session, on all three protocol paths (simple, split-batch, extended):

  • Transpiler intercepts the GUC like duckgres.s3_cache — validated with 22023 on a bad duration (without echoing client input), never forwarded to DuckDB, never intercepted inside a multi-statement batch.
  • Connection layer applies it through a control-plane hook that updates the bound worker's pool-side hot-idle TTL; the next hot → hot_idle park persists the overridden ttl_minutes, which is exactly what both reapers (leader janitor, per-CP fallback) read. Session state flips only after a successful apply, so SHOW never reports a TTL the worker won't park with; a clamped value is stored clamped.
  • Same trust boundary as the startup option: gated on DUCKGRES_K8S_ALLOW_CLIENT_WORKER_PROFILE (SET rejected with 22023 when off) and clamped to DUCKGRES_K8S_WORKER_MAX_TTL.
  • Exploratory tier: SET binds a worker first on lazily-activated connections (the TTL is worker-side pool state), and the override is re-applied across tier worker switches; a failed re-apply fails the statement and resets session state, never the connection.
  • Standalone/process backends accept SET/SHOW as session state only (no per-worker hot-idle TTL exists there).

SHOW duckgres.worker_ttl reports the session override, else the bound worker's current park TTL, else the connect-time baseline (startup GUC > org default > deployment default > built-in 1m).

Notes / limitations

  • TTL is stamped with whole-minute precision (pre-existing: ttl_minutes truncates), so sub-minute overrides round down — documented in the README.
  • A CP restart between SET and disconnect loses the in-memory override (the park then uses the last persisted TTL); acceptable because the session dies with the CP connection anyway.

Test plan

  • transpiler/worker_ttl_test.go: SET/SHOW/RESET interception, normalization, 22023 rejections (no echo), multi-statement guard.
  • server/worker_ttl_test.go: simple/batch/extended apply paths, lazy activation on SET, failure-keeps-state, gate 22023 passthrough, clamp reporting, escalation re-apply + failure semantics, state-only without the capability.
  • controlplane/worker_ttl_test.go + k8s_pool_worker_ttl_test.go: pid→worker routing, gate/clamp hook, baseline resolution, pool SetWorkerTTL/WorkerTTL, and override persisted as ttl_minutes at park.
  • just lint clean; full unit suites pass in both build flavors (-tags kubernetes and without). controlplane/admin Postgres-container tests fail in this environment (missing docker-compose) — pre-existing on main, unrelated.

duckgres.worker_ttl (how long a worker stays hot-idle/warm after its
last session ends) was connect-time only: clients that cannot set libpq
startup options had no way to extend warm retention, so their worker was
reaped at the default TTL whenever the connection went idle.

Add SET/SHOW/RESET support on all three protocol paths (simple, batch,
extended), mirroring duckgres.s3_cache:

- the transpiler intercepts and validates the GUC (22023 on a bad
  duration, never forwarded to DuckDB, never intercepted inside a
  multi-statement batch)
- the connection layer applies it through a control-plane hook that
  updates the bound worker's pool-side hot-idle TTL, gated on
  DUCKGRES_K8S_ALLOW_CLIENT_WORKER_PROFILE and clamped to
  DUCKGRES_K8S_WORKER_MAX_TTL exactly like the startup option
- session state only flips after the apply succeeds, so SHOW never
  reports a TTL the worker won't park with; SET acquires a worker on
  lazily-activated connections and the override is re-applied across
  exploratory-tier worker switches
- standalone/process backends get session-state-only SET/SHOW (they
  have no per-worker hot-idle TTL)
@github-actions

Copy link
Copy Markdown

Test Impact Plan

Deterministic summary of how this PR changes tests, CI runners, and coverage-risk signals.

Summary

Area Added Changed Deleted
Test files 4 0 0
E2E/journey files 0 0 0
Workflow files 0 0 0

Signals

  • Test cases: +25 / -0
  • Assertions: +112 / -0
  • Skips or known failures added: 0
  • Workflow continue-on-error added: 0
  • Workflow path filters added: 0
  • Test commands removed from justfile: 0
  • E2E/journey retry lines added: 0

Coverage risk: neutral or increased

No coverage-reduction warnings detected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant