mirror of
https://github.com/kvcache-ai/Mooncake.git
synced 2026-11-05 01:17:55 +00:00
[GH-ISSUE #4546] [Bug]: [Store] HA OpLog metrics audit: gauges and counters registered but never written in production #1159
Labels
No labels
ascend
auto-closed
bug
bug
CI/Build
Common
documentation
enhancement
feature request
good first issue
help wanted
high priority
MetaX
nightly-failure
pull-request
PyTorch Backend
question
RFC
Roadmap
stale
Store
Tests
Transfer Engine
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
kvcache-ai/Mooncake#1159
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @sususama on GitHub (Oct 9, 2026).
Original GitHub issue: https://github.com/kvcache-ai/Mooncake/issues/4546
Bug Report
Summary
While reviewing #4402 (syncing
ha_oplog_applied_sequence_idwith recovered standby baselines), @he-yufeng flagged thatset_oplog_last_sequence_idhas no production caller, soha_oplog_last_sequence_idexports 0 forever in a real deployment (comment).I audited every gauge and counter registered in
HAMetricManager(mooncake-store/src/ha_metric_manager.cpp) against all call sites insrc/on current main (db6f14bd). The pattern is broader than the one flagged metric — 8 of 17 OpLog-related set/inc methods have zero production callers.Audit results
Gauges — always export their initial value:
ha_oplog_last_sequence_idset_oplog_last_sequence_idset_batch_record_durable_sequenceha_oplog_standby_lagset_oplog_standby_lagHotStandbyService::GetSyncStatus()(primary_seq_id_ - applied_seq_id_) but never pushed to the gaugeha_oplog_pending_entriesset_oplog_pending_entriesha_pending_mutation_queue_sizeset_pending_mutation_queue_sizeCounters — always export 0:
ha_oplog_skipped_entries_totalinc_oplog_skipped_entriesha_oplog_gap_resolve_attempts_total/ha_oplog_gap_resolve_success_totalinc_oplog_gap_resolve_attempts/..._successha_oplog_etcd_write_failures/ha_oplog_etcd_write_retriesinc_oplog_etcd_write_failures/..._retriesha_oplog_dropped_put_endinc_oplog_dropped_put_endFor comparison, these are wired correctly:
set_oplog_applied_sequence_id(oplog_applier.cpp:133),set_standby_state/inc_state_transitions/inc_oplog_watch_disconnections(hot_standby_service.cpp:38-45),set_batch_record_*andinc_batch_record_*(ordered_oplog_writer.cpp),inc_oplog_checksum_failures(oplog_applier.cpp:64),inc_oplog_applied_entries.Two concrete examples of why this matters
ha_oplog_standby_lag— the lag value already exists internally:GetSyncStatus()computeslag_entries = primary_seq_id_ - applied_seq_id_(hot_standby_service.cpp:487-489), andprimary_seq_id_is kept fresh by the replication loop (:695,:1144,:1232). The only missing step is pushing it to the gauge where the poll loop already mirrorsapplied_seqinto metrics.ha_oplog_last_sequence_id— either it is superseded byset_batch_record_durable_sequence(writer-side durable watermark, wired) and should be deleted, or it needs a writer. Same class of decision as #4399 / #4402, but on the primary side.Proposal
For each orphan metric, decide between two options, and split the work into small, independently reviewable units:
ha_oplog_last_sequence_idifha_writer_durable_sequence/ batch-record metrics already cover it).Happy to start with a per-metric disposition list and small PRs (one metric family per PR), beginning with
ha_oplog_standby_lagsince the lag value is already computed — or with deletions if maintainers prefer to trim the metric surface first.Before submitting...
@github-actions[bot] commented on GitHub (Oct 9, 2026):
Thanks for opening this issue, @sususama!
52159774A maintainer will triage this when possible. To help us respond faster, please include:
Useful links: Documentation · Contributing guide
@he-yufeng commented on GitHub (Oct 9, 2026):
Spot-checked three of the eight on current main (
db6f14bd): set_oplog_standby_lag, set_oplog_last_sequence_id and inc_oplog_dropped_put_end each have exactly one hit in src/, their own definition, and the lag value really is computed at hot_standby_service.cpp:487 without ever reaching the gauge. The audit holds.Direction from my side, splitting the eight into two classes:
Wire these, they answer questions an operator actually asks during an HA incident: ha_oplog_standby_lag (the value already falls out of GetSyncStatus, the sync loop just needs to push it to the gauge), ha_oplog_pending_entries and ha_pending_mutation_queue_size (backpressure visibility), ha_oplog_etcd_write_failures / retries (etcd health), ha_oplog_gap_resolve_attempts / success and ha_oplog_skipped_entries_total (replay correctness), ha_oplog_dropped_put_end (data-loss signal).
Delete ha_oplog_last_sequence_id instead of wiring it. The writer-side durable watermark already exists as set_batch_record_durable_sequence with a real caller in ordered_oplog_writer.cpp; a second sequence gauge with a subtly different definition invites exactly the confusion that produced #4402.
Small PRs per metric family would review fastest: lag + pending, etcd, gap-resolve, dropped-put-end, plus the deletion. You did the audit, so first right of refusal is yours. Otherwise I can pick it up.
@sususama commented on GitHub (Oct 10, 2026):
Thanks @he-yufeng — taking this. I re-ran the audit across all 34 set_/inc_/observe_/record_ methods in ha_metric_manager.h on
db6f14bd, not just the eight, and it changes three of your callings.Agree, wiring:
I'd flip these from "wire" to "delete":
ha_writer_committed_queue_depth / ha_writer_waiting_slots from the runtime snapshot. There is no separate pending-mutation queue in the HA path to read from, so wiring them means inventing a second definition of the same number.
One I'd like your call on: ha_oplog_etcd_write_failures. The only candidate site is the writer terminal path (ordered_oplog_writer.cpp:139-144), but what it reports is "writer terminated" (non_retryable / fenced / retry_timeout), not a single etcd write failure. Delete it, or rename it to something like ha_writer_terminal_failures?
Scope is wider than the issue states. Zero production callers is 15 of 34, not 8 — additionally observe_oplog_etcd_write_latency_us, observe_oplog_apply_latency_us, inc_oplog_batch_commits, inc_oplog_sync_batch_commits, and record_snapshot_operation. That last one looks like a real bug rather than an orphan: record_snapshot_skip is called from 17 sites but record_snapshot_operation from none, so snapshot success counts never move. I'll handle it separately.
Plan, smallest reviewable units:
I'll start with 1 and 2.
@Juhyun-Kim-Memphis commented on GitHub (Oct 21, 2026):
One data point for the
ha_oplog_dropped_put_enddisposition. #4582 deletes it because "no such logic exists insrc/ha".A dropped-PutEnd path does exist, but on the primary, in
MasterService::PutEnd, not insrc/ha. When the OpLog writer has no free entry (TASK_PENDING_LIMIT_EXCEEDED, -1401), PutEnd logs a warning and returns success. The record is not retried, so the standby does not get the object from this PutEnd. On currentmain(7e4fc11e):github.com/kvcache-ai/Mooncake@7e4fc11ee0/mooncake-store/src/master_service.cpp (L5824-L5833)We counted these warnings in a rig on
b140aa92. It used the HA example flags from the deployment guide (oplog_batch_max_entries=1024), 3 etcd members, and a load that calls master RPCs only, 3 runs:etcd was not slow in these runs (WAL fsync averaged 0.33 ms). No metric counted these drops; the WARNING line was the only trace. So the counter, under this name or another, has a natural increment site at that branch. It would be the data-loss signal suggested earlier in this thread. Whether PutEnd should keep returning success in this case is a separate question; we only note that the path exists.
Related: #4666 (its Appendix §B has the measurement details).
@sususama commented on GitHub (Oct 21, 2026):
Confirmed on current main — you are right, and my audit statement was too broad.
PutEnd(master_service.cpp:5853-5861on7540f582) callsAppendOpLogVisibleBeforeDurableand, on failure, only logsPutEnd: OpLog queue failed ...before falling through toreturn {}. The failure has more inputs than the queue being full:ReserveBatchreturnsTASK_PENDING_LIMIT_EXCEEDEDwhenopen_waiting_slots + entry_count > max_entries_per_batch(ordered_oplog_writer.cpp:284-287), and also returns the writer's terminal-state error, orUNAVAILABLE_IN_CURRENT_STATUSwhen the writer is no longer accepting. Nothing propagates to the caller, nothing is retried, and no metric counts it.Two things to add to what you measured:
PersistSegmentOpForHAOrEnqueue(master_service.cpp:13544-13550on7540f582) isvoidand only logssegment OpLog queue failed, so the silent-drop surface is wider than PUT_END.inc_oplog_dropped_put_enddid have a real caller once: ina811c288,src/oplog_applier.cpp:116discarded a PUT_END whose sequence had been skipped viaskipped_sequence_ids_.ff217b8c(RFC #2756, batch-record writer) removed that mechanism together with the call site, which is why the metric became an orphan — and why "due to late arrival after skip" describes behavior that no longer exists.Changing course on #4582
I said earlier I would keep the deletion and file a follow-up under a new name. Your measurement changes that: at 6-7% of PUT_END records lost during a plain fill, and with the failover deltas in your §G matching the skipped counts to within 0.3%, this is the data-loss signal an operator needs during an HA incident, and it should not wait on a new issue.
So #4582 now keeps
ha_oplog_dropped_put_endand wires it at the branch you found, instead of retiring it:PutEnd'sif (!result)block, the only place where the loss is visible whilePutEndstill returns success.ha_oplog_append_failed_total{op_type}instead, that is a one-line rename on top of this.MasterServiceHATestthat drives a writer toTASK_PENDING_LIMIT_EXCEEDEDand asserts both thatPutEndstill returns success and that the counter moves, and the serialization test now asserts the series is exported rather than retired.I credited your numbers in the PR description and linked #4666 (Appendix §B) as the measurement source; say the word if you would rather I did not cite them directly.
Still open
The segment-op swallow is not covered by this counter, and the name is PUT_END-specific. I am filing that separately — a counter with
op_type(and possibly the error code) as labels, incremented atPersistSegmentOpForHAOrEnqueueand atPutEnd, would generalize this without pretending one metric covers both. YourReserveBatchfailure taxonomy is what that issue will be built on.The behavioural question you flagged — whether
PutEndshould keep returning success when the record never reached the log — I agree is separate and more important. A counter makes the loss visible; it does not fix it, and it should not be folded into either of these.@he-yufeng commented on GitHub (Oct 21, 2026):
Citing the numbers directly is fine, and the revised wiring is the right shape: incrementing at the
if (!result)branch is the one place the loss is still observable, and keeping the existing metric name avoids breaking dashboards that already key on it. The new help text matches what the counter now actually counts.Agreed on keeping the three follow-ups separate: the segment-op swallow (PersistSegmentOpForHAOrEnqueue is void, same pattern), the generalized op_type/error-code counter, and the behavioral question of whether PutEnd should keep returning success when the record never reached the log. The last one is the one that matters for failover correctness; the counters just make it visible.
@Juhyun-Kim-Memphis commented on GitHub (Oct 22, 2026):
Thanks for checking it on current
mainand for the history of the counter. Please do cite the numbers and #4666 Appendix §B directly; that is what they are there for. Keeping the existing name and wiring it at theif (!result)branch sounds right to us, and we agree thePutEndreturn value is the separate, more important question.