You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
The I/O-latency baseline re-reads 30 days of raw per-file rows every hour for every server and spills ~50 MB to disk each time (store B: ~50–95 GB/day of temp writes) #4248
Related:#2820 (the MAD-tier join fix on this arm), #3941 (baseline cache sharing), #1757
Problem
PgBaselineProvider's MetricNames.IoLatency arm reads the rawfile_io_stats hypertable at per-file grain over the full 30-day baseline window (BaselineMath.BaselineWindowDays). It feeds the rows through RobustTierScaffold:
GROUPING SETS over hour × day-of-week, hour, and all;
a keyed_tiersUNION ALL that triples the rows;
percentile_cont twice (median, then MAD).
Those sorts don't fit in work_mem, so each call writes a temp file. The window ends on the analysis hour (#3941), so every server's baseline is recomputed once an hour. A 30-day baseline moves by about 1/720 per hour.
Measured
Production SQL Server stores, build 3.8.0-nightly.20260924.467, 2026-09-25, pg_stat_statements (owner role). work_mem is 64 MB on both stores.
calls
temp written
per call
per day
Store B (42 servers), 21:59–03:57Z
262
13.5 GB
~52 MB
~54 GB
Store B, window 03:43–04:38Z
78
3.63 GB
~47 MB
~95 GB
Store B, steady window 04:45–05:41Z
42
2.00 GB
~48 MB
~52 GB
Store A (43 servers), 22:09–03:57Z
274
6.0 GB
~22 MB
~25 GB
Store A, windows 03:43–04:38Z / 04:45–05:41Z
54 / 43
0.26 / 0.26 GB
~5–6 MB
~7 GB
On store B this one statement was the largest temp writer:
13.5 of 22.1 GB of all temp since the stats reset;
3.6 of 6.0 GB in the first window;
86% of temp in the steady window.
Does it reach the disk? Store B's volume took 440 GB of writes on Sep 24 (CloudWatch). The store-side sum of WAL, data, extend and temp writes came to 98% and 111% of the volume's writes in the two windows, so on that store the temp files do reach the disk, give or take ~10%.
Share of disk writes: in the steady window this read was ~16% of store B's volume writes (1.5–2 GB of 9.8 GB), and ~10–20% across all the readings.
Stop the spill.SET LOCAL work_mem sized for this statement: the measured spill is ~50 MB, so the in-memory sort needs roughly 2–4× that. Add a live rig pin that EXPLAIN (ANALYZE) of the arm over a seeded 30-day, per-file dataset reports no external merge.
Shrink the input. Collapse per-file rows to one ratio per collection (sum of stall over sum of reads) before the scaffold, if the per-file grain isn't load-bearing for the anomaly semantics. This changes Lite parity, so it's the maintainer's call.
Pin: the arm's pg_stat_statements.temp_blks_written over a full analysis pass on the rig is zero, or under a stated bound.
No work_mem change. With a daily recompute the spill happens once a day per server, not 24 times. That is enough, and a larger per-statement work_mem risks memory on a busy store.
No change to the per-file grain. Collapsing it changes what the baseline means, and Lite needs the same change.
Lite: check whether Lite recomputes the same baseline every hour from raw data. If it does, give it the same daily key, so both products flag the same anomalies.
Pins:
Two calls in the same UTC day share one computation, and a call the next day recomputes.
On the rig, temp_blks_written for the arm across a day of hourly passes shows one spill, not 24.
The cache entry lasts until the day ends.CacheTtl is 1 hour. If these entries keep that TTL, each one expires after an hour and the arm recomputes anyway. Give these arms' entries a lifetime that runs to the end of their key's day. Check that the shared tier's sweep honors it.
The pins stand. The first pin must span more than an hour inside one day.
Activity
erikdarlingdata commentedon Sep 25, 2026
Ruled: how this gets built
work_memchange. With a daily recompute the spill happens once a day per server, not 24 times. That is enough, and a larger per-statementwork_memrisks memory on a busy store.temp_blks_writtenfor the arm across a day of hourly passes shows one spill, not 24.🤖 Generated with Claude Code
https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
erikdarlingdata commentedon Sep 25, 2026
Addendum to the ruling above
Two things the daily key needs, or it saves nothing:
CacheTtlis 1 hour. If these entries keep that TTL, each one expires after an hour and the arm recomputes anyway. Give these arms' entries a lifetime that runs to the end of their key's day. Check that the shared tier's sweep honors it.The pins stand. The first pin must span more than an hour inside one day.
🤖 Generated with Claude Code
https://claude.ai/code/session_01TszxYhJJbTEh4LrZ56NYo3
Lite: mirror the day-cache fix for Cpu and IoLatency (#4248)
Cache the I/O-latency and CPU baselines by UTC day, not the hour (#4248…
erikdarlingdata commentedon Sep 25, 2026
Fixed by #4291, merged to dev (ff376dc).
Pin the day-keyed PgTargetBaselineProvider cache (#4298)
PostgreSQL-target baselines recompute once a day, not every hour (#4298…
SQL Server blocking and deadlock baselines divide by the hours collec…