Repository navigation
copyApply: snapshot non-system base-copy rows without audit entries (part of #480) - #486
Conversation
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
|
Reviewed; no blockers found. The new commit ( |
|
Scope clarification (post-CI iteration). copyApply applies to non-system databases only. The system DB drives cluster machinery off the audit Consequently this PR does not fix #480's system-analytics copy spin (hdb_analytics lives in the system DB) — that is handled separately by the retention-horizon dedup guard, which removes the O(n) keyed-lookup cost without dropping audit entries. This PR is the non-system base-copy optimization (perf + no txn-log amplification on data copies). Also added: backoff (not busy-loop) on a persistent copy-cursor flush failure. Possible follow-up (discussion): a txn-log "table-reload" marker would let copyApply safely cover the system DB too — subscribers that need back-filled data react to the marker by reloading from the table, instead of relying on per-row events. — generated by an LLM (Claude Opus 4.8) |
…sactions (#480) Receive-side half of copyApply (paired with harper core). Marks base-copy frames older than copyStartTime as snapshot applies (event.isCopyApply) so core writes them without an audit/transaction-log entry — removing the O(n) keyed-dedup spin and the transaction-log amplification when copying an analytics-laden table (#480). C2: only rows with version < copyStartTime are snapshotted; rows >= copyStartTime are re-delivered by the post-copy audit replay and keep real audit entries so the redelivery dedups (no commutative double-apply). The copy-frame status is latched once per WS message body (the decode loop awaits and ws does not serialize async handlers, so COPY_COMPLETE could otherwise flip mid-body and drop trailing rows to the audited path). Durability: copied DBs run WAL-off and these rows carry no transaction-log entry, so the resume cursor and the [seq] watermark must only advance behind an explicit RocksDB flush (memtable -> SST). flushDurableCopyCursor flushes on a 64MB/5s cadence and persists the cursor only after the flush resolves; seqUpdateEndTxn gates every plain sequence-update end_txn (and the per-batch end_txn onCommit) so [seq]=copyStartTime is persisted only after the snapshot rows are durable (core awaits onCommit, then updateRecordedSequenceId). Dual-engine flush (RocksDB flush() / LMDB flushed). Bumps core to include the copyApply write mode (harper#1479). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
) CI integration regression: cluster/copy tests (incl. "Cluster Replication with LMDB") timed out with cert-trust/1006 churn. The durability flush gate ran for ALL copies, but only RocksDB copy-apply rows are WAL-off with no transaction-log entry — LMDB copy rows stay audited (durable via the txn-log) and never needed it. Forcing the gate on LMDB (and adding an awaited onCommit to EVERY sequence-update end_txn) destabilized copy/cluster formation. Scope the gate to STORAGE_IS_ROCKSDB: - seqUpdateEndTxn returns a plain end_txn for normal replication, LMDB, and mid-copy updates below copyStartTime (exactly as before — no per-seq-update overhead); only the RocksDB copy-final update (localTime >= copyStartTime) carries the flush onCommit. - flushDurableCopyCursor persists the cursor directly for LMDB (original behavior); the RocksDB flush gate is unchanged. - The per-batch endTxnEvent onCommit flush gate is likewise RocksDB-gated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The system DB drives event-based machinery off the audit `aftercommit` stream: hdb_nodes feeds subscribeToNodeUpdates (peer discovery / connection setup) and hdb_certificate feeds CA install. copy-apply suppresses audit entries, so copying the system DB left a fresh node unable to discover peers or trust certs — cluster never connected (cert-CA errors, 1006 churn, "connect nodes" timeouts across integration shards 1/2/4). Scope copy-apply AND its WAL-off durability gate to non-system RocksDB copies via copyApplyActive(): the system copy reverts exactly to main's behavior (audited rows, events fire, direct cursor persist, plain seq-updates), while data-DB copies keep copy-apply. The system-analytics spin (#480) is handled by the retention-horizon dedup guard instead, which fixes the O(n) lookup without removing audit entries. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On a persistent copy-cursor flush failure (disk-full / I/O error), the prior .catch re-staged the cursor and the .finally re-invoked maybeFinishCopy → flushDurableCopyCursor immediately, busy-looping at ~100% CPU and flooding logs. Add an escalating backoff (250ms → 30s cap) gating the flush, set on failure with a scheduled retry, and reset on success. A transient error self-heals; a persistent one idles until the operator (or a higher-level watchdog) acts, without dropping the connection. (Gemini cross-model review.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ain) Points the core submodule at core/main 28db4fde4 (harper#1479 merged), the rebased-onto-main equivalent of the RC's core copyApply commit. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
9665f43 to
fcc403d
Compare
Summary
Receive-side half of copyApply (paired with harper#1479). Base-copy frames are applied as current-state snapshots — no audit/transaction-log entry, no out-of-order resequencing — instead of being re-fabricated as transactions. Removes the O(n) keyed audit-dedup spin and the per-peer transaction-log amplification when copying an analytics-laden table (#480).
Purpose
Copying a table whose rows predate transaction-log retention makes every applied row's keyed dedup miss and scan to end-of-log (one worker ~100% CPU, no forward progress), and re-fabricating copied rows as transactions floods the per-peer logs that other peers then copy. See #480.
Design — please focus review here
event.isCopyApply = messageIsCopyFrame && auditRecord.version < copyModeStartTime): only rows older thancopyStartTimeare snapshotted. The post-copy audit replay resumes fromcopyStartTimeand re-delivers every row>= copyStartTime, so those keep real audit entries and the redelivery dedups (a commutative patch would otherwise double-apply). Older rows are never re-delivered → safe to snapshot. RocksDB-only (core gates onisRocksDB).messageIsCopyFrame, once per WS body): the decode loop awaits (waitForDrain/setImmediate) andwsdoes not serialize async handlers, so a laterCOPY_COMPLETEcould flipcopyCompleteReceivedmid-body and drop trailing rows to the audited path.[seq]watermark must only advance behind an explicit RocksDB flush (memtable → SST):flushDurableCopyCursorflushes on areplication_copyCursorFlush{Bytes,IntervalMs}cadence (64 MB / 5 s) and persists the copyCursor only after the flush resolves;maybeFinishCopyforces a final flush before removing the cursor.seqUpdateEndTxnwraps every plain sequence-update end_txn (SEQUENCE_ID_UPDATE, REMOTE_SEQUENCE_UPDATE, blob-drain re-emit); itsonCommitflushes the snapshot rows before[seq]=copyStartTimeis persisted. core awaitsonCommitthen callsupdateRecordedSequenceId, so this orders flush-before-seq; a flush rejection skips the seq persist so the copy re-runs rather than losing rows. The per-batchendTxnEvent.onCommitcarries the same gate (defense-in-depth).flush()/ LMDBflushed).Relationship to #483
Complementary and non-overlapping (#483 paces the send-side socket flush; this is the receive-side apply). Shares the 5 s cadence +
replication_copy*env convention; can stack/rebase once #483 lands. (Did not reuse #483'screateCopyFlushPacerto keep this PR independently mergeable; worth consolidating after #483 merges.)Review notes
Cross-model reviewed (Codex, 4 rounds): CRDT redelivery double-apply → C2 gate; seq cursor not gated on flush →
seqUpdateEndTxnat all emit sites; LMDBflush()/replay-cursor divergence → RocksDB-gating + dual-engine flush; asyncCOPY_COMPLETEinterleaving → latched status. No open findings; a Gemini pass is running and I'll fold anything it surfaces. Coverage analysis: C2 + the interim retention-guard (separate) cover the mass O(n)-spin paths, so the txn-log index work is optional (demoted).Generated by an LLM (Claude Opus 4.8).