Skip to content

feat(analytics): record write-transaction commit latency#1688

Open
kriszyp wants to merge 2 commits into
mainfrom
kris/txn-commit-latency-592
Open

feat(analytics): record write-transaction commit latency#1688
kriszyp wants to merge 2 commits into
mainfrom
kris/txn-commit-latency-592

Conversation

@kriszyp

@kriszyp kriszyp commented Jul 7, 2026

Copy link
Copy Markdown
Member

Summary

Adds transaction-commit-time, a distribution metric (mean + p90/p95/p99/p999 via the standard analytics action path) recording each write transaction's submit→settle duration.

Purpose

#592 asks for a leading indicator before write-heavy workloads hit Outstanding write transactions have too long of queue, please try again later (503). That rejection fires when the oldest outstanding commit has been outstanding longer than storage.maxTransactionQueueTime (45s default) — a latency condition, not a count. transaction-commit-time is measured on that exact clock (outstandingCommitStart → settle), so a rising p99/p999 is the direct leading indicator: it reflects transaction size/duration and the shared storage-engine write path, not just per-thread submission concurrency.

Recorded per commit attempt — a transient-conflict retry rejects and re-issues commit(), and the overload check's own outstandingCommit re-arms per attempt the same way, so this matches the failure semantics exactly.

Design note (dependency inversion)

resources/DatabaseTransaction.ts does not import the analytics/server modules. It exposes setCommitLatencyRecorder(recorder); resources/analytics/write.ts registers the recorder at load. This mirrors the existing replicationConfirmation callback-registration pattern in the same file and avoids a circular import between the storage layer and the analytics/server layer. The recorder is wrapped so a metrics failure can never throw into the floating .then() or disturb the commit path.

Attention

  • Companion docs PR: docs(analytics): document transaction-commit-time metric documentation#572
  • Sibling PR (secondary/complementary metric, not required for this one): feat(analytics): expose write/read transaction queue depth (count) metrics #1689 — a per-thread in-flight transaction count. Kris flagged that count doesn't capture transaction size/duration or the shared write-path queue the way latency does, so it's split out as an independent, optional PR.
  • Cross-model review (Codex + Gemini) caught three real issues in an earlier revision of this diff — an unhandled-rejection risk on the floating .then(), a circular-import risk (DatabaseTransactionServer), and a test binding issue. All three are resolved by the registration-based redesign above (verified by re-running build/lint/tests); no open concerns remain from that review.

Generated by Claude Opus 4.8.

Emit transaction-commit-time as a distribution metric (mean + percentiles via
the standard analytics action path) capturing each write commit's submit->settle
duration. This is the same clock the overload check compares against, so a rising
p99/p999 is the leading indicator for the 'Outstanding write transactions have
too long of queue' (503) rejection — and unlike an in-flight count it reflects
transaction size/duration and the shared storage-engine write path, not just
per-thread submission concurrency.

Recorded per commit attempt (retries re-arm the outstanding-commit clock, so
per-attempt timing matches the overload semantics). Async commit path only;
synchronous commits do not queue.

Closes #592

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
gemini-code-assist[bot]

This comment was marked as resolved.

@claude

claude Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Reviewed; no blockers found.

…ni hardening

- Skip the commit-recording assertion under HARPER_STORAGE_ENGINE=lmdb: LMDB
  writes route through the separate LMDBTransaction.commit() override
  (resources/LMDBTransaction.ts), which does not call the RocksDB-path
  latency hook — matching the existing RocksDB-only carve-outs in
  transaction.test.js. This is what failed CI (all 3 Node versions, via the
  npm run test:unit:all -> test:unit:lmdb pass).
- setCommitLatencyRecorder now types the recorder as optional, matching
  actual call sites (tests unregister it with undefined).
- recordCommitLatency guards commitResolution as thenable before calling
  .then(), per Gemini review feedback -- defensive against a future caller
  passing a non-Promise value, even though today's only caller
  (rocksdb-js's async Transaction.commit()) always returns a real Promise.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@kriszyp kriszyp marked this pull request as ready for review July 7, 2026 16:39
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