Repository navigation
[Store] L2->L1 promotion-on-hit: observability metrics + max_per_heartbeat knob - #2176
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces comprehensive "Promotion-on-hit" metrics and a new configuration parameter, promotion_max_per_heartbeat, to control the number of promotion tasks returned per heartbeat. The changes include the definition, initialization, and integration of various metrics (in-flight, admitted, completed, failed, etc.) within the MasterMetricManager and MasterService. Feedback focuses on ensuring these new metrics are correctly exported for monitoring and maintaining metric invariants across all task cleanup paths, specifically by incrementing terminal counters like promotion_failed when tasks are removed without being successfully committed.
|
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
45ca3c3 to
fae0a4d
Compare
|
@ykwd @LujhCoconut PTAL for adding metrics. Thanks |
There was a problem hiding this comment.
Pull request overview
This PR is a follow-up enhancement to the existing L2→L1 “promotion-on-hit” feature in mooncake-store, adding Prometheus observability metrics for the promotion lifecycle and exposing the previously hardcoded per-heartbeat batching limit as a configurable knob (promotion_max_per_heartbeat).
Changes:
- Add a set of
master_promotion_*Prometheus metrics to track the promotion funnel (in-flight/admitted/completed/bytes/failed/expired/cancelled) and rejection reasons (frequency/watermark/cap). - Expose
promotion_max_per_heartbeatas a gflag + JSON-configurable field, plumb it through config structures, and apply it inPromotionObjectHeartbeat(with a defensive clamp of0 -> 1). - Extend unit tests to validate key metric increments and validate the new heartbeat batch-size knob behavior.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| mooncake-store/tests/promotion_on_hit_test.cpp | Adds assertions for new promotion metrics and adds tests for the max-per-heartbeat batching knob. |
| mooncake-store/src/master.cpp | Introduces --promotion_max_per_heartbeat flag and wires it into config parsing/override logic. |
| mooncake-store/src/master_service.cpp | Implements metric increments across promotion lifecycle + uses promotion_max_per_heartbeat_ to bound heartbeat batches. |
| mooncake-store/src/master_metric_manager.cpp | Defines/serializes new promotion-related Prometheus metrics and includes them in the summary string. |
| mooncake-store/include/master_service.h | Tracks the new promotion_max_per_heartbeat_ field and updates cancellation paths to emit metrics. |
| mooncake-store/include/master_metric_manager.h | Declares promotion metric helpers/getters and adds member fields for the new metrics. |
| mooncake-store/include/master_config.h | Adds promotion_max_per_heartbeat to relevant config structs and propagation helpers. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Sorry for the late review on your PR.
Suggested fix — add one line before each shard->metadata.erase(it): // master_service.cpp:2440 (RemoveAll):
ErasePromotionTaskIfPresent(shard, it->first);
it = shard->metadata.erase(it);
// master_service.cpp:2496 (BatchRemove, stale-handle path):
ErasePromotionTaskIfPresent(shard, key);
shard->metadata.erase(it);
// master_service.cpp:2532 (BatchRemove, normal path):
ErasePromotionTaskIfPresent(shard, key);
shard->metadata.erase(it); Without this, |
| if (shard->promotion_tasks.erase(it->first) > 0) { | ||
| promotion_in_flight_.fetch_sub(1, | ||
| std::memory_order_relaxed); | ||
| MasterMetricManager::instance().dec_promotion_in_flight(); | ||
| MasterMetricManager::instance().inc_promotion_cancelled(); | ||
| } | ||
| it = shard->metadata.erase(it); | ||
| } else { |
There was a problem hiding this comment.
[Code Smell] this block inlines the same logic as ErasePromotionTaskIfPresent. Consider replacing with the helper call to keep a single source of truth:
ErasePromotionTaskIfPresent(shard, it->first);
it = shard->metadata.erase(it); There was a problem hiding this comment.
Thanks for the comments! PTAL at the fixes
|
Thanks for your patience. After addressing the comments above, please resolve the merge conflicts before merging. |
306df39 to
8149e35
Compare
|
Thank you for the PR. I've noticed the intermittent CI build failures caused by Errors while running CTest
The following tests FAILED:
18 - promotion_on_hit_test (Failed)
Error: Process completed with exit code 8.This seems like an easily fixable timing sensitivity。 The A straightforward fix may be like this: // Set put_start_release_timeout_sec to a value at least 2x smaller than the sleep duration, giving the reaper a comfortable margin:
config.put_start_release_timeout_sec = 1;
// ...
std::this_thread::sleep_for(std::chrono::seconds(3)); // 3s >> 1s timeout Would you be open to fixing this pre-existing timing sensitivity? Either way, it won't block the merge of this PR. |
…beat knob
Adds Prometheus metrics for the promotion-on-hit funnel and exposes the
previously-hardcoded kMaxPerHeartbeat as a config knob.
Metrics (master_promotion_*)
----------------------------
Funnel:
- promotion_in_flight (gauge): mirror of promotion_in_flight_
- promotion_admitted_total: tasks past all gates, enqueued
- promotion_completed_total: NotifyPromotionSuccess success
- promotion_completed_bytes_total: bytes promoted (sum of source object_size)
- promotion_failed_total: NotifyPromotionFailure accepted
- promotion_expired_total: reaper Part 4 sweeps
Rejection (per gate):
- promotion_rejected_frequency_total: below admission threshold
- promotion_rejected_watermark_total: DRAM at or above high watermark
- promotion_rejected_cap_total: promotion_in_flight at queue limit
Together: admitted = completed + failed + expired + in_flight. Wires a
working 'promotion feature health' Grafana panel: rate(admitted) vs
rate(completed) shows yield; rate(rejected_*) breaks down where work
is dropping; in_flight / promotion_queue_limit shows saturation.
promotion_max_per_heartbeat knob
--------------------------------
The previous compile-time constant kMaxPerHeartbeat = 1 (in
PromotionObjectHeartbeat) capped per-client throughput at ~6
promotions/min with the default 10s heartbeat, making
promotion_queue_limit = 50000 mostly theoretical.
Exposed as MasterServiceConfig::promotion_max_per_heartbeat, wired
through the flag parser (FLAGS_promotion_max_per_heartbeat) and
default_config.GetUInt32 path. Constructor clamps 0 -> 1 so a
mistyped config doesn't silently halt promotion delivery. Startup
log now includes max_per_heartbeat= alongside threshold= /
queue_limit=.
Default stays 1 (no behavior change on existing deployments).
Operators with small objects + RDMA-rich clusters can raise it; the
new MaxPerHeartbeatKnobControlsBatchSize test exercises 3.
Tests
-----
- MetricsFunnelTracksSuccessfulPromotion: single full lifecycle
bumps admitted/completed/completed_bytes correctly and brings
in_flight back to baseline.
- MetricsRejectionCountersIncrementOnGateMiss: each of frequency
and cap counters increments when its branch fires.
- MaxPerHeartbeatKnobControlsBatchSize: knob=3, 5 admitted tasks
drain across 3+2+0 heartbeats.
- MaxPerHeartbeatZeroClampsToOne: pathological config clamps to 1.
Suite: 32/32 pass (was 28; +4 new). No behavior change in existing
tests.
MetricsRejectionCountersIncrementOnGateMiss claimed coverage of all three rejection counters but only exercised frequency and cap. Add a sub-case that forces the watermark gate by configuring eviction_high_watermark_ratio = 0.0, asserting that promotion_rejected_watermark_total increments.
8149e35 to
4777358
Compare
upstream kvcache-ai#2180 introduced EraseMetadataEntry as the centralized metadata-erase helper and routed RemoveAll, BatchRemove, and RemoveByRegex through it, so promotion_tasks cleanup on those paths is already correct on main. The metric instrumentation (dec_promotion_in_flight + inc_promotion_cancelled) is wired into EraseMetadataEntry in this branch's earlier commit so every site that erases metadata bumps the funnel counters consistently. Add regression tests for the three paths so any future refactor that reintroduces a metadata.erase without going through EraseMetadataEntry will fail the suite: - RemoveAllErasesPromotionTask - BatchRemoveErasesPromotionTask (normal-completion branch) - BatchRemoveStaleHandleErasesPromotionTask
Three tests configure put_start_release_timeout_sec=1 and then sleep 2s waiting for the eviction-thread reaper to expire the promotion task. The reaper schedule is `now - last_discard_time > put_start_release_timeout_sec_` (strict greater-than), so a 1s release with a 2s sleep leaves only ~1s margin between the reaper firing and the assertion. CI runs observed intermittent failures when scheduling jitter erased that margin. Bump the sleep to 3s in the three affected tests so the margin is ~2s. Configuration values unchanged. Affected: - StalePromotionReaper - RemoveDuringPromotion - AllocStartRejectsReapedTask Verified 5 consecutive clean runs of all three under -j1 build.
a9df75b to
0633b29
Compare
|
@LujhCoconut PTAL. Should've addressed all your comments |
LujhCoconut
left a comment
There was a problem hiding this comment.
LGTM. Thanks for this PR.
…tbeat knob (kvcache-ai#2176) * [Store] L2->L1 promotion-on-hit: Tier A observability + max_per_heartbeat knob Adds Prometheus metrics for the promotion-on-hit funnel and exposes the previously-hardcoded kMaxPerHeartbeat as a config knob. Metrics (master_promotion_*) ---------------------------- Funnel: - promotion_in_flight (gauge): mirror of promotion_in_flight_ - promotion_admitted_total: tasks past all gates, enqueued - promotion_completed_total: NotifyPromotionSuccess success - promotion_completed_bytes_total: bytes promoted (sum of source object_size) - promotion_failed_total: NotifyPromotionFailure accepted - promotion_expired_total: reaper Part 4 sweeps Rejection (per gate): - promotion_rejected_frequency_total: below admission threshold - promotion_rejected_watermark_total: DRAM at or above high watermark - promotion_rejected_cap_total: promotion_in_flight at queue limit Together: admitted = completed + failed + expired + in_flight. Wires a working 'promotion feature health' Grafana panel: rate(admitted) vs rate(completed) shows yield; rate(rejected_*) breaks down where work is dropping; in_flight / promotion_queue_limit shows saturation. promotion_max_per_heartbeat knob -------------------------------- The previous compile-time constant kMaxPerHeartbeat = 1 (in PromotionObjectHeartbeat) capped per-client throughput at ~6 promotions/min with the default 10s heartbeat, making promotion_queue_limit = 50000 mostly theoretical. Exposed as MasterServiceConfig::promotion_max_per_heartbeat, wired through the flag parser (FLAGS_promotion_max_per_heartbeat) and default_config.GetUInt32 path. Constructor clamps 0 -> 1 so a mistyped config doesn't silently halt promotion delivery. Startup log now includes max_per_heartbeat= alongside threshold= / queue_limit=. Default stays 1 (no behavior change on existing deployments). Operators with small objects + RDMA-rich clusters can raise it; the new MaxPerHeartbeatKnobControlsBatchSize test exercises 3. Tests ----- - MetricsFunnelTracksSuccessfulPromotion: single full lifecycle bumps admitted/completed/completed_bytes correctly and brings in_flight back to baseline. - MetricsRejectionCountersIncrementOnGateMiss: each of frequency and cap counters increments when its branch fires. - MaxPerHeartbeatKnobControlsBatchSize: knob=3, 5 admitted tasks drain across 3+2+0 heartbeats. - MaxPerHeartbeatZeroClampsToOne: pathological config clamps to 1. Suite: 32/32 pass (was 28; +4 new). No behavior change in existing tests. * [Store] L2->L1 promotion-on-hit: cover watermark gate in rejection test MetricsRejectionCountersIncrementOnGateMiss claimed coverage of all three rejection counters but only exercised frequency and cap. Add a sub-case that forces the watermark gate by configuring eviction_high_watermark_ratio = 0.0, asserting that promotion_rejected_watermark_total increments. * [Store] L2->L1 promotion-on-hit: cover RemoveAll/BatchRemove cleanup upstream kvcache-ai#2180 introduced EraseMetadataEntry as the centralized metadata-erase helper and routed RemoveAll, BatchRemove, and RemoveByRegex through it, so promotion_tasks cleanup on those paths is already correct on main. The metric instrumentation (dec_promotion_in_flight + inc_promotion_cancelled) is wired into EraseMetadataEntry in this branch's earlier commit so every site that erases metadata bumps the funnel counters consistently. Add regression tests for the three paths so any future refactor that reintroduces a metadata.erase without going through EraseMetadataEntry will fail the suite: - RemoveAllErasesPromotionTask - BatchRemoveErasesPromotionTask (normal-completion branch) - BatchRemoveStaleHandleErasesPromotionTask * [Store] L2->L1 promotion-on-hit: bump reaper-test sleep margin to 3s Three tests configure put_start_release_timeout_sec=1 and then sleep 2s waiting for the eviction-thread reaper to expire the promotion task. The reaper schedule is `now - last_discard_time > put_start_release_timeout_sec_` (strict greater-than), so a 1s release with a 2s sleep leaves only ~1s margin between the reaper firing and the assertion. CI runs observed intermittent failures when scheduling jitter erased that margin. Bump the sleep to 3s in the three affected tests so the margin is ~2s. Configuration values unchanged. Affected: - StalePromotionReaper - RemoveDuringPromotion - AllocStartRejectsReapedTask Verified 5 consecutive clean runs of all three under -j1 build.
Description
Ref #2031
Follow-up to #2071 (L2->L1 promotion-on-hit, merged as da70ac9). Adds Prometheus metrics for the promotion funnel and exposes the previously-hardcoded
kMaxPerHeartbeatas a config knob.Metrics (Prometheus,
master_promotion_*prefix)Funnel — answers "is the feature firing, and where does work get lost?":
master_promotion_in_flightmaster_promotion_admitted_totalmaster_promotion_completed_totalNotifyPromotionSuccesssuccessmaster_promotion_completed_bytes_totalobject_size)master_promotion_failed_totalNotifyPromotionFailureaccepted (holder-reported failure)master_promotion_cancelled_totalRemove/Upsert, holder client expired, orNotifyPromotionSuccesscould not commit the staged replica)master_promotion_expired_totalput_start_release_timeout_secInvariant:
admitted = completed + failed + cancelled + expired + in_flight.failedvscancelledare intentionally separate so dashboards can distinguish holder-side failures (network/IO/transfer error reported viaNotifyPromotionFailure) from upstream-side cancellation (the thing being promoted no longer exists).Rejection — answers "why aren't we admitting?":
master_promotion_rejected_frequency_totalpromotion_admission_thresholdmaster_promotion_rejected_watermark_totalmaster_promotion_rejected_cap_totalpromotion_in_flightatpromotion_queue_limitEnough to build a working Grafana panel:
rate(admitted)vsrate(completed)shows yield;rate(rejected_*)breaks down where work is dropping;in_flight / promotion_queue_limitshows saturation.promotion_max_per_heartbeatknobWas a compile-time
constexpr kMaxPerHeartbeat = 1inPromotionObjectHeartbeat. With the default 10s client heartbeat, this capped per-client throughput at ~6 promotions/min and madepromotion_queue_limit = 50000mostly theoretical.Exposed as
MasterServiceConfig::promotion_max_per_heartbeat(default 1, no behavior change). Wired through:DEFINE_uint32(promotion_max_per_heartbeat, ...)flag inmaster.cppdefault_config.GetUInt32(...)parse from JSONMasterServiceSupervisorConfigplumbingMasterServiceconstructor stores intopromotion_max_per_heartbeat_, used inPromotionObjectHeartbeat0 → 1(defense-in-depth —0would silently halt promotion delivery)max_per_heartbeat=alongsidethreshold=/queue_limit=Operators with small objects and RDMA-rich clusters can safely raise it; the new
MaxPerHeartbeatKnobControlsBatchSizetest exercises=3.Module
mooncake-transfer-engine)mooncake-store)mooncake-ep)mooncake-integration)mooncake-p2p-store)mooncake-wheel)mooncake-pg)mooncake-rl)Type of Change
How Has This Been Tested?
promotion_on_hit_test: 33/33 pass (was 28; +5 new), 5 consecutive clean runs.New tests:
MetricsFunnelTracksSuccessfulPromotion— single full lifecycle bumpsadmitted/completed/completed_bytescorrectly and returnsin_flightto baseline.MetricsRejectionCountersIncrementOnGateMiss— verifies all three rejection counters (rejected_frequency,rejected_watermark,rejected_cap) increment when their respective branches fire. Watermark sub-case useseviction_high_watermark_ratio = 0.0to force the gate.MetricsRemoveMidPromotionCountsAsCancelled— verifies that erasing a key with an in-flight promotion (viaRemove) routes the task topromotion_cancelled_totalrather thanpromotion_failed_total.MaxPerHeartbeatKnobControlsBatchSize— sets knob to 3, admits 5 tasks, asserts heartbeats drain as 3 + 2 + 0.MaxPerHeartbeatZeroClampsToOne— pathological=0config gets clamped to 1 by the constructor; promotion delivery is not silently disabled.Existing tests enhanced with metric assertions:
StalePromotionReaper— assertspromotion_expiredincrements.RemoveDuringPromotion— assertspromotion_cancelledincrements.QueueLimitRejectsBeyondCap— assertspromotion_rejected_capincrements.NotifyFailureReleasesStateImmediately— assertspromotion_failedincrements.ClientExpiryClearsPromotionTask— assertspromotion_cancelledincrements.Regression check:
file_storage_promotion_test: 11/11 passmaster_service_promotion_test_for_snapshot: 5/5 passoffload_on_evict_test: 9/9 passscripts/code_format.sh --check --base upstream/main(clang-format-20 in CI image): clean.Default
promotion_max_per_heartbeat = 1means existing deployments see no behavior change; all metrics zero-emit on cold start so dashboards built against them work immediately.Checklist
./scripts/code_format.shbefore submitting.