Skip to content

Commit e2d6708

Browse files
committed
[manager] harden EventReport GC lifecycle and accounting
1 parent 89adfa5 commit e2d6708

17 files changed

Lines changed: 472 additions & 80 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
@@ -485,16 +485,38 @@ bool LRUCacheShard::ApplyToEntryNoTouch(
485485
return false;
486486
}
487487
assert(e->InCache());
488+
const bool in_lru = !e->HasRefs();
488489
const ssize_t delta = callback(e->value, e->total_charge, e->helper);
489490
if (delta > 0) {
490-
e->total_charge += static_cast<size_t>(delta);
491-
usage_ += static_cast<size_t>(delta);
491+
const size_t increase = static_cast<size_t>(delta);
492+
e->total_charge += increase;
493+
usage_ += increase;
494+
if (in_lru) {
495+
lru_usage_ += increase;
496+
if (e->InHighPriPool()) {
497+
high_pri_pool_usage_ += increase;
498+
} else if (e->InLowPriPool()) {
499+
low_pri_pool_usage_ += increase;
500+
}
501+
MaintainPoolSize();
502+
}
492503
} else if (delta < 0) {
493504
size_t decrease = static_cast<size_t>(-delta);
494505
decrease = std::min(decrease, e->total_charge);
495506
decrease = std::min(decrease, usage_);
496507
e->total_charge -= decrease;
497508
usage_ -= decrease;
509+
if (in_lru) {
510+
assert(lru_usage_ >= decrease);
511+
lru_usage_ -= decrease;
512+
if (e->InHighPriPool()) {
513+
assert(high_pri_pool_usage_ >= decrease);
514+
high_pri_pool_usage_ -= decrease;
515+
} else if (e->InLowPriPool()) {
516+
assert(low_pri_pool_usage_ >= decrease);
517+
low_pri_pool_usage_ -= decrease;
518+
}
519+
}
498520
}
499521
return true;
500522
}

kv_cache_manager/common/test/lru_cache_test.cc

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,33 @@ TEST_F(LRUCacheTest, BasicLRU) {
160160
ValidateLRUList({"e", "z", "d", "u", "v"}, 0, 5);
161161
}
162162

163+
TEST_F(LRUCacheTest, ApplyToEntryNoTouchKeepsUnpinnedPoolChargeConsistent) {
164+
NewCache(100, /* high_pri_pool_ratio */ 0.50, /* low_pri_pool_ratio */ 0.50);
165+
Insert("a", Cache::Priority::HIGH, 20);
166+
ASSERT_EQ(20u, cache_->GetUsage());
167+
ASSERT_EQ(0u, cache_->GetPinnedUsage());
168+
ValidateLRUList({"a"}, 1, 0, 0);
169+
170+
ASSERT_TRUE(cache_->ApplyToEntryNoTouch(
171+
"a", 0, [](Cache::ObjectPtr, size_t, const Cache::CacheItemHelper *) { return -10; }));
172+
EXPECT_EQ(10u, cache_->GetUsage());
173+
EXPECT_EQ(0u, cache_->GetPinnedUsage());
174+
ValidateLRUList({"a"}, 1, 0, 0);
175+
176+
ASSERT_TRUE(cache_->ApplyToEntryNoTouch(
177+
"a", 0, [](Cache::ObjectPtr, size_t, const Cache::CacheItemHelper *) { return 20; }));
178+
EXPECT_EQ(30u, cache_->GetUsage());
179+
EXPECT_EQ(0u, cache_->GetPinnedUsage());
180+
ValidateLRUList({"a"}, 1, 0, 0);
181+
182+
// A later insertion exercises MaintainPoolSize and LRU_Remove using the
183+
// adjusted charge; stale pool counters would corrupt these transitions.
184+
Insert("b", Cache::Priority::HIGH, 30);
185+
EXPECT_EQ(60u, cache_->GetUsage());
186+
EXPECT_EQ(0u, cache_->GetPinnedUsage());
187+
ValidateLRUList({"a", "b"}, 1, 1, 0);
188+
}
189+
163190
TEST_F(LRUCacheTest, LowPriorityMidpointInsertion) {
164191
// Allocate 2 cache entries to high-pri pool and 3 to low-pri pool.
165192
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 & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -414,10 +414,17 @@ CacheManager::CacheManager(std::shared_ptr<MetricsRegistry> metrics_registry,
414414

415415
CacheManager::~CacheManager() {
416416
if (cache_garbage_collector_) {
417-
cache_garbage_collector_->Stop();
418-
cache_garbage_collector_.reset();
417+
// Close intent admission before detaching callbacks. A liveness thread
418+
// may already have copied a callback, so the callback itself also uses
419+
// a weak GC reference and will either observe stopped admission or an
420+
// expired collector.
421+
cache_garbage_collector_->RequestStop();
419422
}
420423
ClearEventCleanupCallbacks();
424+
if (cache_garbage_collector_) {
425+
cache_garbage_collector_->Join();
426+
cache_garbage_collector_.reset();
427+
}
421428
StopRecoverRetryLoop();
422429
if (write_location_manager_) {
423430
write_location_manager_->Stop();
@@ -2546,12 +2553,15 @@ ErrorCode CacheManager::ReportEvent(RequestContext *request_context,
25462553
if (!event_backend->IsCleanupCallbackSet()) {
25472554
const std::weak_ptr<EventReportBackend> weak_backend = event_backend;
25482555
if (cache_garbage_collector_ && cache_garbage_collector_->IsEventReportCleanupEnabled()) {
2549-
event_backend->SetCleanupCallback([this, requested_type, weak_backend](const std::string &cleanup_instance,
2550-
const std::string &down_host,
2551-
uint64_t generation) {
2552-
if (auto backend = weak_backend.lock()) {
2553-
if (!cache_garbage_collector_->RegisterHostCleanupIntent(
2554-
cleanup_instance, requested_type, down_host, generation, backend)) {
2556+
const std::weak_ptr<CacheGarbageCollector> weak_gc = cache_garbage_collector_;
2557+
event_backend->SetCleanupCallback([requested_type, weak_backend, weak_gc](
2558+
const std::string &cleanup_instance,
2559+
const std::string &down_host,
2560+
uint64_t generation) {
2561+
if (auto backend = weak_backend.lock(); backend) {
2562+
auto gc = weak_gc.lock();
2563+
if (gc && !gc->RegisterHostCleanupIntent(
2564+
cleanup_instance, requested_type, down_host, generation, backend)) {
25552565
KVCM_INTERVAL_LOG_WARN(
25562566
10,
25572567
"EventReport liveness cleanup intent rejected, instance[%s] host[%s] generation[%" PRIu64
@@ -2954,7 +2964,8 @@ ErrorCode CacheManager::ReportEvent(RequestContext *request_context,
29542964
}
29552965
if (!has_host_down && event_backend->IsNodeRegistered(instance_id, host_ip_port) && cache_garbage_collector_ &&
29562966
cache_garbage_collector_->IsEventReportCleanupEnabled()) {
2957-
cache_garbage_collector_->CancelHostCleanupIntent(instance_id, requested_type, host_ip_port);
2967+
cache_garbage_collector_->CancelHostCleanupIntent(
2968+
instance_id, requested_type, host_ip_port, event_backend->GetNodeGeneration(instance_id, host_ip_port));
29582969
}
29592970

29602971
if (!block_to_add.empty()) {

kv_cache_manager/manager/meta_searcher.cc

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1973,6 +1973,7 @@ MetaSearcher::BatchDeleteLocationsForMaintenance(RequestContext *request_context
19731973
request_context, keys, location_ids_per_key, expected_location_values);
19741974
out.ec = result.ec;
19751975
out.shard_lock_wait_time_us = result.shard_lock_wait_time_us;
1976+
out.shard_lock_hold_time_us = result.shard_lock_hold_time_us;
19761977
out.per_location_results = std::move(result.per_location_results);
19771978
if (out.per_location_results.size() != keys.size()) {
19781979
out.ec = EC_ERROR;
@@ -1997,18 +1998,15 @@ MetaSearcher::BatchDeleteLocationsForMaintenance(RequestContext *request_context
19971998
for (size_t i = 0; i < out.per_location_results.size(); ++i) {
19981999
for (size_t j = 0; j < out.per_location_results[i].size(); ++j) {
19992000
auto &current = out.per_location_results[i][j];
2000-
if (current.ec == EC_OK || current.ec == EC_NOENT) {
2001-
if (prior_shape_valid) {
2002-
const auto &prior = (*prior_results)[i][j];
2003-
current.removed_from_hot = current.removed_from_hot || prior.removed_from_hot;
2004-
current.removed_from_persistent = current.removed_from_persistent || prior.removed_from_persistent;
2005-
current.reclaimed_hot_key = current.reclaimed_hot_key || prior.reclaimed_hot_key;
2006-
current.reclaimed_persistent_key =
2007-
current.reclaimed_persistent_key || prior.reclaimed_persistent_key;
2008-
}
2009-
needs_sync = needs_sync || current.removed_from_hot || current.removed_from_persistent ||
2010-
current.reclaimed_hot_key || current.reclaimed_persistent_key;
2011-
}
2001+
if (prior_shape_valid) {
2002+
const auto &prior = (*prior_results)[i][j];
2003+
current.removed_from_hot = current.removed_from_hot || prior.removed_from_hot;
2004+
current.removed_from_persistent = current.removed_from_persistent || prior.removed_from_persistent;
2005+
current.reclaimed_hot_key = current.reclaimed_hot_key || prior.reclaimed_hot_key;
2006+
current.reclaimed_persistent_key = current.reclaimed_persistent_key || prior.reclaimed_persistent_key;
2007+
}
2008+
needs_sync = needs_sync || current.removed_from_hot || current.removed_from_persistent ||
2009+
current.reclaimed_hot_key || current.reclaimed_persistent_key;
20122010
}
20132011
}
20142012

kv_cache_manager/manager/meta_searcher.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ class MetaSearcher {
181181
MaintenanceLocationDeleteResults per_location_results;
182182
bool sync_succeeded = false;
183183
int64_t shard_lock_wait_time_us = 0;
184+
int64_t shard_lock_hold_time_us = 0;
184185
};
185186
// Exact-value metadata-only maintenance deletion. The optional prior
186187
// result carries mutations from a previous Sync failure so accounting is

0 commit comments

Comments
 (0)