Commit Graph
2 Commits
Author SHA1 Message Date
Erik JohnstonandClaude Opus 5.5 47367a14a2 Make the Rust event cache EventsWorkerStore's event cache
`EventsWorkerStore._get_event_cache` is now the Rust `EventCache` that the
sync handler and state resolution read, so each event is held once.

The cache keeps Rust `CachedEvent` values. Python gets them through
`RustEventCache`, a `RustCache` subclass with the `AsyncLruCache` names the
rest of Synapse uses, and each hit wraps the cached event in a new `Event`
object. The wrapper shares the cached event's internal metadata, unsigned
data and signatures, so a change Python makes, such as adding
`prev_content` or flipping `outlier`, lands in the one copy the Rust
readers see. Python's `_event_ref` weak map is checked before the cache,
so while anything holds an event, a lookup returns that same object, as
before. A hit there marks the cache entry as read, and puts the event back
if it was evicted.

- The cache is sized from `caches.event_cache_size` and named
  `*getEvent*`, so its metrics and `per_cache_factors` keep their names.
  The Rust loader now counts its misses too.
- Invalidating an event drops its chain position as well, and a room
  purge drops the room's events through the room index, plus the chain
  cover and state group caches, as `RustStore.invalidate_event` and
  `clear_event_cache` did. Those two calls are gone.
- `EventsWorkerStore` and `RustWorkerStore` run their `__init__` bodies
  in a different order in `DataStore` and `GenericWorkerStore`, as they
  sit differently in each MRO. So whichever runs first builds the
  `RustStore`. A datastore without `RustWorkerStore`, such as the port
  script's, gets a standalone `RustEventCache`.
- The external cache stubs (`get_external`, `set_external`,
  `_invalidate_async_get_event_cache` and its room variant) are dropped.
  They were documented no-ops.
- The sync shadow diff still ignores `prev_content` on state events.
  Python keeps the objects it holds after the cache evicts them, so a Rust
  reader can still load a second copy without it. Trial's cache of one
  event makes that routine.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VpBQxvqZTUvHCMm3u6CR7J
2026-10-15 12:14:54 +00:00
Erik JohnstonandClaude Fable 5.1 f7c9f55d8f Add the Rust sync store
The queries of the per-room sync loop, on the native Postgres driver, each
a port of the store method it is named after. A `SyncReader` is made per
request and borrows a pooled connection for one group of statements at a
time, so that a sync worker with hundreds of requests in flight does not
pin a connection per room; a group that needs one snapshot runs in a
`REPEATABLE READ READ ONLY` transaction. State groups are loaded whole,
with the recursive CTE sent as one simple query per batch, and cached as
full maps.

The caches that copy a `@cached` function (rooms for user, room summary,
partial state rooms, erased users, retention policies, event to state
group, unread counts) live on the Rust store and are attached to the
Python cache each names by the new `RustWorkerStore`, which also takes
over building the Rust store. Unread counts are invalidated by room as
well as by room and user, as the Python tree cache is.

`tests/storage/test_sync_store.py` runs every query and the Python
method against the same database, with multi-writer tokens, redactions
of every kind, and the summary rotation of the unread counts.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0194eNQP5weBtVMg8NKtc6VU
2026-10-14 15:11:51 +00:00