Skip to content

The Query Store liveness touch costs ~13% of the Query Store–ingesting store's WAL (~33 GB/day) at its 6-hour guard: every touch is a non-HOT update #4250

Description

@erikdarlingdata

Related: #3189, #3210, #3216 (the touch guard). The guard's doc comment names write volume as its trade and records no measurement of it at the 6-hour width.

Problem

QueryStoreLivenessTouchGuard sets how often the touch-and-probe statements refresh last_seen on query_store_plan_map and query_store_text: GuardHours = StampSkewMarginHours / MarginShareDivisor = 24 / 4 = 6 h.

The comment on MarginShareDivisor states the divisor is "CHOSEN, not measured". It also says what the divisor trades: "WRITE VOLUME against stamp freshness". last_seen is indexed on both tables, so every touch is a non-HOT update: a new heap tuple, new b-tree entries, a dead tuple, and WAL for all of it. With data checksums and 5-minute checkpoints, most of those pages are imaged in full. Here is the write-volume side, measured.

Measured

Production SQL Server store A (43 servers, the Query Store–ingesting store), build 3.8.0-nightly.20260924.467. Steady window 2026-09-25 04:45–05:41Z (55.6 min), no restart inside.

  • pg_stat_statements:
    • plan-map touch-and-probe: 310 calls, 399,287 WAL records, 128,191 page images, 0.74 GB;
    • text touch-and-probe: 308 calls, 280,439 records, 75,733 page images, 0.53 GB;
    • together 1.28 GB = 13.5% of the store's WAL (9.45 GB), and 1.71 GB of buffers dirtied.
  • pg_stat_all_tables, same window: query_store_plan_map 82,880 updates (0 HOT), query_store_text 79,836 (31 HOT). That's ~163 k touches an hour, ~3.9 M a day, which matches the 6-hour guard over the referenced set.
  • pg_waldump per-relation sample (1.1 GB at 05:41Z): the query_store_text heap 5.6%, the query_store_plan_map heap 4.1%, their pkeys 1.1%. Most of it is page images and hint images.
  • Per day: ~33 GB of WAL and ~44 GB of dirtied pages written back, on a store generating ~250–350 GB of WAL a day.
  • The window straddling the 04:04Z restart had 3.73 GB, 23% of WAL: the first cycle after a start probes and touches the whole referenced set at once.
  • Store B holds Query Store READ_ONLY replicas and stores nothing here.

Where

  • Darling/PerformanceMonitor.Darling.Storage/QueryStoreLivenessTouchGuard.cs: MarginShareDivisor :133, GuardHours :141, StampSkewMarginDays :102.
  • Darling/PerformanceMonitor.Darling.Storage/QueryStorePlanMap.cs:170, TouchAndProbeSql; last_seen index :68.
  • Darling/PerformanceMonitor.Darling.Storage/QueryStoreTextStore.cs:114, TouchAndProbeSql; last_seen index :65.

Fix shape

Pick by what the margin can afford. All three cut writes roughly in proportion.

  • Widen the guard.
    • Raise QueryStorePlanMap.PruneMarginDays from 1 to 2, matching the text store. Then MarginShareDivisor 4 gives a 12 h guard and halves the touches, with the same ¼-of-margin headroom the comment asks for.
    • Or keep the margin and move the divisor to 2.
  • Make the touch HOT.
    • Drop the last_seen indexes and prune by a batched sequential scan on the daily purge (the tables are keyed stores, not hypertables). With a fillfactor below 100, a last_seen-only update stays on-page with no index entries.
    • Measure the purge's cost before and after.
  • Spread the post-restart wave. The first cycle touches everything at once, which is 23% of WAL in that hour. It could jitter the touch by server or by key hash.
  • Pin:
    • the guard derivation test already exists; add the measured write volume to the divisor's comment, so the next reader has both sides of the trade;
    • if the index is dropped, a live test that a touch is HOT (n_tup_hot_upd increments).

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    client-siteOwned by the client-site agents (other laptop). Local sessions never pick these up.enhancementNew feature or requestin-progressActively being worked by a local session or its agents (PR open or in flight)

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions