Skip to content

perf(performance-monitor): move blocking sqlite3 I/O off the event loop - #1200

Closed
groupthinking wants to merge 1 commit into
mainfrom
claude/determined-maxwell-i68kve
Closed

perf(performance-monitor): move blocking sqlite3 I/O off the event loop#1200
groupthinking wants to merge 1 commit into
mainfrom
claude/determined-maxwell-i68kve

Conversation

@groupthinking

Copy link
Copy Markdown
Owner

Canonical issue

Closes #

Outcome

PerformanceMonitor used the fully synchronous sqlite3 driver directly inside six async def methods. Each call ran connect + statement + commit (fsync) + close on the event loop, stalling every other coroutine for the duration of the disk write. Because record_metric() is awaited on live request paths (api/v1/router.py:1165 and :1183), each metric write blocked request handling. This PR moves that I/O onto a worker thread so the loop stays responsive.

Scope

  • Included: _store_metric, _store_alert, _basic_cleanup, get_current_performance_summary, _get_recent_metrics_summary, _store_benchmark_result — each DB block is now a nested sync function dispatched via await asyncio.to_thread(...). Statement text, transaction boundaries, return values, and error handling are unchanged; only the thread the work runs on changes.
  • Explicitly excluded: _init_database — it is a synchronous method called from __init__, so it never runs on the event loop.

Risk

  • Risk level: low
  • Failure mode: connections are opened and closed inside each to_thread call, so there is no shared handle a caller could tear down mid-flight; a thread-pool exhaustion would degrade to the same latency as before, not error.
  • Rollback: revert this commit; behaviour returns to synchronous on-loop writes.

Verification

  • Focused tests — pytest tests/unit/test_performance_monitor.py: 119 passed (113 pre-existing unchanged + 6 new in TestSqliteDoesNotBlockEventLoop)
  • Non-vacuity — replacing all six to_thread dispatches with direct calls fails exactly the 3 heartbeat tests and no others
  • Required CI — pending on this head
  • Review threads resolved — pending review

Production evidence

Not applicable — internal service latency change with no user-facing surface; effect is reduced event-loop stall during metric persistence, covered by the heartbeat tests.

Agent handoff

  • One canonical issue is linked — no dedicated issue exists
  • No competing PR implements the same issue
  • Acceptance criteria are satisfied
  • Required checks pass on the current head — pending CI
  • Human decision is requested for merge (protected main)

Generated by Claude Code

`PerformanceMonitor` used the fully synchronous `sqlite3` driver directly
inside six `async def` methods. Each call ran connect + statement + commit
(which fsyncs) + close on the event loop, so nothing else on the loop could
be scheduled for the duration of the disk write.

This is on a live request path: `api/v1/router.py:69` imports the monitor and
awaits `record_metric()` at `:1165` and `:1183`, and `record_metric` calls
`_store_metric` unconditionally.

Each method's database work is now a nested synchronous function dispatched
via `await asyncio.to_thread(...)`. Statement text, transaction boundaries,
return values and error handling are unchanged; only the thread the work runs
on changes. Connections are created and closed inside each call, so there is
no shared handle a caller could tear down mid-flight.

Sites moved off-loop:
  _store_metric, _store_alert, _basic_cleanup,
  get_current_performance_summary, _get_recent_metrics_summary,
  _store_benchmark_result

`_init_database` is left alone: it is a synchronous method called from
`__init__`, so it never runs on the event loop.

Tests: 113 pre-existing pass with zero edits, plus 6 new in
`TestSqliteDoesNotBlockEventLoop`. Non-vacuity was proven by behavioural
mutation: replacing all six `to_thread` dispatches with direct calls fails
exactly the 3 heartbeat tests and no others. The other 3 new tests are
preserved-behaviour guards -- they pass under both old and new code and exist
to pin behaviour the change must not regress, not to prove the change.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@vercel

vercel Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
v0-uvai Canceled Canceled v0 Aug 1, 2026 10:42pm

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are limited based on label configuration.

🏷️ Required labels (at least one) (1)
  • [‘architecture-gap’, ‘bug’, ‘ci-cd’, ‘ci/cd’, ‘copilot-rabbit’, ‘documentation’, ‘duplicate’, ‘enhancement’, ‘frontend’, ‘github_actions’, ‘good first issue’, ‘help wanted’, ‘high-priority’, ‘invalid’, ‘javascript’, ‘ml-model’, ‘needs-triage’, ‘pipeline-critical’, ‘placeholder-code’, ‘priority:high’, ‘python’, ‘python:uv’, ‘question’, ‘styling’, ‘tests’, ‘v0’]

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 047c2919-908b-4815-99ee-54a388937a45

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@github-actions github-actions Bot added the python label Aug 1, 2026
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Snapshot Warnings

⚠️: No snapshots were found for the head SHA b38b9fd.
Ensure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice.

Scanned Files

None

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

Agent Completion Truth Gate: BLOCKED

Reasons: invalid_payload

Machine-readable verdict
{
  "details": {
    "invalid_fields": [
      "issue.number",
      "policy.agent_login",
      "policy.run_id"
    ]
  },
  "reasons": [
    "invalid_payload"
  ],
  "verdict": "blocked"
}

Workflow evidence

@groupthinking groupthinking added the tests label Aug 1, 2026 — with Claude

Copy link
Copy Markdown
Owner Author

CI status & blockers (head b38b9fd)

The code change is verified: pytest tests/unit/test_performance_monitor.py119 passed (113 pre-existing unchanged + 6 new), and non-vacuity is proven by mutation (reverting the six to_thread dispatches fails exactly the 3 heartbeat tests). Dependency Review passed clean.

Two checks are red, and neither is caused by this diff (which only touches performance_monitor.py + its test file):

  1. gitleaks (working tree) — false positive on uv.lock:5129: a PyPI wheel sha256: hash matches the square-access-token rule. This diff does not modify uv.lock; it's the org-wide false positive already being addressed in fix(security): scope uv.lock checksum false-positive allowlist (#1141) #1142 and fix(ci): stop gitleaks flagging uv.lock PyPI package hashes #1197. Landing either of those clears this for the whole fleet.

  2. agent-completion/truth-gateinvalid_payload (issue.number, policy.agent_login, policy.run_id). This is a standalone perf change with no canonical issue or provider dispatch run_id, so there is no honest agent-lock-manifest to declare — I will not fabricate one (cf. fix: scope agent gate applicability to real dispatch evidence #1154, which scopes this gate to real dispatch evidence). The Vercel "Canceled" status is also not a failure (backend-only change, no deploy target).

Terminal state: HALTED(awaiting_merge_approval) — protected main requires human sign-off, and the two red gates are pre-existing/policy blockers, not defects in this change. Ready for review; added python/tests labels so CodeRabbit auto-review applies.


Generated by Claude Code

Copy link
Copy Markdown
Owner Author

Closing as a duplicate — no action needed.

This PR carries the exact same commit (b38b9fd) as #1196 ("perf(performance-monitor): move blocking sqlite3 I/O off the event loop", Closes #1195), which was merged to main at 22:40 UTC as 26b2a7c. main already contains all six asyncio.to_thread dispatches in performance_monitor.py and the TestSqliteDoesNotBlockEventLoop suite, so this change is fully landed and this PR is redundant.

I opened #1200 from my working branch ~20 min after #1196 had already merged-and-closed, so #1196 didn't appear in the open-PR scan. The two red checks discussed above (gitleaks uv.lock false positive, agent-completion/truth-gate) are moot here — the work is in main via the canonical PR. Closing.


Generated by Claude Code

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant