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 }}
This repository was archived by the owner on Aug 3, 2026. It is now read-only.
From the 2026-07-27 concurrency work (root-cause analysis behind patches 0006/0010). indexset's CDC maps assign event ids with fetch_addinside every mutation, including the non-CDC convenience methods: insert()/remove() call put_cdc/remove_cdc internally and then discard the events (see indexset src/concurrent/map.rs, e.g. insert → put_cdc(..).0; try_insert → .ok().map(|_| ())).
Consequence: any non-CDC mutation on an index that also feeds a persistence stream consumes ids without queueing events → a permanent gap in the event-id sequence. Since the strict-deferral fix (patch 0006), BatchOperation::validate never force-applies a gapped stream, so such a gap stalls persistence forever, loudly (tracing::error after 8 attempts). That is deliberate: a loud stall instead of silent positional corruption of the on-disk index. Vacuum was the one reachable case on persisted tables and was fixed in patch 0010 (VacuumPersistence sink).
What remains
In-memory tables still use non-CDC mutations (e.g. the non-CDC reinsert in src/table/mod.rs, vacuum's sink-less path in src/table/vacuum/vacuum.rs). Harmless today — no persistence consumer exists there — but it is a trap: attaching any future consumer (replication, S3 sync, CDC subscribers) to a path that mixes in even one non-CDC mutation produces an unfixable stall by design.
Nothing enforces the invariant "an index with a persistence consumer is only mutated through *_cdc".
Suggested direction
One or more of:
Documentation: state the invariant on LockMap-adjacent index types and in the persistence module docs.
Debug guard: wrap indexes destined for persistence in a newtype that only exposes *_cdc methods (compile-time enforcement, the strongest option).
Runtime tripwire: a debug_assert!/counter comparing ids consumed vs events queued per index (catches regressions in tests immediately, given the suite already runs everything through wait_for_ops with timeouts).
Verification hints
The stall symptom is deterministic: wait_for_ops hangs, tracing::error names the last applied and next available ids. tests/persistence/vacuum.rs shows the pattern for turning a stall into a 30s-timeout failure. Reverting patch 0010's one-line .with_persistence(..) wiring reproduces a genuine gap stall on demand.
Context
From the 2026-07-27 concurrency work (root-cause analysis behind patches 0006/0010). indexset's CDC maps assign event ids with
fetch_addinside every mutation, including the non-CDC convenience methods:insert()/remove()callput_cdc/remove_cdcinternally and then discard the events (see indexsetsrc/concurrent/map.rs, e.g.insert→put_cdc(..).0;try_insert→.ok().map(|_| ())).Consequence: any non-CDC mutation on an index that also feeds a persistence stream consumes ids without queueing events → a permanent gap in the event-id sequence. Since the strict-deferral fix (patch 0006),
BatchOperation::validatenever force-applies a gapped stream, so such a gap stalls persistence forever, loudly (tracing::errorafter 8 attempts). That is deliberate: a loud stall instead of silent positional corruption of the on-disk index. Vacuum was the one reachable case on persisted tables and was fixed in patch 0010 (VacuumPersistencesink).What remains
reinsertin src/table/mod.rs, vacuum's sink-less path in src/table/vacuum/vacuum.rs). Harmless today — no persistence consumer exists there — but it is a trap: attaching any future consumer (replication, S3 sync, CDC subscribers) to a path that mixes in even one non-CDC mutation produces an unfixable stall by design.*_cdc".Suggested direction
One or more of:
LockMap-adjacent index types and in the persistence module docs.*_cdcmethods (compile-time enforcement, the strongest option).debug_assert!/counter comparing ids consumed vs events queued per index (catches regressions in tests immediately, given the suite already runs everything through wait_for_ops with timeouts).Verification hints
The stall symptom is deterministic:
wait_for_opshangs,tracing::errornames the last applied and next available ids.tests/persistence/vacuum.rsshows the pattern for turning a stall into a 30s-timeout failure. Reverting patch 0010's one-line.with_persistence(..)wiring reproduces a genuine gap stall on demand.