Repository navigation
Description
MutableEpoch::exact_query (asap-query-engine/src/stores/simple_map_store/common.rs:238) takes &mut self because it lazily builds window_to_ids: Option<HashMap<TimestampRange, Vec<u32>>> on first access after any write, invalidating it on every insert. Callers (per_key.rs:620, global.rs:568, and legacy equivalents) must therefore take a write lock on the whole per-aggregation store shard for any exact-window query — even though the query itself doesn't mutate stored data, only an internal index.
By contrast:
query_precomputed_output(the range/scan call) only takes a read lock (per_key.rs:477,.read()).SealedEpoch::exact_query(common.rs:350) already takes&selfand does a binary search — sealed (immutable) epochs have no lock problem at all.
So the write-lock requirement is scoped to exactly one struct: the current, still-being-written MutableEpoch's lazy index.
Effect: any code path that calls query_precomputed_output_exact serializes against all other readers/writers of that aggregation's current epoch for the query's duration — a real concurrency cost, not just latency. This matters more once #608 routes more traffic through the exact-query call (currently only Sliding-window instant queries use it).
Proposed fix
Give MutableEpoch::exact_query a &self signature via interior locking scoped to just the index, e.g.:
- An atomic "dirty" flag (replacing the plain
window_to_ids = Nonefield write on insert — should stay ~zero-cost, an atomic store instead of a bare field write). - The index itself behind its own small
RwLock<Option<HashMap<TimestampRange, Vec<u32>>>>(or similar), rebuilt under that inner lock only when dirty.
This lets per_key.rs/global.rs (and their legacy equivalents) always take the outer per-shard lock as .read(), for both scan and exact queries. Insert's cost should remain effectively unchanged (still O(1) amortized, no HashMap work on the hot path).
Scope
asap-query-engine/src/stores/simple_map_store/common.rs(MutableEpoch)asap-query-engine/src/stores/simple_map_store/per_key.rsandglobal.rs(lock acquisition at the call sites)- Legacy equivalents under
simple_map_store/legacy/— check whether they're still live or dead code before touching.
Testing
Needs both a correctness test (index still returns correct results across concurrent insert+query, dirty flag correctly forces rebuild) and, if feasible, a concurrency test demonstrating that two exact queries (or an exact query + a scan) no longer block each other.
Related: #581 (this removes a blocker for routing more of #581's fetch-mode unification through the exact-query call), #608.
Activity
milindsrivastava1997 commentedon Aug 25, 2026
FYI @zzylol
ASAPQuery-backend doesn't solve it — it has the identical unfixed design, and currently avoids triggering it only by not calling the affected function anywhere in production.
Structural equivalent: Yes, exact port. data_plane/src/storage_engines/sketch_db/index/epoch_columnar.rs is explicitly lifted from sketch_db::common (MutableEpoch). Same window_to_ids: Option<HashMap<TimestampRange, Vec>> field (line 162), same pub fn exact_query(&mut self, ...) (line 265), same lazy-build-on-first-query/invalidate-on-insert design, same &mut self requirement for the same reason. Nothing in its "differences from legacy" notes touches lock granularity.
Mitigation: None exists — but the problem is dormant. exact_query/exact_query_owned have zero production call sites (only internal self-calls and one unit test). Every live query path uses range_query_into (&self, no index) instead. Interestingly, several call
sites still take .write() defensively even though they never call exact_query — e.g. index/mod.rs:610 with the comment // exact_query may build the lazy index — rather than actually needing it. So the backend isn't ahead of you here; if anything it's slightly more
conservative (over-locking against a hypothetical), and it'll hit the exact same MutableEpoch::exact_query requires a write lock, serializing concurrent queries #607 problem the moment something starts calling exact_query for real.