Skip to content

[codex] add opt-in statement cache#1387

Merged
mattn merged 7 commits intomasterfrom
codex/stmt-cache
Apr 8, 2026
Merged

[codex] add opt-in statement cache#1387
mattn merged 7 commits intomasterfrom
codex/stmt-cache

Conversation

@mattn
Copy link
Copy Markdown
Owner

@mattn mattn commented Apr 7, 2026

This change adds an opt-in connection-local statement cache to go-sqlite3, enabled via the DSN parameter _stmt_cache_size=N.

The motivation is that, in this benchmark, the main gap in mattn/go-sqlite3 was not cgo alone but repeated prepare/finalize on hot QueryRow paths. Reusing prepared statements at the driver level closes most of that gap without requiring application code to switch to explicit db.Prepare(...).

Implementation details:

  • add _stmt_cache_size DSN parsing
  • keep a small per-connection cache of prepared statements
  • reuse cached statements from internal Query / Exec paths
  • reset and return statements to the cache on close
  • finalize cached statements when the connection closes
  • leave default behavior unchanged unless _stmt_cache_size is set

Benchmark workload:

  • read-only point lookup
  • query: SELECT id, name, hp, attack FROM monster WHERE id = ?
  • 1000-row SQLite database
  • parallel cases:
    • P1: about 16 goroutines
    • P10: about 160 goroutines
    • P100: about 1600 goroutines

Benchmark commands used:

For mattn/go-sqlite3:

go test -run '^$' -bench '^BenchmarkFair(Cached|Prepared|)_C(1|4)_' -benchtime=200ms -count=1

For modernc:

go test -run '^$' -bench '^BenchmarkFair(Prepared)?_' -benchtime=200ms -count=1

Benchmark results on 2026-04-07 (Ryzen 7 7735HS, WSL2):

Condition mattn before mattn with stmt cache modernc
Seq 10.1 us 7.7 us 15.0 us
P1 42.2 us 17.7 us 14.8 us
P10 42.5 us 15.7 us 18.3 us
P100 45.4 us 17.7 us 212.9 us

In this workload, the cached version improves mattn substantially:

  • Seq: about 1.3x faster
  • P1: about 2.4x faster
  • P10: about 2.7x faster
  • P100: about 2.6x faster

With the cache enabled, mattn is fastest in 3 of 4 measured cases here (Seq, P10, P100), while modernc remains slightly faster in P1.

This is intentionally opt-in because statement caching changes connection behavior and should be evaluated conservatively in real applications.

@mattn mattn marked this pull request as ready for review April 7, 2026 05:09
@mattn mattn requested a review from rittneje April 7, 2026 05:18
Comment thread sqlite3.go Outdated
Comment thread sqlite3.go Outdated
Comment thread sqlite3.go Outdated
Comment thread sqlite3.go Outdated
Comment thread sqlite3.go Outdated
Comment thread sqlite3.go Outdated
Comment thread sqlite3.go
mattn added 6 commits April 8, 2026 13:38
stmtCacheSize is immutable after connection open, so checking it
before the lock avoids mutex overhead when cache is not enabled.
When stmtCacheSize <= 0, stmtCacheCount >= stmtCacheSize is always
true, so the explicit check is unnecessary.
Finalize all cached statements even if one fails. Leaving a
finalized statement in the cache map would be a use-after-finalize
bug per SQLite documentation.
prepareWithCache now delegates to prepare and sets cacheKey
afterward, removing the useCache boolean parameter.
This avoids an unnecessary reset when the cache is full, guarantees
a statement cannot enter the cache without being reset/cleared, and
fixes a leak where sqlite3_finalize was not called when reset failed.
Clarify that each connection in the sql.DB pool maintains its own
independent statement cache.
@mattn mattn merged commit 5df13a0 into master Apr 8, 2026
20 checks passed
@mattn
Copy link
Copy Markdown
Owner Author

mattn commented Apr 8, 2026

@rittneje Thank you

@mattn mattn deleted the codex/stmt-cache branch April 8, 2026 13:29
@rittneje
Copy link
Copy Markdown
Collaborator

rittneje commented Apr 9, 2026

@mattn Reflecting on this more, it seems like this approach doesn't quite account for evicting stuff from the cache. For example, let's say the cache size is 5. My first 5 queries might be one-time only, and they will all be cached forever. And then the 6th query - which maybe gets run frequently - never gets cached.

@mattn
Copy link
Copy Markdown
Owner Author

mattn commented Apr 11, 2026

@rittneje Right. I'll implement LRU cache.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants