Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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) — 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, 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 whose stale answer would go out on the wire or overwrite a cache unconditionally (app-state sync key lookups, `messageSecret` reads, `get_devices`) are deliberately kept on `run` instead.
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Outdated
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
</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 instead of queueing behind `pool_size`'s single write permit, raising the ceiling on read concurrency during a write-behind flush. A handful of reads stay on the write queue by design because a stale answer would go out on the wire, fail an operation outright, or get promoted into a cache unconditionally — app-state sync key lookups, `messageSecret` reads, and `get_devices` among them. Widening `read_pool_size` past `0` now buys concurrency for the whole read surface, not just chat/message queries.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Split the read-pool guidance into concise sentences

Rewrite this note as several short, reader-focused sentences: it currently combines coverage, examples, performance effects, exceptions, and a summary in one very long sentence, while api/store.mdx:519 similarly extends an already long sentence. This makes the configuration guidance difficult to scan and violates the repository requirement to keep one idea per sentence.

AGENTS.md reference: AGENTS.md:L24-L25

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid claiming concurrency for the whole read surface

Replace “the whole read surface” with “most of the read surface” or similarly qualified wording. The same sentence explicitly states that app-state sync key, messageSecret, and get_devices reads remain on the write queue, so read_pool_size > 0 does not provide concurrency for the whole read surface and the conclusion can mislead readers sizing this pool.

Useful? React with 👍 / 👎.

</Note>
Comment thread
coderabbitai[bot] marked this conversation as resolved.

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

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