atagen

matrix-rust-sdk

public
Star0
Watch0

Commit cd3986c6d5e2

tarball

atagen <boss@atagen.co>2026-10-28 02:46 UTC

3 files changed+47-24
indexeddb: save a batch of events in one transaction, and read chunk metadata read-only

`RoomEventCache::save_events` stored each event in its own readwrite transaction on the events store, so a page of 100 thread roots queued every other event cache transaction behind 100 commits, and a soak saw single sync writes take 100 to 700ms and some 5s while the room state lock was held. `EventCacheStore::save_events` now defaults to the per-event loop and IndexedDB overrides it with one transaction. `load_all_chunks` and `load_all_chunks_metadata` only read, so they no longer take readwrite transactions that serialise with every write.
Modified file: crates/matrix-sdk-base/src/event_cache/store/traits.rs+13 additions
@@ -176,6 +176,15 @@ pub trait EventCacheStore: AsyncTraitDeps {
176176 /// without causing an error.
177177 async fn save_event(&self, room_id: &RoomId, event: Event) -> Result<(), Self::Error>;
178178
179+ /// Save several events as [`Self::save_event`] does, in as few store
180+ /// transactions as the store allows.
181+ async fn save_events(&self, room_id: &RoomId, events: Vec<Event>) -> Result<(), Self::Error> {
182+ for event in events {
183+ self.save_event(room_id, event).await?;
184+ }
185+ Ok(())
186+ }
187+
179188 /// Close the store, releasing all held resources (database connections,
180189 /// file descriptors, file locks).
181190 ///
@@ -305,6 +314,10 @@ impl<T: EventCacheStore> EventCacheStore for EraseEventCacheStoreError<T> {
305314 self.0.save_event(room_id, event).await.map_err(Into::into)
306315 }
307316
317+ async fn save_events(&self, room_id: &RoomId, events: Vec<Event>) -> Result<(), Self::Error> {
318+ self.0.save_events(room_id, events).await.map_err(Into::into)
319+ }
320+
308321 async fn close(&self) -> Result<(), Self::Error> {
309322 self.0.close().await.map_err(Into::into)
310323 }
Modified file: crates/matrix-sdk-indexeddb/src/event_cache_store/mod.rs+33 additions−21 deletions
@@ -313,7 +313,7 @@ impl EventCacheStore for IndexeddbEventCacheStore {
313313
314314 let transaction = self.transaction(
315315 &[keys::LINKED_CHUNKS, keys::GAPS, keys::EVENTS],
316- IdbTransactionMode::Readwrite,
316+ IdbTransactionMode::Readonly,
317317 )?;
318318
319319 let mut raw_chunks = Vec::new();
@@ -348,7 +348,7 @@ impl EventCacheStore for IndexeddbEventCacheStore {
348348
349349 let transaction = self.transaction(
350350 &[keys::LINKED_CHUNKS, keys::EVENTS, keys::GAPS],
351- IdbTransactionMode::Readwrite,
351+ IdbTransactionMode::Readonly,
352352 )?;
353353
354354 let mut raw_chunks = Vec::new();
@@ -611,29 +611,41 @@ impl EventCacheStore for IndexeddbEventCacheStore {
611611 room_id: &RoomId,
612612 event: Event,
613613 ) -> Result<(), IndexeddbEventCacheStoreError> {
614+ self.save_events(room_id, vec![event]).await
615+ }
616+
617+ #[instrument(skip(self, events), fields(count = events.len()))]
618+ async fn save_events(
619+ &self,
620+ room_id: &RoomId,
621+ events: Vec<Event>,
622+ ) -> Result<(), IndexeddbEventCacheStoreError> {
614623 let _timer = timer!("method");
615624
616- let Some(event_id) = event.event_id() else {
617- error!(%room_id, "Trying to save an event with no ID");
618- return Ok(());
619- };
625+ // one readwrite transaction per event queued every other event cache transaction behind a
626+ // page of them, a hundred commits long
620627 let transaction = self.transaction(&[keys::EVENTS], IdbTransactionMode::Readwrite)?;
621-
622- let mut events = transaction
623- .get_events_by_room(room_id, &event_id)
624- .await?
625- .into_iter()
626- .map(|e| e.with_content(event.clone()))
627- .collect::<Vec<types::Event>>();
628- if events.is_empty() {
629- events.push(types::Event::OutOfBand(OutOfBandEvent {
630- linked_chunk_id: LinkedChunkId::Room(room_id).to_owned(),
631- content: event,
632- position: (),
633- }));
634- }
635628 for event in events {
636- transaction.put_event(&event).await?;
629+ let Some(event_id) = event.event_id() else {
630+ error!(%room_id, "Trying to save an event with no ID");
631+ continue;
632+ };
633+ let mut stored = transaction
634+ .get_events_by_room(room_id, &event_id)
635+ .await?
636+ .into_iter()
637+ .map(|e| e.with_content(event.clone()))
638+ .collect::<Vec<types::Event>>();
639+ if stored.is_empty() {
640+ stored.push(types::Event::OutOfBand(OutOfBandEvent {
641+ linked_chunk_id: LinkedChunkId::Room(room_id).to_owned(),
642+ content: event,
643+ position: (),
644+ }));
645+ }
646+ for event in stored {
647+ transaction.put_event(&event).await?;
648+ }
637649 }
638650 transaction.commit().await?;
639651 Ok(())
Modified file: crates/matrix-sdk/src/event_cache/caches/mod.rs+1 additions−3 deletions
@@ -65,9 +65,7 @@ pub(super) async fn save_events(
6565 let room_id = room_id.clone();
6666 async move {
6767 debug!(%room_id, count, "saving events to the store");
68- for event in events {
69- store.save_event(&room_id, event).await?;
70- }
68+ store.save_events(&room_id, events).await?;
7169 Result::Ok(())
7270 }
7371 });