Skip to content

perf(data): stop query-debug diagnostics convoying the SQLite worker - #388

Merged
crs48 merged 2 commits into
mainfrom
claude/relaxed-saha-bf1c62
Jul 6, 2026
Merged

perf(data): stop query-debug diagnostics convoying the SQLite worker#388
crs48 merged 2 commits into
mainfrom
claude/relaxed-saha-bf1c62

Conversation

@crs48

@crs48 crs48 commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Problem

With xnet:query:debug enabled, the query-plan diagnostics path issued per query: EXPLAIN QUERY PLAN + PRAGMA schema_version + one PRAGMA index_info() per index (~29), each a separate round-trip to the single serial SQLite worker. A live capture (2026-07-05, post-#381, fast 8MB database, worker execMs 0-2ms) showed hundreds of index_info round-trips per boot; the aggregate convoy delayed real query results by 18-20s (loadDoc readMs 19637 with worker exec 0ms) while boot infra finished in 1.4s. Debug mode was distorting the very thing it measures.

Why #351's cache missed

The schema_version-keyed cache added in #351 is correct for sequential calls, but it only populates after a full build finishes. Boot fires dozens of debug-instrumented queries concurrently — every one checked the still-empty cache and enqueued its own full sqlite_master + 29× index_info build onto the serial worker. Hence identical index_info calls repeating dozens of times in the capture.

Fix

@xnetjs/sqlite (diagnostics.ts)

  • getIndexInfo now shares ONE in-flight probe+build across concurrent callers (the resolved cache stays keyed on PRAGMA schema_version).
  • The build itself is batched into a single statement via a pragma_index_info table-valued join (SQLite ≥ 3.16), with the per-index loop kept as a fallback for runtimes without table-valued pragmas. Cold diagnostic run = 2 worker round-trips, warm = 1.

@xnetjs/data (sqlite-adapter.ts)

  • Plan diagnostics are collected once per unique compiled SQL shape per session (bound values are ? params, so keyset pages share an entry) and served from a memo after that — instead of per execution. The memo is cleared when the adapter creates/drops an adaptive index (plans may change), skips caching failed collections, and is capped at 512 entries (IN-list binds mint one shape per list length).

Regression tests

  • Diagnostics layer: counts worker round-trips per diagnostic run — cold build exactly 2, warm 1, 25 concurrent calls = 2 total; fallback path; plus a real sql.js engine pass validating the batched join (unique/partial/multi-column/expression ordering + rebuild on DDL).
  • Adapter layer: one EXPLAIN QUERY PLAN per compiled SQL shape across sequential, value-varied, and 8-way concurrent executions, with plan diagnostics still present on every result.

Full suite: 924 files / 10,133 tests green locally. Changeset (patch @xnetjs/sqlite + @xnetjs/data) and changelog fragment (performance,devtools) included.

🤖 Generated with Claude Code

With xnet:query:debug enabled, plan diagnostics issued EXPLAIN QUERY PLAN
+ PRAGMA schema_version + one PRAGMA index_info per index (~29) as
separate round-trips per query on the single serial SQLite worker. The
#351 schema_version-keyed cache only populated after a build finished,
so concurrent boot queries all missed it and each enqueued its own full
build — hundreds of identical index_info round-trips convoying real
query results by 18-20s (2026-07-05 capture: loadDoc readMs 19637 with
worker execMs 0).

- getIndexInfo shares one in-flight probe+build across concurrent
  callers, and fetches all index metadata in ONE batched
  pragma_index_info join (per-index fallback for runtimes without
  table-valued pragmas): a cold diagnostic run is 2 round-trips, warm 1.
- The storage adapter memoizes plan diagnostics per unique compiled SQL
  shape per session (bound values are params, so keyset pages share an
  entry), cleared when adaptive indexes are created/dropped, capped at
  512 entries.
- Regression tests count worker round-trips per diagnostic run at the
  diagnostics layer, verify concurrent dedupe + fallback + a real
  sql.js engine pass, and assert one EXPLAIN per SQL shape at the
  adapter layer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: xNet Test <test@xnet.dev>
@crs48
crs48 temporarily deployed to pr-388 July 5, 2026 23:56 — with GitHub Actions Inactive
github-actions Bot added a commit that referenced this pull request Jul 6, 2026
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Preview removed for PR #388.

@crs48
crs48 temporarily deployed to pr-388 July 6, 2026 01:04 — with GitHub Actions Inactive
@crs48
crs48 merged commit 81fe1b1 into main Jul 6, 2026
15 of 16 checks passed
@crs48
crs48 deleted the claude/relaxed-saha-bf1c62 branch July 6, 2026 01:44
github-actions Bot added a commit that referenced this pull request Jul 6, 2026
crs48 added a commit that referenced this pull request Jul 6, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## @xnetjs/abuse@0.1.1

### Patch Changes

- Updated dependencies []:
  - @xnetjs/identity@0.1.1
  - @xnetjs/crypto@0.1.1
## @xnetjs/cli@0.0.5

### Patch Changes

- Updated dependencies
[[`2ab72a9`](2ab72a9)]:
  - @xnetjs/sqlite@0.1.1
  - @xnetjs/data@0.1.1
  - @xnetjs/plugins@0.1.1
  - @xnetjs/runtime@0.1.1
  - @xnetjs/sync@0.1.1
  - @xnetjs/identity@0.1.1
  - @xnetjs/crypto@0.1.1
  - @xnetjs/core@0.1.1
## @xnetjs/crypto@0.1.1

### Patch Changes

- Updated dependencies []:
  - @xnetjs/core@0.1.1
## @xnetjs/data@0.1.1

### Patch Changes

- [#388](#388)
[`2ab72a9`](2ab72a9)
Thanks [@crs48](https://github.com/crs48)! - Query-plan debug
diagnostics no longer convoy the SQLite worker. With
`xnet:query:debug` enabled, every query used to issue EXPLAIN QUERY PLAN
+
PRAGMA schema_version + one PRAGMA index_info per index as separate
serial
  worker round-trips — hundreds per boot, delaying real query results by
18-20s. `getIndexInfo` now dedupes concurrent callers onto one in-flight
  build and fetches all index metadata in a single batched
`pragma_index_info` join (with a per-index fallback for runtimes without
table-valued pragmas), and the storage adapter collects plan diagnostics
once per unique compiled SQL shape per session instead of per execution
  (invalidated when adaptive indexes are created or dropped).
- Updated dependencies
[[`2ab72a9`](2ab72a9)]:
  - @xnetjs/sqlite@0.1.1
  - @xnetjs/storage@0.1.1
  - @xnetjs/sync@0.1.1
  - @xnetjs/identity@0.1.1
  - @xnetjs/crypto@0.1.1
  - @xnetjs/core@0.1.1
## @xnetjs/data-bridge@0.1.1

### Patch Changes

- Updated dependencies
[[`2ab72a9`](2ab72a9)]:
  - @xnetjs/sqlite@0.1.1
  - @xnetjs/data@0.1.1
  - @xnetjs/sync@0.1.1
  - @xnetjs/core@0.1.1
## @xnetjs/history@0.1.1

### Patch Changes

- Updated dependencies
[[`2ab72a9`](2ab72a9)]:
  - @xnetjs/data@0.1.1
  - @xnetjs/sync@0.1.1
  - @xnetjs/core@0.1.1
## @xnetjs/identity@0.1.1

### Patch Changes

- Updated dependencies []:
  - @xnetjs/crypto@0.1.1
  - @xnetjs/core@0.1.1
## @xnetjs/plugins@0.1.1

### Patch Changes

- Updated dependencies
[[`2ab72a9`](2ab72a9)]:
  - @xnetjs/data@0.1.1
  - @xnetjs/abuse@0.1.1
  - @xnetjs/core@0.1.1
## @xnetjs/react@0.1.1

### Patch Changes

- Updated dependencies
[[`2ab72a9`](2ab72a9)]:
  - @xnetjs/data@0.1.1
  - @xnetjs/data-bridge@0.1.1
  - @xnetjs/history@0.1.1
  - @xnetjs/plugins@0.1.1
  - @xnetjs/runtime@0.1.1
  - @xnetjs/sync@0.1.1
  - @xnetjs/identity@0.1.1
  - @xnetjs/crypto@0.1.1
  - @xnetjs/core@0.1.1
## @xnetjs/runtime@0.1.1

### Patch Changes

- Updated dependencies
[[`2ab72a9`](2ab72a9)]:
  - @xnetjs/data@0.1.1
  - @xnetjs/data-bridge@0.1.1
  - @xnetjs/storage@0.1.1
  - @xnetjs/history@0.1.1
  - @xnetjs/plugins@0.1.1
  - @xnetjs/sync@0.1.1
  - @xnetjs/identity@0.1.1
  - @xnetjs/crypto@0.1.1
  - @xnetjs/core@0.1.1
## @xnetjs/sqlite@0.1.1

### Patch Changes

- [#388](#388)
[`2ab72a9`](2ab72a9)
Thanks [@crs48](https://github.com/crs48)! - Query-plan debug
diagnostics no longer convoy the SQLite worker. With
`xnet:query:debug` enabled, every query used to issue EXPLAIN QUERY PLAN
+
PRAGMA schema_version + one PRAGMA index_info per index as separate
serial
  worker round-trips — hundreds per boot, delaying real query results by
18-20s. `getIndexInfo` now dedupes concurrent callers onto one in-flight
  build and fetches all index metadata in a single batched
`pragma_index_info` join (with a per-index fallback for runtimes without
table-valued pragmas), and the storage adapter collects plan diagnostics
once per unique compiled SQL shape per session instead of per execution
  (invalidated when adaptive indexes are created or dropped).
## @xnetjs/storage@0.1.1

### Patch Changes

- Updated dependencies
[[`2ab72a9`](2ab72a9)]:
  - @xnetjs/sqlite@0.1.1
  - @xnetjs/crypto@0.1.1
  - @xnetjs/core@0.1.1
## @xnetjs/sync@0.1.1

### Patch Changes

- Updated dependencies []:
  - @xnetjs/identity@0.1.1
  - @xnetjs/crypto@0.1.1
  - @xnetjs/core@0.1.1
## @xnetjs/core@0.1.1


## xnet-cloud@0.0.3

### Patch Changes

- Updated dependencies []:
  - @xnetjs/cloud@0.0.1
  - @xnetjs/crypto@0.1.1
## @xnetjs/brain@0.0.4

### Patch Changes

- Updated dependencies
[[`2ab72a9`](2ab72a9)]:
  - @xnetjs/data@0.1.1
  - @xnetjs/vectors@0.0.1
## @xnetjs/comms@0.0.4

### Patch Changes

- Updated dependencies
[[`2ab72a9`](2ab72a9)]:
  - @xnetjs/data@0.1.1
  - @xnetjs/crypto@0.1.1
## @xnetjs/dashboard@0.0.4

### Patch Changes

- Updated dependencies
[[`2ab72a9`](2ab72a9)]:
  - @xnetjs/data@0.1.1
  - @xnetjs/plugins@0.1.1
  - @xnetjs/react@0.1.1
  - @xnetjs/social@0.0.4
## @xnetjs/labs@0.0.4

### Patch Changes

- Updated dependencies
[[`2ab72a9`](2ab72a9)]:
  - @xnetjs/data@0.1.1
  - @xnetjs/plugins@0.1.1
## @xnetjs/licenses@0.0.4

### Patch Changes

- Updated dependencies []:
  - @xnetjs/crypto@0.1.1
## @xnetjs/maps@0.0.4

### Patch Changes

- Updated dependencies
[[`2ab72a9`](2ab72a9)]:
  - @xnetjs/data@0.1.1
## @xnetjs/server@0.0.3

### Patch Changes

- Updated dependencies
[[`2ab72a9`](2ab72a9)]:
  - @xnetjs/data@0.1.1
  - @xnetjs/data-bridge@0.1.1
  - @xnetjs/identity@0.1.1
  - @xnetjs/crypto@0.1.1
## @xnetjs/social@0.0.4

### Patch Changes

- Updated dependencies
[[`2ab72a9`](2ab72a9)]:
  - @xnetjs/data@0.1.1
  - @xnetjs/crypto@0.1.1
## @xnetjs/unreal@0.0.4

### Patch Changes

- Updated dependencies
[[`2ab72a9`](2ab72a9)]:
  - @xnetjs/data@0.1.1
## xnet-desktop@0.1.1

Desktop shell release riding the @xnetjs/core 0.1.1 train.
Desktop-specific changes are not tracked here; see the core packages'
changelogs for what shipped.
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.

1 participant