Skip to content

Darling: every server skips one collection pass about 30 minutes after a service restart (first seen in the slot log of nightly 550) #5824

Closed

Description

@erikdarlingdata

Claude posting for Erik Darling.

Measured on the largest field store (store A, 42 servers) on 2026-10-14, read-only, after the nightly 550 restart at 20:57Z. Nightly 550 is the first build that records a stepped-over collection slot (#5741), so this is the first time the dip is visible in collect.collection_log. The 547 restart on 10-13 shows the same dip in the count of successful runs, so it is not new.

What happens

  • After a service restart, the first data_retention run goes long: 1,820 s (20:58 to 21:28), 240,055 rows deleted, about 3.9 GB of WAL. The purge then runs in rounds of about 30 minutes.
  • About 2 minutes after that first run ends, every server skips one collection pass: 84 SKIPPED rows at 21:30 to 21:39, one or two on all 42 servers, each waiting 78 to 211 s for a fleet collection slot (or "no single cause was measured"). Eleven more at 21:01 to 21:02 are restart catch-up (56 to 67 s waits). From 22:00 nothing is skipped.
  • Successful runs per 5 minutes around the dip (two restarts):
Minutes after restart 547 (10-13) 550 (10-14)
25 to 30 (normal) 4,572 4,679
30 to 35 2,905 2,230
35 to 40 4,226 3,744
40 to 45 (normal again) 4,574 4,644

Inferred, not tested: the end of the purge, or the work that follows it (checkpoint, autovacuum on the purged chunks, or compression), slows the store for a few minutes; collectors hold their fleet slots longer, and the next passes skip.

Why it matters

About 16 KB of WAL per deleted row is far more than a plain DELETE should write; it points at deletes against compressed chunks, or at a purge that rewrites more than it removes. One skipped pass per server per restart is small; a purge that writes gigabytes every 30 minutes is the thing to measure.

Ask

  1. Measure, on the same store, what the retention purge deletes from and how much WAL each table's purge writes per row (the data_retention log rows and pg_stat_wal around one round). If deletes hit compressed chunks, use drop_chunks for whole-chunk retention and delete rows only inside the partial chunk.
  2. Check whether the dip follows the purge's later rounds too, not only the first after a restart. If it does, the fix is in the purge; if it does not, the first round's size (the restart backlog) is the cause and a capped first round would do.
  3. If the purge stays long, make the fleet slot wait not count a store-side stall as a missed slot for every server at once (one purge should not skip 42 passes).

No seat work is assigned yet; this records the measurement.

Activity

  1. added
    bugSomething isn't working
    client-siteOwned by the client-site agents (other laptop). Local sessions never pick these up.
    owner:noneNo owner yet - deliberately unclaimed
    on Oct 14, 2026
  2. erikdarlingdata commented on Oct 14, 2026

    @erikdarlingdata
    OwnerAuthor

    Claude posting for Erik Darling.

    Correction, from one row per data_retention round on the same store since 10-11 (the SKIPPED column exists only from build 550; the before/after columns are successful collection_log runs in the 10 minutes before and after the round ended):

    Round ended (UTC) Rows deleted WAL MB SKIPPED next 10 min Success before Success after Change
    10-11 to 10-12, seven rounds 2.7 M to 7.5 M 2,516 to 6,886 n/a about 9,100 about 9,100 0 to -3%
    10-13 16:42, first after the 547 restart 476,915 3,498 n/a 9,077 7,221 -20%
    10-13 18:13, second after 547 6,647,178 5,778 n/a 8,977 8,260 -8%
    10-13 19:45 and 21:11 1.9 M to 7.2 M 3,194 to 3,587 n/a about 9,120 about 9,100 0 to -1%
    10-14 21:28, first after the 550 restart 240,055 3,877 84 9,385 5,952 -37%
    10-14 22:58, second after 550 11,535,659 5,250 8 9,368 9,206 -2%

    What changes

    • The dip follows only the first round after a restart. Later rounds show none, and it does not follow the purge's size: the two biggest dips came after the two smallest purges, and the largest purge of all (11.5 M rows tonight) gave 8 skips and -2%.
    • So the purge's end is probably not the cause. The better suspect is something else that starts about 30 minutes after a restart, and the first purge round only ends at the same time. The issue's title blamed the purge; it is retitled.
    • The WAL figure in the opening post is wrong as a per-row claim: a round writes 2.5 to 7 GB whether it deletes 240 thousand or 11.5 million rows, so the WAL is mostly per round, not per row. That is still worth one look (what in a round writes gigabytes regardless of rows), but it is a separate, lower question.

    Inferred, not tested: the first scheduled analysis pass runs about 30 minutes after a service start (the analysis cadence), and before #5812 its PLAN_REGRESSION read was the store's most expensive statement, with its slowest runs at the 60 s timeout. A first pass over every server at once would hold the store for a few minutes and make every collector wait for its fleet slot. If that is it, #5812 (PR #5817) removes the cause.

    Next check, cheap: on the same store, the start time of the first scheduled analysis pass after the 550 restart, and whether its PLAN_REGRESSION reads ran inside 21:28 to 21:40. After #5817 is installed, the next restart answers it for good.

  3. changed the title [-]Darling: the first retention purge after a restart writes gigabytes of WAL, and every server skips one collection pass when it ends[/-] [+]Darling: every server skips one collection pass about 30 minutes after a service restart (first seen in the slot log of nightly 550)[/+] on Oct 14, 2026
  4. erikdarlingdata commented on Oct 14, 2026

    @erikdarlingdata
    OwnerAuthor

    Claude posting for Erik Darling.

    The analysis-pass suspect from the last comment does not hold on its own. From the same store's analysis_findings (2-minute buckets) and the service log:

    Pass after the 550 restart Ran PLAN_REGRESSION findings Inside the dip?
    1st 20:58 to 21:05, 42 servers 41 servers no
    2nd 21:28 to 21:35, 42 servers 41 servers yes
    3rd 21:58 to 22:05, 42 servers 41 servers no

    The first pass runs 0 to 150 s after each server connects, not 30 minutes after the start. The second pass sits inside the dip, but every later pass has the same shape and no dip, and the 547 restart on 10-13 shows the same pattern (the dip on the second pass only).

    Three things ended together inside the dip window: the first purge round (21:28:27), the daily module_map refresh that starts when a purge round ends (done 21:32:58, about 60,000 attributions), and the second analysis pass. All three also ran together at 22:58 to 23:05, with 8 skips and a -2% change, and after each of the four purge rounds on 10-13 with a dip only after the first. So nothing found so far happens only once after a restart.

    A small separate finding: three PgDrillDownCollector errors (NpgsqlException: Exception while reading from stream, PLAN_REGRESSION -> CPU_SQL_PERCENT -> SOS_SCHEDULER_YIELD), each next to a purge end and a module_map refresh (22:59:38 and 23:00:16 on 550; 19:46:30 on 547). Not new in 550.

    Next check (cheap, on the same store): total collector time per collector in the 10 minutes after 21:28 against the 10 minutes after 22:58, and which collectors the 84 SKIPPED rows name. That shows what held the fleet slots the first time and not the second.

  5. erikdarlingdata commented on Oct 15, 2026

    @erikdarlingdata
    OwnerAuthor

    Claude posting for Erik Darling.

    The per-collector check (same store, collection_log by end time, SKIPPED rows excluded; rounded, top rows by difference):

    Collector Seconds 21:28 to 21:38 Seconds 22:58 to 23:08 Runs 21:28 Runs 22:58 Avg s 21:28 Avg s 22:58
    attention 209 352 250 411 0.8 0.9
    procedure_stats 111 220 250 412 0.4 0.5
    plan_correction 338 235 84 84 4.0 2.8
    query_store 314 409 84 83 3.7 4.9
    query_stats 106 157 250 413 0.4 0.4
    data_retention (one round ended in each window) 1,820 1,804 1 1
    All collectors 3,281 3,740
    • No collector was slower per run in the dip window (only plan_correction, 4.0 s against 2.8 s over the same 84 runs). The per-minute collectors simply ran about 39% less often (250 runs against about 412).
    • The 84 SKIPPED rows name all 42 servers, 3,400 slots, every due collector on each row (20 per row); 47 rows say "waited for a fleet slot", 37 "no single cause". The 8 rows at 22:58 name 8 servers, 160 slots.
    • The SKIPPED message's "top" list names the same three collectors on every row (ag_database_replica_states, ag_replica_states, attention, 1 or 2 slots each): that is a tie broken alphabetically, not a signal. The message should say so.
    • The Npgsql pool reported no exhaustion or connection timeout in either window (0 "pool" lines; 0 WARN or ERROR with "connection").

    So the fleet slots were held by work that collection_log does not record. A comment in DarlingWorker.cs says the analysis pass runs inside the per-server sweep body; if that body holds the server's fleet slot, a slow pass holds the slot, and the second pass after a restart sits inside the dip (comment above). Its run time is not recorded anywhere, so this cannot be shown from the store today. Inferred, not traced.

    Next, in code rather than in the store: (1) trace whether the analysis pass runs inside the fleet slot; (2) record each server's analysis-pass run time (a collection_log row, so get_collector_cost and this check can see it); (3) make the SKIPPED message name "every due collector" when the slot counts tie; (4) read what the end of a purge round does that writes gigabytes of WAL whatever the row count, and that sits next to the three PgDrillDownCollector stream errors. The next restart after that change answers the question with numbers.

  6. added
    in-progressActively being worked by a local session or its agents (PR open or in flight)
    on Oct 15, 2026
  7. erikdarlingdata commented on Oct 15, 2026

    @erikdarlingdata
    OwnerAuthor

    Claude posting for Erik Darling.

    Field check on nightly 553 (3.10.0-nightly.20261015.553, dev ab5b0b7, carrying #5817, #5826 and #5828), installed on all four stores by 14:04Z. Numbers from the collection log of the 42-server store, read after the restart.

    Successful collection runs per 5 minutes after the restart (every collector except the analysis pass):

    Minutes after restart 0-5 5-10 10-30 30-40 40-110
    Successful runs 2,156 4,985 4,641-4,742 4,587 and 4,716 4,574-4,735
    SKIPPED rows 18 11 0 0 0

    On 547 and 550 the 30-35 minute bucket fell to 2,905 and 2,230. On 553 it is 4,587. SKIPPED rows between +15 and +45 minutes, the symptom this issue describes: 0.

    The only SKIPPED rows are 29 in the first 10 minutes, one per server, all naming the analysis pass with the #5828 cause text: 18 waited for a fleet collection slot held by scheduled analysis passes, 11 had the previous pass's scheduled analysis holding the server's slot after its collectors.

    Analysis pass durations over 42 servers: the second pass (+15 to +45 min) p50 15.7 s, p95 24.7 s, max 26.8 s; the quiet window (+75 to +105 min) p50 5.9 s, p95 17.9 s, max 22.1 s. The second pass still takes about 2.7 times the quiet p50, but it no longer starves collection.

    One restart, not a trend. Closing as fixed in 553. The next nightly restart is the confirmation; this reopens if the dip comes back.

  8. removed
    in-progressActively being worked by a local session or its agents (PR open or in flight)
    on Oct 17, 2026
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

    bugSomething isn't workingclient-siteOwned by the client-site agents (other laptop). Local sessions never pick these up.owner:noneNo owner yet - deliberately unclaimed

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions