Experiment 045: Microtask invalidation coalescing

Date: 2026-04-15

Status: Accepted

Problem

In StreamEngine.handleDirtyTables (stream_engine.dart:68), each write dispatches re-queries immediately. When multiple rapid db.execute() calls happen synchronously (e.g., a loop of inserts without an explicit transaction), each triggers a separate invalidation pass. If 5 inserts each dirty the same table, a stream watching that table gets 5 re-queries — only the last result matters.

The reQueryGeneration counter already handles correctness (stale results are discarded), but the redundant re-queries waste reader pool capacity.

Hypothesis

Accumulate dirty tables within a Dart microtask and flush once. The Dart event loop provides a natural coalescing boundary: scheduleMicrotask fires after the current synchronous work completes. Multiple handleDirtyTables calls within the same synchronous execution batch are unioned into a single invalidation pass.

Approach

 Set<String>? _pendingDirtyTables; bool _flushScheduled = false; void handleDirtyTables(List<String> dirtyTables) { if (dirtyTables.isEmpty) return; _writeGeneration++; // Accumulate dirty tables. if (_pendingDirtyTables == null) { _pendingDirtyTables = Set<String>.from(dirtyTables); } else { _pendingDirtyTables!.addAll(dirtyTables); } if (!_flushScheduled) { _flushScheduled = true; scheduleMicrotask(_flushDirtyTables); } } void _flushDirtyTables() { _flushScheduled = false; final tables = _pendingDirtyTables; _pendingDirtyTables = null; // ... existing invalidation logic using `tables` } 

Results

18 wins, 0 regressions (3 repeats vs baseline).

BenchmarkBaseline (ms)Coalesced (ms)Delta
Fan-out (10 streams)0.240.13-46%
Fan-out [main]0.240.13-46%
Invalidation latency0.050.04-20% (within noise)

The fan-out improvement is the direct signal: the benchmark performs rapid sequential writes that trigger multiple invalidations on the same stream set. Coalescing reduces these to a single re-query dispatch per microtask.

Decision

Accepted. Clean 46% improvement on the fan-out benchmark with zero regressions. The change is a strict behavioral improvement — fewer redundant re-queries, less reader pool contention — with no observable latency increase for single-write scenarios (invalidation latency unchanged).

Correctness is preserved by _writeGeneration and reQueryGeneration — stale results from in-flight re-queries are already discarded on arrival.