3946117084
Replace the SharedFlow-based broadcast in LiveEventStore with an inverted index over filter-bearing subscribers. Each REQ registers its filters into a per-store FilterIndex<LiveSubscription>; insert() calls index.candidatesFor(event) and only delivers to candidates whose Filter.match still passes. Cuts the per-event walk from O(N_subs * N_filters) to a few hash lookups plus match() over a small candidate set. FilterIndex itself lives next to Filter.kt (commonMain, KMP-friendly, AtomicReference + COW) so other call sites with the same shape (LocalCache.observables, ObservableEventStore.changes) can reuse it. Each filter contributes entries on its single most-selective dimension (ids > authors > tags > tagsAll > kinds > unindexed) to keep buckets narrow and avoid Set-dedupe work in candidatesFor. The historical-replay race the previous SharedFlow + onSubscription handoff closed is preserved by registering BEFORE replay starts and deduping seen ids until EOSE.
geode plans
Performance-focused design docs for future work. Each file is a self-contained sketch — problem statement, observed numbers, proposed fix, how to verify, risks. None of these are committed work; they're the queue.
Ordered roughly by expected impact:
| Plan | Headline gain |
|---|---|
| 2026-05-07-event-ingestion-batching.md | 5–10× write EPS via SQLite group commit + ingest pipelining |
| 2026-05-07-live-broadcast-fanout-index.md | >10× fanout speedup at >2 000 subscribers |
| 2026-05-07-connection-scaling.md | 2 000 → 10 000+ concurrent connections |
| 2026-05-07-negentropy-large-corpus.md | 25× lower memory + faster NEG-OPEN on M-event corpora |
Verification target for each plan is a new method on
geode.perf.LoadBenchmark (gated by -DrunLoadBenchmark=true) so
regressions show up in the regular CI matrix once they're enabled.