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
PlanRegressionSql is ~90% of store statement cancellations: 25-90s dedup sort, no CommandTimeout, and a faster identical-result formulation #2827
PgFactCollector.PlanRegressionSql is ~90% of all statement cancellations on the use1 Darling store: 325 of 371 for 2026-09-03, and 31 of 34 in the two hours to 08:50Z. It has no explicit CommandTimeout, so it inherits Npgsql's undocumented 30 s default, and it measures 25–90 s on the busier servers. Every cancellation is then silently swallowed (see #2826).
Grouping every canceling statement due to user request in the store's PG log by the statement that follows it (with multi-line STATEMENT: continuation capture):
statement
all day
last 2 h
WITH deduped AS (… — this query
325
31
SELECT DISTINCT database_name FROM query_store_stats …
27
2
WITH snaps AS (SELECT DISTINCT collection_time FROM v_index_object_stats …
8
0
WITH per_collection AS (…
4
1
SELECT MAX(last_execution_time) FROM query_store_stats … (#2796's, now bounded)
3
0
four others
1 each
0
The plan is healthy — this is not #2796's or #2820's shape
EXPLAIN (ANALYZE, BUFFERS), server 1671144557, work_mem=31MB:
Funnel: ~4M rows scanned → 1,724,344 deduped → 81,127 plan_agg → 30,925 plan_dedup → 2,091 past the CPU floor → 20 rows.
work_mem is not the constraint, and raising it makes things worse — measured, not assumed:
work_mem
Execution Time
31 MB (default)
25,617 ms
512 MB
59,323 ms
Not a one-server outlier: the 3rd-busiest server measures 25,141 ms on the same plan shape. Several servers sit just under the 30 s cliff, which is why cancellation is intermittent (325/day) rather than total.
The dedup is the cost, and there is a faster formulation
Isolating the deduped CTE alone (same filters, same server, count(*) of the result):
formulation
time
rows
current — ROW_NUMBER() OVER (…) … WHERE rn = 1
23,374 ms
1,724,487
DISTINCT ON (…)
49,807 ms
1,724,681
GROUP BY … + ordered array_agg(…)[1]
9,608 ms
1,724,681
(row-count differences are live-data growth between runs, not semantics — DISTINCT ON and array_agg agree exactly)
The window-function form forces a global sort of ~4M rows on 8 keys. The GROUP BY form lets the planner hash-aggregate at the interval grain and sort only within each small group.
Results are byte-identical. md5 over the full ordered result set, both arms, 6 servers:
server
rows
ORIG md5
NEW md5
match
1671144557
20
4fef20b1…
4fef20b1…
✓
406978503
20
e9b9ee07…
e9b9ee07…
✓
-1782126272
20
97e1b994…
97e1b994…
✓
-1201003152
20
419a73fa…
419a73fa…
✓
1137744987
0
(empty)
(empty)
✓
1137774987
20
99aeb51c…
99aeb51c…
✓
Paired timings in that run (ORIG → NEW): 27.8→25.7, 25.5→24.0, 46.1→31.6, 43.6→39.0, 91.0→41.3 s.
Honest limits
A later alternating A/B, run while the box was loaded by this investigation's own queries, was noisier: NEW won 3 of 4 and lost once (33.6 → 40.9 s). NEW's spread was 38.9–40.9 s against ORIG's 33.6–83.9 s, so the clearer benefit may be variance rather than median.
PlanRegressionSql was reported earlier as having "dropped off the cancellation list entirely". It did not — WITH deduped AS (isPlanRegressionSql's leading CTE. The earlier grouping labelled it by method name and this one by query text; they are the same statement. Its rate fell from ~93/hr at 01:00Z to ~17/hr, tracking the overall decline after #2796 and the retention arming, but it has been the dominant source throughout.
Resolved by #2830, merged to dev. Closing manually: Fixes #N only fires on the dev->main release merge, so completed work otherwise sits open until release.
Summary
PgFactCollector.PlanRegressionSqlis ~90% of all statement cancellations on the use1 Darling store: 325 of 371 for 2026-09-03, and 31 of 34 in the two hours to 08:50Z. It has no explicitCommandTimeout, so it inherits Npgsql's undocumented 30 s default, and it measures 25–90 s on the busier servers. Every cancellation is then silently swallowed (see #2826).Grouping every
canceling statement due to user requestin the store's PG log by the statement that follows it (with multi-lineSTATEMENT:continuation capture):WITH deduped AS (…— this querySELECT DISTINCT database_name FROM query_store_stats …WITH snaps AS (SELECT DISTINCT collection_time FROM v_index_object_stats …WITH per_collection AS (…SELECT MAX(last_execution_time) FROM query_store_stats …(#2796's, now bounded)The plan is healthy — this is not #2796's or #2820's shape
EXPLAIN (ANALYZE, BUFFERS), server1671144557,work_mem=31MB:PlanRegressionWindowDays) while retention now keeps 4 — thecollection_time >= $3bound added by [BUG] Darling: PLAN_REGRESSION analysis query full-scans/decompresses entire query_store_stats history every cycle (no collection_time bound) #2387 is doing its job and simply has nothing left to exclude. Compressed chunks useCustom Scan (ColumnarScan)at ~7–8k cost each; the recent uncompressed chunk is 1.195M of the 1.571M Append cost.Run Condition: (row_number() OVER w1 <= 1)pushdown is working, incremental sorts throughout.shared hit=146705 read=105041,temp read=70188 written=70208(~548 MB spill,external merge).plan_agg→ 30,925plan_dedup→ 2,091 past the CPU floor → 20 rows.work_memis not the constraint, and raising it makes things worse — measured, not assumed:Not a one-server outlier: the 3rd-busiest server measures 25,141 ms on the same plan shape. Several servers sit just under the 30 s cliff, which is why cancellation is intermittent (325/day) rather than total.
The dedup is the cost, and there is a faster formulation
Isolating the
dedupedCTE alone (same filters, same server,count(*)of the result):ROW_NUMBER() OVER (…) … WHERE rn = 1DISTINCT ON (…)GROUP BY …+ orderedarray_agg(…)[1](row-count differences are live-data growth between runs, not semantics —
DISTINCT ONandarray_aggagree exactly)The window-function form forces a global sort of ~4M rows on 8 keys. The
GROUP BYform lets the planner hash-aggregate at the interval grain and sort only within each small group.Results are byte-identical. md5 over the full ordered result set, both arms, 6 servers:
4fef20b1…4fef20b1…e9b9ee07…e9b9ee07…97e1b994…97e1b994…419a73fa…419a73fa…99aeb51c…99aeb51c…Paired timings in that run (ORIG → NEW): 27.8→25.7, 25.5→24.0, 46.1→31.6, 43.6→39.0, 91.0→41.3 s.
Honest limits
Correction to a prior finding
PlanRegressionSqlwas reported earlier as having "dropped off the cancellation list entirely". It did not —WITH deduped AS (isPlanRegressionSql's leading CTE. The earlier grouping labelled it by method name and this one by query text; they are the same statement. Its rate fell from ~93/hr at 01:00Z to ~17/hr, tracking the overall decline after #2796 and the retention arming, but it has been the dominant source throughout.