Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/store.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,7 @@ impl SharedSqlite {
```

<Note>
`read` takes a permit from a separate reader pool, sized by [`SqliteStoreConfig::read_pool_size`](/concepts/storage#memory-and-thread-tuning-sqlitestoreconfig) — so a burst of reads can run alongside a pending write instead of queueing behind it on `run`'s single permit. If `read_pool_size` is `0` (the default) or the underlying connection isn't WAL, `read` falls back to queueing on the same permit as `run` — it's always safe to call. Wrapping the closure in a deferred transaction also means a `read` that issues more than one statement (e.g. resolve a chat's identity keys, then query by them) sees one consistent snapshot across all of them, rather than possibly straddling a write that commits in between. Prefer `read` over `run` for anything that only queries — [`whatsapp-rust-chat-store`](/api/chat-store)'s query paths do.
`read` takes a permit from a separate reader pool, sized by [`SqliteStoreConfig::read_pool_size`](/concepts/storage#memory-and-thread-tuning-sqlitestoreconfig). A burst of reads can then run alongside a pending write instead of queueing behind `run`'s write-path permits. If `read_pool_size` is `0` (the default) or the underlying connection isn't WAL, `read` falls back to queueing on the same permits as `run` — it's always safe to call. Wrapping the closure in a deferred transaction also means a `read` that issues more than one statement (e.g. resolve a chat's identity keys, then query by them) sees one consistent snapshot across all of them, rather than possibly straddling a write that commits in between. Prefer `read` over `run` for anything that only queries. [`whatsapp-rust-chat-store`](/api/chat-store)'s query paths do, and so does most of `SqliteStore`'s own `SignalStore`/`AppSyncStore`/`ProtocolStore`/`DeviceStore` surface as of [whatsapp-rust#1222](https://github.com/oxidezap/whatsapp-rust/pull/1222) — session, identity, sender-key, and pre-key lookups among them. A handful of reads are deliberately kept on `run` instead: a stale answer for these would go out on the wire, fail an operation outright, or overwrite a cache unconditionally (app-state sync key lookups, `messageSecret` reads, `get_devices`).
</Note>

### Features
Expand Down
4 changes: 4 additions & 0 deletions concepts/storage.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -965,6 +965,10 @@ let backend = Arc::new(
`SqliteStore::new` and `SqliteStore::new_for_device` are unchanged — they delegate to `SqliteStoreConfig::default()` internally. Raising `pool_size` above `1` makes *writes* concurrent, which SQLite does not want — two deferred transactions that both read and then write deadlock on the upgrade, and `busy_timeout` cannot break it. `read_pool_size` (set via the `with_read_pool_size` builder, or the struct field directly) is the knob for concurrency instead: it reserves connections purely for reads, which WAL lets run alongside a pending write without contending for the write lock. Left at `0`, reads queue on the same permit as writes exactly as before this knob existed.
</Note>

<Note>
As of [whatsapp-rust#1222](https://github.com/oxidezap/whatsapp-rust/pull/1222), `read_pool_size` also covers most of `SqliteStore`'s own `SignalStore`/`AppSyncStore`/`ProtocolStore`/`DeviceStore` reads, not just [`whatsapp-rust-chat-store`](/api/chat-store)'s queries. Session, identity, sender-key, and pre-key lookups on the decrypt path — `get_session`, `load_identity`, `get_sender_key`, `load_prekey`, and similar — now run on the reader pool. Before this change they queued behind `pool_size`'s write-path permits even with `read_pool_size` set, so this raises the ceiling on read concurrency during a write-behind flush. A handful of reads stay on the write queue by design: app-state sync key lookups, `messageSecret` reads, and `get_devices` among them, since a stale answer for these would go out on the wire, fail an operation outright, or get promoted into a cache unconditionally. Widening `read_pool_size` past `0` now buys concurrency for most of the read surface, not just chat/message queries.
</Note>
Comment thread
coderabbitai[bot] marked this conversation as resolved.

**Multi-session measurements (from PR #926):**

| | before | after (default config) |
Expand Down