Skip to content

perf(orchestration): remove mutation receipt capacity scans - #13647

Merged
brennanb2025 merged 1 commit into
mainfrom
brennanb2025/perf-mutation-ledger-capacity
Aug 10, 2026
Merged

brennanb2025 merged 1 commit into
mainfrom
brennanb2025/perf-mutation-ledger-capacity

Conversation

@brennanb2025

Copy link
Copy Markdown
Contributor

Summary

  • add a schema-v26 partial covering index for completed-receipt retention and oldest-first pruning
  • replace per-mutation table counts with an exact trigger-maintained ledger count that remains correct for concurrent connections and writes from older binaries
  • prune 64 completed receipts at the high-water mark to amortize cleanup while preserving the 10,000-row hard cap, 30-day retention, pending-receipt fail-closed behavior, and replay ordering

Performance

Reproduction:

node tests/tools/benchmarks/mutation-receipt-capacity-bench.mjs --iterations 64 --payload-bytes 9500

On a 102,776,832-byte (98.02 MiB), 10,000-row WAL fixture:

Capacity path Median p95 Max 64 mutations
Previous full scans/sort 32.620 ms 41.840 ms 51.255 ms 2,174.174 ms
Indexed/count-ledger path 0.031 ms 0.045 ms 0.606 ms 2.657 ms

That is a 1,055x median speedup and 818x total reduction in this run. EXPLAIN QUERY PLAN regression coverage verifies the retention seek and oldest-completed walk use idx_mutation_receipts_completed_updated without a temporary B-tree.

Validation

  • orchestration database suite: 320 passed, 2 skipped
  • focused migration/capacity suite: 13 passed
  • pnpm run typecheck:node
  • pnpm lint
  • max-lines ratchet: no new bypasses
  • full unit run: 49,267 passed, 99 skipped; 14 unrelated inherited-agent-environment failures remained in three PTY/relay test files

Electron evidence

Launched this checkout through REMOTE_DEBUGGING_PORT=9336 node config/scripts/run-electron-vite-dev.mjs, attached only through CDP, and verified the visible app was rendered with zero console errors. The automation identity reported devRepoRoot as /Users/brennanbenson/orca/workspaces/orca/perf-mutation-ledger-capacity; the live database reported schema v26, both count triggers, the ledger row, and the covering-index query plan.

Visible Orca dev instance

Residual risk

The additive migration performs one index-build scan on an existing receipt table. Older binaries can continue operating against the additive schema and keep the counter exact through database triggers, but they retain the slower capacity query until updated.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Mutation receipt capacity management moved into a shared module. Schema version 26 adds a completed-receipt index, a count ledger, and maintenance triggers. Capacity enforcement expires old completed receipts, prunes older records in batches, and raises OrchestrationError when unresolved receipts fill the limit. Orchestration receipt creation uses the shared helper. Tests cover migrations, query plans, pruning, ledger consistency, and multiple connections. A benchmark compares legacy and optimized pruning.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description explains the implementation and testing, but it omits the required Screenshots, AI Review Report, Security Audit, and Notes sections. Add all missing template sections and document the AI review, security audit, platform compatibility checks, visual-change status, and follow-up notes.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: removing mutation-receipt capacity scans to improve orchestration performance.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 17f90707-1ddd-4a4b-881c-492fb55be813

📥 Commits

Reviewing files that changed from the base of the PR and between c0c893d and 2b7792f.

📒 Files selected for processing (5)
  • src/main/runtime/orchestration/db.ts
  • src/main/runtime/orchestration/mutation-receipt-capacity.test.ts
  • src/main/runtime/orchestration/mutation-receipt-capacity.ts
  • src/main/runtime/orchestration/orchestration-db-retention-pagination.test.ts
  • tests/tools/benchmarks/mutation-receipt-capacity-bench.mjs

Comment on lines +158 to +187
try {
const legacyPath = join(fixtureDir, 'legacy.db')
const optimizedPath = join(fixtureDir, 'optimized.db')
const legacyDb = createFixture(legacyPath, options.payloadBytes, false)
const optimizedDb = createFixture(optimizedPath, options.payloadBytes, true)
const databaseBytes = statSync(legacyPath).size
const legacy = measure(legacyDb, options.iterations, runLegacyMutation)
const optimized = measure(optimizedDb, options.iterations, runOptimizedMutation)
legacyDb.close()
optimizedDb.close()

process.stdout.write(
`${JSON.stringify(
{
rows: ROW_LIMIT,
payloadBytes: options.payloadBytes,
databaseBytes,
iterations: options.iterations,
legacy,
optimized,
medianSpeedup: legacy.medianMs / optimized.medianMs,
totalSpeedup: legacy.totalMs / optimized.totalMs
},
null,
2
)}\n`
)
} finally {
rmSync(fixtureDir, { recursive: true, force: true })
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
file="tests/tools/benchmarks/mutation-receipt-capacity-bench.mjs"
printf '%s\n' "== file outline =="
ast-grep outline "$file" || true
printf '%s\n' "== relevant source =="
cat -n "$file" | sed -n '1,220p'
printf '%s\n' "== runtime availability =="
node --version || true
printf '%s\n' "== DatabaseSync references =="
rg -n "DatabaseSync|createFixture|function measure|const measure" "$file"

Repository: stablyai/orca

Length of output: 8872


🏁 Script executed:

#!/bin/bash
set -eu
node --input-type=module <<'JS'
import { DatabaseSync } from 'node:sqlite'
import { existsSync, mkdtempSync, rmSync, statSync } from 'node:fs'
import { join } from 'node:path'
import { tmpdir } from 'node:os'

const directory = mkdtempSync(join(tmpdir(), 'sqlite-cleanup-check-'))
const databasePath = join(directory, 'receipt.db')
const db = new DatabaseSync(databasePath)
let removalResult
try {
  db.exec('CREATE TABLE receipts (id INTEGER PRIMARY KEY)')
  removalResult = 'succeeded'
  try {
    rmSync(directory, { recursive: true, force: true })
  } catch (error) {
    removalResult = `failed: ${error.code ?? error.name}: ${error.message}`
  }
  console.log(JSON.stringify({
    platform: process.platform,
    node: process.version,
    removalResult,
    directoryExistsAfterRemoval: existsSync(directory),
    databaseExistsAfterRemoval: existsSync(databasePath)
  }))
} finally {
  db.close()
  if (existsSync(directory)) rmSync(directory, { recursive: true, force: true })
}
JS

Repository: stablyai/orca

Length of output: 289


🌐 Web query:

Node.js node:sqlite DatabaseSync close() documentation open database file handles Windows fs.rmSync recursive behavior

💡 Result:

In Node.js, the DatabaseSync.close method is a wrapper around the SQLite native function sqlite3_close_v2 [1][2]. When called, it attempts to close the database connection and release associated file handles [1][2]. Regarding your specific concerns: 1. Windows File Locking and Deletion: SQLite, by design, requires all prepared statements to be finalized before a connection can be fully closed and the underlying file released [3]. If you encounter issues deleting a database file on Windows after calling close, it is often because there are unfinalized statements or active sessions associated with that connection [4][3]. Ensure that all statements created with prepare are explicitly finalized (or have their scope managed, if applicable) and that any active sessions are closed before calling database.close [4][3]. 2. Node.js Specific Behavior: Recent updates have addressed issues where DatabaseSync.close could be called reentrantly from within user-defined function callbacks, which previously caused segmentation faults [5][6]. Node.js now tracks callback depth and will throw an ERR_INVALID_STATE error if you attempt to close the database while a callback is still executing [6]. Additionally, ensure that you are not holding stale references to objects like sessions or prepared statements, as these may retain native handles that prevent the file from being released even after the main database connection is closed [4]. 3. fs.rmSync and Recursive Behavior: The fs.rmSync function in Node.js, when used with the { recursive: true } option, behaves as follows: - If the path points to a file, it deletes the file. - If the path points to a directory, it deletes the directory and all of its contents (recursively). - On Windows, if any file within that structure is locked by another process (or a lingering, unfinalized handle from your application), the operation will fail with an EBUSY or EPERM error. To reliably delete the database file, verify that the connection and all associated statements/sessions are truly closed. If using journal modes like WAL (Write-Ahead Logging), be aware that temporary files (such as -wal or -shm files) are also created and must be handled; deleting the main database file while these remain can result in errors or database corruption [3]. Top Results: [1][6][4][3]

Citations:


Close databases during all cleanup paths.

If measure throws after createFixture returns, the current close calls do not run. Move both close calls into finally before rmSync. If createFixture throws after opening DatabaseSync, close that local connection inside createFixture.

@brennanb2025

brennanb2025 commented Aug 10, 2026 •

Copy link
Copy Markdown
Contributor Author

Grok Electron QA evidence (HEAD 2b7792f1d2)

User impact: before vs. after

Measured on the same high-water fixture: 10,000 mutation receipts, 98.02 MiB WAL, 9,500-byte payloads, 64 sequential mutations.

Before

Every durable orchestration mutation walked the receipt table for retention and performed full-table receipt counts. At the 10,000-row high-water mark it also had to find and sort the oldest completed receipts to make room. For users with long-running or high-volume orchestration sessions, task/message/dispatch operations could pause on SQLite work, and that delay grew with the receipt database.

Previous capacity path Measured time
Median per mutation 32.620 ms
p95 per mutation 41.840 ms
Maximum 51.255 ms
Total for 64 mutations 2,174.174 ms

After

A trigger-maintained ledger provides the exact row count without scanning the receipt table. A partial covering index handles age retention and oldest-completed pruning, while pruning 64 completed rows at the high-water mark amortizes cleanup.

New capacity path Measured time Improvement
Median per mutation 0.031 ms 1,055× faster
p95 per mutation 0.045 ms 929× faster
Maximum 0.606 ms 85× faster
Total for 64 mutations 2.657 ms 818× faster

That removes 32.589 ms from the median mutation and 2,171.517 ms across the 64-mutation run. The median capacity check is now effectively sub-millisecond while preserving the 10,000-row hard cap, 30-day retention, oldest-first replay retention, pending-receipt fail-closed behavior, and exact counts for older binaries through database triggers.

Checkout / identity

  • Exact HEAD: 2b7792f1d263572ed0d04d219d7ba1a5eb9f90e2
  • Isolated launch: ORCA_DEV_USER_DATA_PATH=/tmp/orca-qa-13647-…-userdata, CDP 61992
  • window.api.app.getIdentity() → devRepoRoot: /Users/brennanbenson/orca/workspaces/orca/perf-mutation-ledger-capacity (isDev: true, branch brennanb2025/perf-mutation-ledger-capacity)

UI exercise (CDP / agent-browser only)

  • App loaded at http://localhost:5179/ with full chrome (sidebar, onboarding, projects empty state)
  • Opened Automations panel (templates + Overview/Runs visible); Settings click also exercised
  • No Computer Use / AppleScript / OS input injection

Screenshots
Visible Orca dev instance
Automations panel

Backend capacity validation (this profile + focused tests)

  • Live orchestration.db PRAGMA user_version = 26
  • Present: idx_mutation_receipts_completed_updated, mutation_receipt_ledger (singleton, count=0), insert/delete count triggers
  • EXPLAIN QUERY PLAN oldest-completed walk: SCAN … USING COVERING INDEX idx_mutation_receipts_completed_updated (no temp B-tree)
  • Vitest: mutation-receipt-capacity.test.ts — 4/4 passed
  • Light bench (--iterations 8 --payload-bytes 1024): median ~63× speedup (legacy 11.21ms → optimized 0.18ms)

Logs / console

  • CDP console during attach: 0 errors/exceptions (debug/info/warning only)
  • Dev log noise only (unrelated): WS transport port contention on multi-instance host, legacy worker provider-ready recovery failed: terminal_liveness_unavailable, Claude usage 429 — no mutation-receipt / schema-v26 failures

Cleanup

  • Killed only the QA Electron/dev tree and removed temp user-data under /tmp/orca-qa-13647-…

Residual risk

  • Additive v26 migration still does one index-build scan on existing large receipt tables; older binaries keep correct trigger-maintained counts but retain slower capacity queries until upgraded. Fresh-profile QA did not load a 10k-row production WAL into the live app UI path.

@brennanb2025
brennanb2025 merged commit a70291a into main Aug 10, 2026
45 checks passed
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