Skip to content

Commit 5a0246e

Browse files
committed
[manager] harden EventReport GC lifecycle and accounting
1 parent eed745c commit 5a0246e

17 files changed

Lines changed: 472 additions & 75 deletions

docs/design/event_report_background_gc.md

Lines changed: 17 additions & 14 deletions
Large diffs are not rendered by default.

kv_cache_manager/common/cache/lru_cache.cc

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -507,16 +507,38 @@ bool LRUCacheShard::ApplyToEntryNoTouch(
507507
return false;
508508
}
509509
assert(e->InCache());
510+
const bool in_lru = !e->HasRefs();
510511
const ssize_t delta = callback(e->value, e->total_charge, e->helper);
511512
if (delta > 0) {
512-
e->total_charge += static_cast<size_t>(delta);
513-
usage_ += static_cast<size_t>(delta);
513+
const size_t increase = static_cast<size_t>(delta);
514+
e->total_charge += increase;
515+
usage_ += increase;
516+
if (in_lru) {
517+
lru_usage_ += increase;
518+
if (e->InHighPriPool()) {
519+
high_pri_pool_usage_ += increase;
520+
} else if (e->InLowPriPool()) {
521+
low_pri_pool_usage_ += increase;
522+
}
523+
MaintainPoolSize();
524+
}
514525
} else if (delta < 0) {
515526
size_t decrease = static_cast<size_t>(-delta);
516527
decrease = std::min(decrease, e->total_charge);
517528
decrease = std::min(decrease, usage_);
518529
e->total_charge -= decrease;
519530
usage_ -= decrease;
531+
if (in_lru) {
532+
assert(lru_usage_ >= decrease);
533+
lru_usage_ -= decrease;
534+
if (e->InHighPriPool()) {
535+
assert(high_pri_pool_usage_ >= decrease);
536+
high_pri_pool_usage_ -= decrease;
537+
} else if (e->InLowPriPool()) {
538+
assert(low_pri_pool_usage_ >= decrease);
539+
low_pri_pool_usage_ -= decrease;
540+
}
541+
}
520542
}
521543
return true;
522544
}

kv_cache_manager/common/test/lru_cache_test.cc

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,33 @@ TEST_F(LRUCacheTest, BatchReleaseFreesEntryErasedWhilePinned) {
204204
ValidateLRUList({"b"}, 0, 1);
205205
}
206206

207+
TEST_F(LRUCacheTest, ApplyToEntryNoTouchKeepsUnpinnedPoolChargeConsistent) {
208+
NewCache(100, /* high_pri_pool_ratio */ 0.50, /* low_pri_pool_ratio */ 0.50);
209+
Insert("a", Cache::Priority::HIGH, 20);
210+
ASSERT_EQ(20u, cache_->GetUsage());
211+
ASSERT_EQ(0u, cache_->GetPinnedUsage());
212+
ValidateLRUList({"a"}, 1, 0, 0);
213+
214+
ASSERT_TRUE(cache_->ApplyToEntryNoTouch(
215+
"a", 0, [](Cache::ObjectPtr, size_t, const Cache::CacheItemHelper *) { return -10; }));
216+
EXPECT_EQ(10u, cache_->GetUsage());
217+
EXPECT_EQ(0u, cache_->GetPinnedUsage());
218+
ValidateLRUList({"a"}, 1, 0, 0);
219+
220+
ASSERT_TRUE(cache_->ApplyToEntryNoTouch(
221+
"a", 0, [](Cache::ObjectPtr, size_t, const Cache::CacheItemHelper *) { return 20; }));
222+
EXPECT_EQ(30u, cache_->GetUsage());
223+
EXPECT_EQ(0u, cache_->GetPinnedUsage());
224+
ValidateLRUList({"a"}, 1, 0, 0);
225+
226+
// A later insertion exercises MaintainPoolSize and LRU_Remove using the
227+
// adjusted charge; stale pool counters would corrupt these transitions.
228+
Insert("b", Cache::Priority::HIGH, 30);
229+
EXPECT_EQ(60u, cache_->GetUsage());
230+
EXPECT_EQ(0u, cache_->GetPinnedUsage());
231+
ValidateLRUList({"a", "b"}, 1, 1, 0);
232+
}
233+
207234
TEST_F(LRUCacheTest, LowPriorityMidpointInsertion) {
208235
// Allocate 2 cache entries to high-pri pool and 3 to low-pri pool.
209236
NewCache(5, /* high_pri_pool_ratio */ 0.40, /* low_pri_pool_ratio */ 0.60);

kv_cache_manager/manager/cache_garbage_collector.cc

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,8 @@ bool CacheGarbageCollector::RegisterHostCleanupIntent(const std::string &instanc
399399

400400
void CacheGarbageCollector::CancelHostCleanupIntent(const std::string &instance_id,
401401
DataStorageType storage_type,
402-
const std::string &host_ip_port) noexcept {
402+
const std::string &host_ip_port,
403+
uint64_t active_lifecycle_generation) noexcept {
403404
try {
404405
bool canceled = false;
405406
EventReportIntentType canceled_type = EventReportIntentType::kDownHost;
@@ -408,7 +409,12 @@ void CacheGarbageCollector::CancelHostCleanupIntent(const std::string &instance_
408409
const EventReportIntentKey key{instance_id, storage_type, host_ip_port};
409410
recovery_observations_.erase(key);
410411
const auto it = event_report_intents_.find(key);
411-
if (it != event_report_intents_.end() && it->second.type != EventReportIntentType::kStaleSnapshot) {
412+
// A liveness callback publishes DownHost before its
413+
// generation-checked unregister. An event from that same active
414+
// lifecycle must not erase the intent in this window. Only a
415+
// strictly newer REGISTER/recovery lifecycle invalidates it.
416+
if (it != event_report_intents_.end() && it->second.type != EventReportIntentType::kStaleSnapshot &&
417+
it->second.lifecycle_generation < active_lifecycle_generation) {
412418
canceled_type = it->second.type;
413419
event_report_intents_.erase(it);
414420
canceled = true;
@@ -824,6 +830,14 @@ bool CacheGarbageCollector::RunEventReportTick() {
824830
for (const auto &[instance_id, _] : event_report_retry_batches_) {
825831
active_instances.insert(instance_id);
826832
}
833+
// A pass owns its frozen intent snapshot until its cursor reaches base.
834+
// Keep scheduling it even if every live intent is canceled mid-pass;
835+
// otherwise a later intent would inherit the stale cursor and barrier.
836+
for (const auto &[instance_id, state] : event_report_scan_states_) {
837+
if (state.context) {
838+
active_instances.insert(instance_id);
839+
}
840+
}
827841
if (active_instances.empty()) {
828842
UpdateEventReportMetrics();
829843
return false;
@@ -965,6 +979,11 @@ bool CacheGarbageCollector::BeginEventReportPass(const std::string &instance_id,
965979
return false;
966980
}
967981

982+
auto indexer = meta_indexer_manager_->GetMetaIndexer(instance_id);
983+
if (!indexer || !indexer->IsMaintenanceDeleteReady()) {
984+
FailEventReportPass(instance_id, state, indexer ? "meta_recovering" : "indexer_missing");
985+
return false;
986+
}
968987
MetaSearcher *meta_searcher = meta_searcher_manager_->GetMetaSearcher(instance_id);
969988
if (!meta_searcher || !meta_searcher->SyncAllForMaintenance()) {
970989
FailEventReportPass(instance_id, state, meta_searcher ? "sync_all" : "searcher_missing");
@@ -1094,7 +1113,8 @@ std::vector<CacheGarbageCollector::EventReportDeleteTarget> CacheGarbageCollecto
10941113
if (backend->ParseLocationId(location_id, medium, host)) {
10951114
const EventReportIntentKey key{instance_id, location->type(), host};
10961115
if (backend->IsNodeRegistered(instance_id, host)) {
1097-
CancelHostCleanupIntent(instance_id, location->type(), host);
1116+
CancelHostCleanupIntent(
1117+
instance_id, location->type(), host, backend->GetNodeGeneration(instance_id, host));
10981118
} else {
10991119
std::lock_guard<std::mutex> lock(event_report_intent_mutex_);
11001120
recovery_observations_.try_emplace(
@@ -1297,6 +1317,8 @@ bool CacheGarbageCollector::ExecuteEventReportDeleteBatch(const std::string &ins
12971317
static_cast<double>(std::max<int64_t>(0, action_duration_ms));
12981318
metrics_registry_->GetGauge("cache_gc.event_report_last_action_shard_lock_wait_us") =
12991319
static_cast<double>(std::max<int64_t>(0, result.shard_lock_wait_time_us));
1320+
metrics_registry_->GetGauge("cache_gc.event_report_last_action_shard_lock_hold_us") =
1321+
static_cast<double>(std::max<int64_t>(0, result.shard_lock_hold_time_us));
13001322
} catch (...) { KVCM_LOG_ERROR("cache gc failed to record event report action metrics"); }
13011323

13021324
if (!result.sync_succeeded || (result.ec != EC_OK && result.ec != EC_MISMATCH)) {

kv_cache_manager/manager/cache_garbage_collector.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,8 @@ class CacheGarbageCollector {
109109
const std::shared_ptr<EventReportBackend> &backend) noexcept;
110110
void CancelHostCleanupIntent(const std::string &instance_id,
111111
DataStorageType storage_type,
112-
const std::string &host_ip_port) noexcept;
112+
const std::string &host_ip_port,
113+
uint64_t active_lifecycle_generation) noexcept;
113114

114115
private:
115116
using Clock = std::chrono::steady_clock;

kv_cache_manager/manager/cache_manager.cc

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -412,10 +412,17 @@ CacheManager::CacheManager(std::shared_ptr<MetricsRegistry> metrics_registry,
412412

413413
CacheManager::~CacheManager() {
414414
if (cache_garbage_collector_) {
415-
cache_garbage_collector_->Stop();
416-
cache_garbage_collector_.reset();
415+
// Close intent admission before detaching callbacks. A liveness thread
416+
// may already have copied a callback, so the callback itself also uses
417+
// a weak GC reference and will either observe stopped admission or an
418+
// expired collector.
419+
cache_garbage_collector_->RequestStop();
417420
}
418421
ClearEventCleanupCallbacks();
422+
if (cache_garbage_collector_) {
423+
cache_garbage_collector_->Join();
424+
cache_garbage_collector_.reset();
425+
}
419426
StopRecoverRetryLoop();
420427
DeactivateEventCleanupCallbacks();
421428
if (write_location_manager_) {
@@ -2777,7 +2784,7 @@ ErrorCode CacheManager::ReportEvent(RequestContext *request_context,
27772784
const std::string &cleanup_instance,
27782785
const std::string &down_host,
27792786
uint64_t generation) {
2780-
if (auto backend = weak_backend.lock()) {
2787+
if (auto backend = weak_backend.lock(); backend) {
27812788
auto gc = weak_gc.lock();
27822789
if (gc && !gc->RegisterHostCleanupIntent(
27832790
cleanup_instance, requested_type, down_host, generation, backend)) {
@@ -3671,7 +3678,8 @@ ErrorCode CacheManager::ReportEvent(RequestContext *request_context,
36713678
}
36723679
if (!has_host_down && event_backend->IsNodeRegistered(instance_id, host_ip_port) && cache_garbage_collector_ &&
36733680
cache_garbage_collector_->IsEventReportCleanupEnabled()) {
3674-
cache_garbage_collector_->CancelHostCleanupIntent(instance_id, requested_type, host_ip_port);
3681+
cache_garbage_collector_->CancelHostCleanupIntent(
3682+
instance_id, requested_type, host_ip_port, event_backend->GetNodeGeneration(instance_id, host_ip_port));
36753683
}
36763684

36773685
// MetaSearcher calls this once after the fused target-location read and
@@ -3906,6 +3914,14 @@ ErrorCode CacheManager::ReportEvent(RequestContext *request_context,
39063914
if (cache_garbage_collector_ && cache_garbage_collector_->IsEventReportCleanupEnabled()) {
39073915
cleanup_dispatched = cache_garbage_collector_->RegisterHostCleanupIntent(
39083916
instance_id, requested_type, host_ip_port, gen_at_trigger, event_backend);
3917+
if (!cleanup_dispatched) {
3918+
KVCM_LOG_WARN("trace_id [%s] | HOST_DOWN: failed to register GC cleanup intent for host [%s], "
3919+
"instance [%s], gen=%" PRIu64,
3920+
trace_id.c_str(),
3921+
host_ip_port.c_str(),
3922+
instance_id.c_str(),
3923+
gen_at_trigger);
3924+
}
39093925
} else {
39103926
const auto cleanup_state = event_cleanup_callback_state_;
39113927
uint64_t cleanup_epoch = 0;

kv_cache_manager/manager/meta_searcher.cc

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3529,6 +3529,7 @@ MetaSearcher::BatchDeleteLocationsForMaintenance(RequestContext *request_context
35293529
request_context, keys, location_ids_per_key, expected_location_values);
35303530
out.ec = result.ec;
35313531
out.shard_lock_wait_time_us = result.shard_lock_wait_time_us;
3532+
out.shard_lock_hold_time_us = result.shard_lock_hold_time_us;
35323533
out.per_location_results = std::move(result.per_location_results);
35333534
if (out.per_location_results.size() != keys.size()) {
35343535
out.ec = EC_ERROR;
@@ -3553,18 +3554,15 @@ MetaSearcher::BatchDeleteLocationsForMaintenance(RequestContext *request_context
35533554
for (size_t i = 0; i < out.per_location_results.size(); ++i) {
35543555
for (size_t j = 0; j < out.per_location_results[i].size(); ++j) {
35553556
auto &current = out.per_location_results[i][j];
3556-
if (current.ec == EC_OK || current.ec == EC_NOENT) {
3557-
if (prior_shape_valid) {
3558-
const auto &prior = (*prior_results)[i][j];
3559-
current.removed_from_hot = current.removed_from_hot || prior.removed_from_hot;
3560-
current.removed_from_persistent = current.removed_from_persistent || prior.removed_from_persistent;
3561-
current.reclaimed_hot_key = current.reclaimed_hot_key || prior.reclaimed_hot_key;
3562-
current.reclaimed_persistent_key =
3563-
current.reclaimed_persistent_key || prior.reclaimed_persistent_key;
3564-
}
3565-
needs_sync = needs_sync || current.removed_from_hot || current.removed_from_persistent ||
3566-
current.reclaimed_hot_key || current.reclaimed_persistent_key;
3567-
}
3557+
if (prior_shape_valid) {
3558+
const auto &prior = (*prior_results)[i][j];
3559+
current.removed_from_hot = current.removed_from_hot || prior.removed_from_hot;
3560+
current.removed_from_persistent = current.removed_from_persistent || prior.removed_from_persistent;
3561+
current.reclaimed_hot_key = current.reclaimed_hot_key || prior.reclaimed_hot_key;
3562+
current.reclaimed_persistent_key = current.reclaimed_persistent_key || prior.reclaimed_persistent_key;
3563+
}
3564+
needs_sync = needs_sync || current.removed_from_hot || current.removed_from_persistent ||
3565+
current.reclaimed_hot_key || current.reclaimed_persistent_key;
35683566
}
35693567
}
35703568

kv_cache_manager/manager/meta_searcher.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,7 @@ class MetaSearcher {
347347
MaintenanceLocationDeleteResults per_location_results;
348348
bool sync_succeeded = false;
349349
int64_t shard_lock_wait_time_us = 0;
350+
int64_t shard_lock_hold_time_us = 0;
350351
};
351352
// Exact-value metadata-only maintenance deletion. The optional prior
352353
// result carries mutations from a previous Sync failure so accounting is

0 commit comments

Comments
 (0)