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.
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_logERROR rows joined againsttimescaledb_information.job_historyon the SQL Server store, 40 of 40 contained: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_statesandquery_snapshotsall appear.The mechanism, from a live lock sample
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_statsruns 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%.Three CAGG jobs and one compression policy, all on
query_store_stats, wereRunningconcurrently at sample time, with three autovacuum workers on the same chunk space (one for 9,066 s). The refreshes' wait events areIO/DataFileReadandIO/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_statsper 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_compressionand the CAGG refresh policies onquery_store_statsfrom 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_statsis 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.