@@ -141,7 +141,7 @@ string EngineShard::TxQueueInfo::Format() const {
141141}
142142
143143EngineShard::Stats& EngineShard::Stats::operator +=(const EngineShard::Stats& o) {
144- static_assert (sizeof (Stats) == 96 );
144+ static_assert (sizeof (Stats) == 104 );
145145
146146#define ADD (x ) x += o.x
147147
@@ -157,6 +157,7 @@ EngineShard::Stats& EngineShard::Stats::operator+=(const EngineShard::Stats& o)
157157 ADD (total_heartbeat_expired_bytes);
158158 ADD (total_heartbeat_expired_calls);
159159 ADD (total_migrated_keys);
160+ ADD (total_update_expire_calls);
160161
161162#undef ADD
162163 return *this ;
@@ -301,6 +302,8 @@ std::optional<CollectedPageStats> EngineShard::DoDefrag(CollectPageStats collect
301302 return page_usage.CollectedStats ();
302303}
303304
305+ constexpr uint32_t kRunAtLowPriority = 0u ;
306+
304307// the memory defragmentation task is as follow:
305308// 1. Check if memory usage is high enough
306309// 2. Check if diff between commited and used memory is high enough
@@ -310,7 +313,6 @@ std::optional<CollectedPageStats> EngineShard::DoDefrag(CollectPageStats collect
310313// priority.
311314// otherwise lower the task priority so that it would not use the CPU when not required
312315uint32_t EngineShard::DefragTask () {
313- constexpr uint32_t kRunAtLowPriority = 0u ;
314316 if (!namespaces) {
315317 return kRunAtLowPriority ;
316318 }
@@ -326,6 +328,98 @@ uint32_t EngineShard::DefragTask() {
326328 return 6 ; // priority.
327329}
328330
331+ // TODO: this is wrong. we can do better by updating the expire base in DeleteExpiredStep.
332+ uint32_t EngineShard::UpdateExpiresTask () {
333+ if (!namespaces) {
334+ return kRunAtLowPriority ;
335+ }
336+
337+ DVLOG (1 ) << " EngineShard::UpdateExpiresTask shard_id: " << shard_id_;
338+
339+ DbSlice& db_slice = namespaces->GetDefaultNamespace ().GetDbSlice (shard_id ());
340+ uint64_t now_ms = GetCurrentTimeMs ();
341+
342+ // measure the age of the current expire base.
343+ unsigned base_age_sec = db_slice.FromAbsoluteTime (now_ms).duration_ms () / 1000 ;
344+
345+ const uint64_t kLowThresholdSec = 3600 * 24 * 2 ; // 2 days
346+ const uint64_t kHighThresholdSec = 3600 * 24 * 7 ; // 7 days
347+ if (update_expire_state_ == nullptr ) {
348+ if (base_age_sec < kLowThresholdSec ) {
349+ // no need to update expire base if period is less than a few days.
350+ return kRunAtLowPriority ;
351+ }
352+
353+ db_slice.NextExpireGen (now_ms);
354+ update_expire_state_ = new UpdateExpireState ();
355+ VLOG (1 ) << " shard " << shard_id_ << " updated expire base to " << now_ms
356+ << " , generation: " << db_slice.expire_gen_id ();
357+ }
358+
359+ DCHECK (update_expire_state_ != nullptr );
360+ if (base_age_sec > kHighThresholdSec ) {
361+ LOG_EVERY_T (ERROR, 3600 ) << " Expire base age is very high: " << base_age_sec;
362+ }
363+
364+ if (update_expire_state_->db_index < db_slice.db_array_size ()) {
365+ unsigned current_gen_id = db_slice.expire_gen_id ();
366+
367+ auto cb = [&](ExpireIterator it) {
368+ if (it->second .generation_id () != current_gen_id) {
369+ int64_t ms = db_slice.ExpireTime (it->second );
370+
371+ // only update if the expire time is in the future.
372+ // we rely on DeleteExpiredStep to delete expired keys because we may need to propagate
373+ // deletions to the journal.
374+ if (ms > int64_t (now_ms)) {
375+ DVLOG (2 ) << " Update expire generation from " << it->second .generation_id () << " to "
376+ << current_gen_id;
377+ auto prev = it->second ;
378+ it->second = db_slice.FromAbsoluteTime (ms);
379+ auto after = db_slice.FromAbsoluteTime (ms);
380+ DCHECK_EQ (current_gen_id, db_slice.expire_gen_id ());
381+ DCHECK_EQ (ms, db_slice.ExpireTime (it->second ));
382+
383+ stats_.total_update_expire_calls ++;
384+ } else {
385+ update_expire_state_->stale_entries ++;
386+ }
387+ }
388+ };
389+
390+ auto & expire_table = db_slice.GetDBTable (update_expire_state_->db_index )->expire ;
391+ unsigned iters = 0 ;
392+ do {
393+ auto next = expire_table.Traverse (detail::DashCursor (update_expire_state_->cursor ), cb);
394+ if (next) {
395+ update_expire_state_->cursor = next.token ();
396+ } else {
397+ // finished this db, move to the next
398+ update_expire_state_->cursor = 0 ;
399+ ++update_expire_state_->db_index ;
400+ while (update_expire_state_->db_index < db_slice.db_array_size () &&
401+ !db_slice.IsDbValid (update_expire_state_->db_index )) {
402+ ++update_expire_state_->db_index ;
403+ }
404+ }
405+ } while (update_expire_state_->cursor && ++iters < 100 );
406+ }
407+
408+ if (update_expire_state_->db_index >= db_slice.db_array_size ()) {
409+ if (update_expire_state_->stale_entries == 0 ) {
410+ // We went over all the items and not stale items were found, we are done.
411+ delete update_expire_state_;
412+ update_expire_state_ = nullptr ;
413+ return kRunAtLowPriority ;
414+ }
415+
416+ // Repeat the process if we found stale entries to update.
417+ *update_expire_state_ = {};
418+ }
419+
420+ return 5 ; // run again soon, moderate frequency.
421+ }
422+
329423EngineShard::EngineShard (util::ProactorBase* pb, mi_heap_t * heap)
330424 : txq_([](const Transaction* t) { return t->txid (); }),
331425 queue_ (kQueueLen , 1 , 1 ),
@@ -347,6 +441,8 @@ void EngineShard::Shutdown() {
347441
348442void EngineShard::StopPeriodicFiber () {
349443 ProactorBase::me ()->RemoveOnIdleTask (defrag_task_);
444+ ProactorBase::me ()->RemoveOnIdleTask (update_expire_base_task_);
445+
350446 fiber_heartbeat_periodic_done_.Notify ();
351447 if (fiber_heartbeat_periodic_.IsJoinable ()) {
352448 fiber_heartbeat_periodic_.Join ();
@@ -394,6 +490,7 @@ void EngineShard::StartPeriodicHeartbeatFiber(util::ProactorBase* pb) {
394490 RunFPeriodically (heartbeat, period_ms, " heartbeat" , &fiber_heartbeat_periodic_done_);
395491 });
396492 defrag_task_ = pb->AddOnIdleTask ([this ]() { return DefragTask (); });
493+ update_expire_base_task_ = pb->AddOnIdleTask ([this ]() { return UpdateExpiresTask (); });
397494}
398495
399496void EngineShard::StartPeriodicShardHandlerFiber (util::ProactorBase* pb,
@@ -605,8 +702,10 @@ void EngineShard::Heartbeat() {
605702
606703 // TODO: iterate over all namespaces
607704 DbSlice& db_slice = namespaces->GetDefaultNamespace ().GetDbSlice (shard_id ());
705+
608706 // Skip heartbeat if we are serializing a big value
609707 static auto start = std::chrono::system_clock::now ();
708+
610709 // Skip heartbeat if global transaction is in process.
611710 // This is determined by attempting to check if shard lock can be acquired.
612711 const bool can_acquire_global_lock = shard_lock ()->Check (IntentLock::Mode::EXCLUSIVE);
@@ -620,6 +719,7 @@ void EngineShard::Heartbeat() {
620719 }
621720 return ;
622721 }
722+
623723 start = std::chrono::system_clock::now ();
624724
625725 if (!IsReplica ()) { // Never run expiry/evictions on replica.
0 commit comments