perf: sqlite prepare cached#7414
Conversation
…e per-connection statement-cache capacity
There was a problem hiding this comment.
Pull request overview
This PR improves SQLite query performance by switching hot-path queries to use rusqlite’s prepared-statement cache (prepare_cached) and increasing per-connection statement-cache capacity to reduce repeated SQL parsing.
Changes:
- Use
prepare_cachedin shared SQLite query helpers and several frequently-called read paths (Clarity VM headers queries, MARF squashed-block lookups, SortDB tip lookups). - Increase prepared-statement cache capacity on long-lived connections (
stackslib: 200; Clarity side-store: 32). - Add a changelog fragment documenting the performance improvement.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| stackslib/src/util_lib/db.rs | Switch generic query_* helpers and a MARF lookup to prepare_cached; bump per-connection statement-cache capacity to 200 in sqlite_open(). |
| stackslib/src/clarity_vm/database/mod.rs | Cache dynamically-built header-table SELECT statements via prepare_cached for repeated access patterns. |
| stackslib/src/chainstate/stacks/index/trie_sql.rs | Use prepare_cached for MARF squashed-block and block-id/hash lookup queries. |
| stackslib/src/chainstate/burn/db/sortdb.rs | Use prepare_cached for canonical tip lookup queries in SortDB. |
| clarity/src/vm/database/sqlite.rs | Use prepare_cached for side-store reads/writes; bump statement-cache capacity to 32. |
| changelog.d/sqlite-prepare-cached.changed | Document statement caching + cache-capacity increase. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Coverage Report for CI Build 29319771434Coverage increased (+0.9%) to 86.564%Details
Uncovered Changes
Coverage Regressions11964 previously-covered lines in 165 files lost coverage.
Coverage Stats
💛 - Coveralls |
|
|
||
| // rusqlite's default statement cache holds only 16 entries; bump it so the | ||
| // per-read `prepare_cached` statements in this module are never LRU-evicted. | ||
| conn.set_prepared_statement_cache_capacity(32); |
There was a problem hiding this comment.
nit: even if used in one place we could define 32 (and 200) as named constants with related docstring.
maybe we could even use just one constants (the bigger), but I'm fine keeping them separated due to the working set.
| ) | ||
| // `column_name`/`table_name` come from small fixed sets (7 as of this | ||
| // writing) times the two header tables, far below the connection's | ||
| // 200-entry statement-cache capacity. |
There was a problem hiding this comment.
nit: this comment looks like can easily drift if we change the cache configuration
Description
Profiling a mainnet follower during catch-up (on top of #7300) showed the SQLite SQL parser (
resolveExprStep,sqlite3RunParser,yy_reduce) as the largest remaining CPU sink: ~5.3% of chains-coordinator self-time. We re-compile the same SQL strings millions of times (the Clarity side store on every data read, the MARF on every trie-cache miss, and thequery_*helpers inutil_lib/db.rsfor every caller).rusqlite ships a per-connection prepared-statement cache,
prepare_cached, which compiles once and reuses the bytecode. We just don't use it on these paths, and its default capacity (16) would LRU-thrash under the node's breadth of statements.This PR routes the hot read paths through the cache and raises the per-connection cache capacity to 200 in
sqlite_open.Measured impact - A/B on a mainnet follower (identical chainstate copies, same start height, 10-minute windows):
I also run a first iteration (that wasn't caching everything this one is caching) with stacks-bench in cylewitruk#5 (comment) that measured it ~4% faster during the benchmark.
Applicable issues
Additional info (benefits, drawbacks, caveats)
Checklist
docs/property-testing.md)changelog.d/README.md)rpc/openapi.yamlfor RPC endpoints,event-dispatcher.mdfor new events)clarity-benchmarkingrepo