Skip to content

feat(sqlite): parallel SQLite readers in Electron (WAL + worker_threads)#282

Merged
crs48 merged 5 commits into
mainfrom
claude/0230-parallel-sqlite-readers-in-electron-wal-and-work
Jun 26, 2026
Merged

feat(sqlite): parallel SQLite readers in Electron (WAL + worker_threads)#282
crs48 merged 5 commits into
mainfrom
claude/0230-parallel-sqlite-readers-in-electron-wal-and-work

Conversation

@crs48

@crs48 crs48 commented Jun 26, 2026

Copy link
Copy Markdown
Owner

Implements exploration 0230.

Why

0228 found the browser can't run SQLite readers in parallel: opfs-sahpool holds an exclusive handle, so a second connection falls back to in-memory. That wall is browser-only. Electron uses native better-sqlite3 already in WAL mode, where SQLite supports one writer concurrent with many readers — each on its own connection. The bottleneck wasn't SQLite; it was that the whole desktop data layer ran on one synchronous thread.

What landed

  • Scheduler on the Electron adapter (scheduler, default on) — interactive reads are served ahead of a queued write/import burst; transaction-internal ops run inline (today's semantics preserved).
  • Read/write connection split (readonlyReadConnection) — a read-only secondary connection so plain reads don't contend with write locks.
  • Reader-thread pool (readerPoolSize: 'auto') — read-only worker_threads better-sqlite3 connections that serve heavy reads (FTS / aggregates / big scans) in parallel, with least-busy dispatch, an isHeavyRead heuristic so cheap reads stay inline, and graceful fallback to the inline connection if a worker can't boot.
  • Cooperative yielding in applyNodeBatch — chunked atomic transactions with setImmediate between chunks so a long import no longer monopolizes the data-process thread.
  • DiagnosticsgetDiagnostics() / getWalStats() / checkpointWal() on the adapter, plus a sqlite:diagnostics IPC; read-your-writes window (readYourWritesWindowMs).
  • App wiring — the desktop data process enables the read-only connection + auto-sized pool and exposes the diagnostics seam.

Proof

  • electron-reader-pool.test.ts spawns real worker_threads and shows two heavy reads complete in ≈ max, not sum (ratio ~1.00) — genuine parallelism.
  • Interactive-read p95 stays sub-ms under a sustained write burst; applyNodeBatch yields so a read interleaves mid-import; cheap reads stay inline; WAL checkpoint bounds growth.
  • Web path untouched; worker-scheduler.ts reverted to main (no changes) — the shared scheduler tests still pass.

Deferred (gated on environment)

Three validation items need a signed/packaged desktop build + a soak run, which can't be exercised in CI here — so the exploration stays unchecked on those. The reader pool is best-effort, so a packaging failure degrades to inline reads rather than breaking the app.

🤖 Generated with Claude Code

xNet Test added 5 commits June 26, 2026 05:45
… Electron

Front the Electron better-sqlite3 adapter with the priority scheduler, add an
optional read-only secondary connection, a read-only worker_threads reader pool
for heavy parallel reads (the browser can't — opfs-sahpool holds an exclusive
handle), cooperative yielding in applyNodeBatch, and WAL/scheduler diagnostics
(exploration 0230).
Turn on the read-only secondary connection and an auto-sized reader-thread pool
for the desktop data process, and expose a sqlite:diagnostics IPC returning
scheduler depth, reader-pool occupancy, and WAL growth (exploration 0230).
@crs48 crs48 temporarily deployed to pr-282 June 26, 2026 13:29 — with GitHub Actions Inactive
@github-actions

github-actions Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Preview removed for PR #282.

github-actions Bot added a commit that referenced this pull request Jun 26, 2026
@crs48 crs48 merged commit d3341fb into main Jun 26, 2026
12 of 13 checks passed
@crs48 crs48 deleted the claude/0230-parallel-sqlite-readers-in-electron-wal-and-work branch June 26, 2026 13:39
github-actions Bot added a commit that referenced this pull request Jun 26, 2026
* interleave. Whole-batch atomicity is traded for yield points — safe here
* because node-batch writes are idempotent LWW upserts.
*/
async applyNodeBatch(input: SQLiteNodeBatchApplyInput): Promise<SQLiteNodeBatchApplyResult> {
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