Skip to content

Collector store-writes are cancelled by the store's own CAGG refresh and compression: a lock convoy on query_store_stats #3012

Description

@erikdarlingdata

Collector store-writes are being cancelled by the store's own background jobs. Every collector write error in a 7-day window falls inside a running continuous-aggregate refresh or compression window — not near one, contained within one.

The containment, joined rather than eyeballed

collect.collection_log ERROR rows joined against timescaledb_information.job_history on the SQL Server store, 40 of 40 contained:

09:01:25-09:25:18  8 errors, 5 collectors   inside CAGG refresh   08:25:10 -> 09:36:11
09:11:16           3 servers, 11 ms apart   inside CAGG refresh   09:07:03 -> 09:12:14
09:38:23-24        8 errors, 6 collectors   inside COMPRESSION    09:25:58 -> 09:39:00
10:40:43-56        5 errors                 inside CAGG refresh   10:38:59 -> 10:47:20
11:19:44-11:24:31  5 errors                 inside CAGG refresh   11:16:00 -> 11:35:25

Three different servers failing 11 ms apart cannot be a target-side coincidence — independent RDS instances do not synchronise. The only shared dependency is the service's store connection. The collector roster is also not query-path-specific: wait_stats, spinlock_stats, ag_database_replica_states and query_snapshots all appear.

The mechanism, from a live lock sample

holder  5912  Refresh Continuous Aggregate   holds AccessShareLock      running 5,507 s
waiter   392  Columnstore Policy             wants AccessExclusiveLock  blocked
waiter  5684  Refresh Continuous Aggregate   wants AccessShareLock      blocked
waiter  2768  client backend                 wants AccessShareLock      blocked

A lock convoy. The refresh holds a shared lock for 92 minutes; the compression policy queues an exclusive request behind it; and because a queued exclusive blocks every subsequent shared request, collectors and readers pile up behind it even though their shared lock is compatible with what is actually held. Convoy head is compression waiting on refresh; the tail is ordinary collector traffic.

Why the refresh is long enough for this to happen

The hourly CAGG refresh over query_store_stats runs 3,300-6,300 s — 118-175% of its own schedule interval. Because the next start is scheduled from the previous finish, an hourly job taking 55-105 minutes occupies roughly half of all wall-clock. Two days earlier it took ~2%.

15:30 ->  61s    17:32 -> 142s    19:42 ->  509s
20:50 -> 5,543s    23:23 -> 6,304s    08:25 -> 4,261s

Three CAGG jobs and one compression policy, all on query_store_stats, were Running concurrently at sample time, with three autovacuum workers on the same chunk space (one for 9,066 s). The refreshes' wait events are IO/DataFileRead and IO/DataFileWrite — they contend for disk with each other as well as blocking.

What is NOT the cause

Not ingest volume. Rows arriving into query_store_stats per hour fell over the same period — 999,101 at 19:00 down to 359,605 at 08:00 — while refresh duration rose ~8x. Run counts moved +23%. The relationship is inverse; "more rows to materialize" is falsified.

Not retention. The application-level purge runs once daily and none of its runs is near a burst; the in-database retention policies do not appear in the containment set either. The correlate is refresh and compression specifically.

Suggested direction

The convoy head is compression contending with refresh on the same hypertable, so schedule deconfliction is the narrowest lever — prevent policy_compression and the CAGG refresh policies on query_store_stats from overlapping. That is independent of collection settings and costs no throughput.

Shortening the refresh (a smaller materialization window per run, or a longer schedule interval) treats the same head from the other side. #2316 already records that query_store_stats is the store's problem child on compression ratio; it is the hypertable to look at, not the collectors.

Not verified

What made the first refresh cross its cadence is unproven. A sweep-concurrency change landed three minutes before the 509 s -> 5,543 s step, and equal volume delivered by more simultaneous writers would raise IO and lock-hold overlap without raising ingest — which fits the measurements — but that is a hypothesis and the concurrency change has since been reverted, so the natural experiment is running now rather than concluded.

Also unmeasured: whether the same convoy exists on the sibling SQL Server store, which carries a much smaller query_store_stats.

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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions