perf(shared): cached SQLite statements no longer hold large parameters#19305

merged4
main
perf/sqlite-large-binding-retention-takeover
opened24d agobyTtheo
View on GitHubTwo-way sync enabledPR last imported 18d ago

Takeover of #17785 by @tris203, which conflicted with the reader worker from #19226. Their two commits are kept as authored. All credit for the diagnosis and the fix goes to them.

Problem

The SQLite client caches up to 200 prepared statements. SQLite keeps a copy of a statement's last text and blob parameters until it is bound again or finalized, and node:sqlite cannot clear them. So each cached statement pins the last payload it ran with, as native memory that shows in RSS but not in the heap. The reader worker added in #19226 has its own statement cache, with no TTL, so it has the same problem.

Fix

  • From #17785: an execution that binds more than 64 KiB of text and blob data runs on a throwaway statement instead of a cached one. Small executions use the cache as before. The values path now sets its integer mode from the caller's SafeIntegers, so a throwaway statement reads integers the same way a cached one does.
  • New here: reader worker requests apply the same rule, and ask the worker for an uncached statement when the parameters are large.
  • New here: the size check reads only data properties of a named-parameter object, so getters still run once, and it counts raw ArrayBuffer blobs.

Proof

  • Repro on Node 24.21, one process per case: 32 cached statements run 4 times with 1 MiB strings held +34.8 MiB RSS. The same work on throwaway statements held +2.0 to 2.4 MiB.
  • vp test run src/nodeSqliteClient.test.ts: 25 passed. With main's nodeSqliteClient.ts, the new tests fail. Each review fix also has a test that fails without it. With only the worker line reverted, the new worker test fails ([true, true] instead of [true, false]).
  • tsc-rs --noEmit in packages/shared, plus vp lint and vp fmt --check on both files: clean.

Co-authored-by: tris203 admin@snappeh.com

Created with Claude Opus 5.5 in Claude Code, running in T3 Code.

šŸ¤– Generated with Claude Code

Checks

25 successful Ā· 8 skipped
All checks passed
332ddf6
Synced from GitHub

Approvability

Verdict: Approved at 332ddf6

Macroscope's review found this PR approvable — This is a focused SQLite memory-retention fix that bypasses statement caching only for large or unmeasurable bindings while preserving normal caching and query behavior. Writer and reader-worker paths are covered by targeted tests, with no schema, deployment, security, billing, default, or static-analysis changes.

You can add or adjust custom eligibility rules. Learn more.

Added 4 commits

  • perf(shared): reader worker no longer caches statements with large parameters36f5f64
  • perf(shared): cached SQLite statements no longer hold large parameters57c8429
  • test(shared): detached view test no longer depends on the Node version6f60b46
  • fix(shared): large-binding check no longer runs getters or misses raw buffers332ddf6
Synced from GitHub

Review in Change Stack →

No actionable comments were generated in the recent review. šŸŽ‰

ā„¹ļø Recent review info
āš™ļø Run configuration
  • Configuration used: Path: .coderabbit.config.ts
  • Review profile: CHILL
  • Plan: Team
  • Run ID: 2f577f94-4291-4cce-9062-0c05493bac1a
šŸ“„ Commits

Reviewing files that changed from the base of the PR and between 173a97097659be5f77ab2c05cf5dfe84be3eb094 and 332ddf66ccd6194339d8de25563733c36096111f.

šŸ“’ Files selected for processing: 2
  • packages/shared/src/nodeSqliteClient.test.ts
  • packages/shared/src/nodeSqliteClient.ts

Included review availability: This review used your included allowance. 6 included reviews remain after this review. Your included PR review attempts over the past 7 days set your current allowance at 10 reviews per hour.

šŸ“ Walkthrough

The SQLite client now bypasses statement caches for oversized or unmeasurable bindings. Value-array execution also applies the current safe-integer setting.

Changes: SQLite binding and execution

Layer / File(s)Summary
Measure bindings and select statements
packages/shared/src/nodeSqliteClient.ts, packages/shared/src/nodeSqliteClient.test.ts
Bindings over 64 KiB or with sizes that cannot be measured use uncached statements in the writer and reader paths. Tests cover large and small parameters, detached views, and parameter getters.
Apply value-array safe-integer settings
packages/shared/src/nodeSqliteClient.ts, packages/shared/src/nodeSqliteClient.test.ts
Value-array execution sets safe-integer mode from the current fiber context. Tests check number and bigint results for small and large text parameters.

Priority: ā¬‡ļø Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Suggested reviewers: juliusmarminge

Merge Risk: ⚪ Minimal · up to 332dd

The changed SQLite paths have no identified issue requiring a fix before merge.

Pre-merge checks: Passed 3 Ā· Failed 1

Failed Failed checks: 1 warning

Check nameStatusExplanationResolution
Description checkWarningThe description explains the problem, fix, affected components, and verification results. It does not include the required Scope and approval section or provide explicit maintainer approval, triaged i…Add a Scope and approval section. Link the triaged issue or approval discussion with the relevant maintainer approval comment, or explain why this focused fix qualifies for an exemption.
Passed Passed checks: 3 passed
Check nameStatusExplanation
Linked Issues checkPassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes checkPassedCheck skipped because no linked issues were found for this pull request.
Title checkPassedThe title clearly and concisely describes the main change: preventing cached SQLite statements from retaining large parameters.
Full details: Description check

Explanation

The description explains the problem, fix, affected components, and verification results. It does not include the required Scope and approval section or provide explicit maintainer approval, triaged issue details, or a valid exemption rationale.

  • Fix all pre-merge checks with AI
✨ Finishing Touches
šŸ“ Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix Ā· Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

Synced from GitHub

Thread transfer impact

āœ… Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire5.0 KiB5.0 KiB0 B (0.0%)6.8 KiBāœ…
CodexThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiBāœ…
CodexLive turn WebSocket wire1.2 KiB1.2 KiB0 B (0.0%)2.0 KiBāœ…
CodexLive turn WebSocket decoded20.9 KiB20.9 KiB0 B (0.0%)29.3 KiBāœ…
CodexLive turn messages220 (0.0%)8āœ…
ClaudeTotal thread wire5.0 KiB5.0 KiB0 B (0.0%)6.8 KiBāœ…
ClaudeThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiBāœ…
ClaudeLive turn WebSocket wire1.2 KiB1.2 KiB0 B (0.0%)2.0 KiBāœ…
ClaudeLive turn WebSocket decoded21.2 KiB21.2 KiB0 B (0.0%)29.3 KiBāœ…
ClaudeLive turn messages220 (0.0%)8āœ…

Baseline: 173a970 Ā· PR result: 332ddf6 Ā· Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.