Skip to content

get_pg_server_config, get_pg_logging_audit and the PostgreSQL config panel plan a day's chunks, not every retained pg_server_config chunk (#3974) - #3992

Merged
erikdarlingdata merged 1 commit into
devfrom
fix/3974-pg-server-config-chunk-plan
Sep 23, 2026
Merged

erikdarlingdata merged 1 commit into
devfrom
fix/3974-pg-server-config-chunk-plan

Conversation

@erikdarlingdata

Copy link
Copy Markdown
Owner

Closes #3974.

Why

DarlingPgServerConfigReader.CurrentConfigSql and OverrideSql (get_pg_server_config, and the WPF
viewer's PostgreSQL config panel) and DarlingPgLoggingAuditReader.NewestSnapshotSql
(get_pg_logging_audit) had no lower bound on the newest-snapshot anchor or the row scan. pg_server_config
keeps 365 days of hourly snapshots in one-day chunks, so TimescaleDB had to plan every retained chunk before
reading a row on every call.

What changes

  • CurrentConfigSql and NewestSnapshotSql get the PostgreSQL-target config reads plan every retained pg_server_config chunk: seconds per read once a store holds a year of snapshots #3928 shape exactly: a day's lower bound on both the
    anchor's MAX(collection_time) and the outer row scan, with a fallback to unbounded only when the day
    found nothing (DarlingPgServerConfigReader.ConfigSnapshotLowerBounds, shared by both readers).
  • OverrideSql needed a different shape - the design question the issue raised. Most servers have zero
    per-database/per-role overrides, so retrying on "the read returned no rows" would run the unbounded
    fallback on nearly every call, defeating the fix. Instead the anchor is a one-row CTE LEFT JOINed to the
    override rows, so its collection_time rides on every call even with zero overrides; the retry loop keys
    on that column, never on row count.
  • Results are unchanged: a target whose config collector has gone dark for more than a day still gets its
    newest snapshot however old, through the same fallback rung these reads always had.

Test plan

  • PgServerConfigToolBoundTests (new): pins the bound on all three statements, the day-first/fallback
    loop at each call site (keyed on row count for two, on the anchor column for OverrideSql), and the
    bounds themselves.
  • PgServerConfigTests.TheCurrentReadAnchorsOnTheNewestSnapshot_NeverComingUpEmptyForADarkServer
    updated: the old pin asserted "no collection_time >= anywhere"; the property that matters (a dark
    target never comes up empty) still holds, now through a bound-plus-guaranteed-fallback rather than no
    bound at all.
  • PgServerConfigOverrideLivePostgresTests (existing, gated): its hand-built CurrentConfigSql call
    updated for the new $4 parameter; still green.
  • PgServerConfigToolBoundLiveTests (new, gated DARLING_TEST_PG): seeds 11 daily chunks and EXPLAINs
    all three reads with each bound - the day plans 1-4 chunk scans, the fallback plans all 11; a dark
    target (72h) still answers with its newest snapshot on all three reads; a snapshot with zero overrides
    does not fall back and leak an older snapshot's overrides (the case the design question was about).
  • Measured on the rig (366 one-day chunks, PostgreSQL 18.6 / TimescaleDB 2.30.1): unbounded planning
    21-288 ms (812-12,592 buffers) against ~2.9-3.0 ms bounded (812 buffers) with a literal-equivalent
    bound - the same order of magnitude the issue measured (4.0-8.3s at full 365-day retention; this rig's
    warm/cold variance was noisier than the issue's but the bounded shape was consistently ~7x+ cheaper at
    every measurement).
  • Targeted: Darling.Tests PgServerConfig/PgLoggingAudit classes, 64 total, 0 failed (7 skipped - ungated
    live tests).
  • Full suite: Darling.Tests, 12,934 total, 0 failed (1 pre-existing flake in
    TimescaleSupportTests.EndToEnd_RetentionPoliciesActuallyApply_AndAreCreatedPaused_AgainstDevPostgres,
    confirmed passing alone, unrelated to this change - not touched by this PR).

Lite and the Dashboard do not monitor PostgreSQL, so there is no parity item.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GdmA4ND1wLSqA91ax1m4xv

…panel plan a day's chunks, not every retained pg_server_config chunk (#3974)

The three tool-side reads of the newest pg_server_config snapshot (CurrentConfigSql, OverrideSql,
NewestSnapshotSql) had no lower bound on the MAX(collection_time) anchor or the row scan, so
TimescaleDB planned every retained chunk before reading a row: 4.0-8.3s cold on a 366-chunk rig.

Bounds CurrentConfigSql and NewestSnapshotSql the way #3928 bounded the analysis reads: a day's lower
bound on both the anchor and the outer scan, falling back to unbounded only when the day found nothing.
OverrideSql needed a different shape (the design question the issue raised): most servers have zero
overrides, so a naive "retry when no rows" loop would fall back on nearly every call. Its anchor now
rides its own column through a LEFT JOIN, so the retry loop can tell "no snapshot in the day" from "a
snapshot with zero overrides" and only retries the former.

Measured on the rig: the bounded read plans in ~3ms against 21-288ms unbounded on 366 one-day chunks.
Answers are unchanged: a dark target's fallback still finds its newest snapshot however old.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdmA4ND1wLSqA91ax1m4xv
@erikdarlingdata
erikdarlingdata enabled auto-merge (squash) September 23, 2026 05:32
@erikdarlingdata
erikdarlingdata merged commit 5726547 into dev Sep 23, 2026
15 of 16 checks passed
@erikdarlingdata
erikdarlingdata deleted the fix/3974-pg-server-config-chunk-plan branch September 23, 2026 05:32
erikdarlingdata added a commit that referenced this pull request Sep 23, 2026
…ntries in their sections (#4080)

Adds 42 entries and 42 link refs (#3992, #3995, #3996, #3998, #4001, #4002, #4003, #4007, #4010, #4011, #4013, #4015, #4020, #4022, #4025, #4029, #4030, #4031, #4036, #4038, #4039, #4040, #4044, #4047, #4048, #4049, #4050, #4051, #4055, #4061, #4063, #4064, #4065, #4066, #4067, #4068, #4069, #4070, #4071, #4073, #4074, #4078). Each PR's entry was buffered, and this lands every entry whose PR was merged on origin/dev when it ran.

#3989 left 26 entries under bare 'Changed' and 'Fixed' lines above '### Added'. They move into '### Changed' and '### Fixed', below the new entries, and one blank line stays under [Unreleased].


Claude-Session: https://claude.ai/code/session_01Ua31ugERL5DmhFVRtf6keQ

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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