@@ -20,6 +20,7 @@ limitations under the License.
2020#include < sys/stat.h>
2121
2222#include < algorithm>
23+ #include < numeric>
2324#include < sys/statvfs.h>
2425
2526#include " cache_store.h"
@@ -79,11 +80,7 @@ ICacheStore* FileCachePool::do_open(std::string_view pathname, int flags, mode_t
7980 return nullptr ;
8081 }
8182
82- // Check idle container first
83- auto idleIt = idleFileIndex_.find (pathname);
84- if (idleIt != idleFileIndex_.end ()) {
85- promoteFromIdle (idleIt);
86- }
83+ promoteToHot (pathname);
8784
8885 auto find = fileIndex_.find (pathname);
8986 if (find == fileIndex_.end ()) {
@@ -96,15 +93,7 @@ ICacheStore* FileCachePool::do_open(std::string_view pathname, int flags, mode_t
9693 find->second ->openCount ++;
9794 }
9895
99- // If LRU exceeds the limit, demote the tail (only if not open) to idle
100- while (lru_.size () > demoteThreshold_) {
101- auto tailIt = lru_.back ();
102- if (tailIt->second ->openCount == 0 ) {
103- demoteToIdle (tailIt);
104- } else {
105- break ;
106- }
107- }
96+ demoteToCold ();
10897
10998 return new FileCacheStore (this , localFile, refillUnit_, find);
11099}
@@ -139,10 +128,13 @@ int FileCachePool::stat(CacheStat* stat, std::string_view pathname) {
139128}
140129
141130int FileCachePool::evict (std::string_view filename) {
142- // Check idle container first
143- auto idleIt = idleFileIndex_.find (filename);
144- if (idleIt != idleFileIndex_.end ()) {
145- return evictIdleEntry (idleIt) >= 0 ? 0 : -1 ;
131+ // Check cold tiers first
132+ for (auto * tier : coldTiers_) {
133+ if (tier->contains (filename)) {
134+ auto freed = truncateAndUnlink (filename);
135+ tier->remove (filename);
136+ return freed >= 0 ? 0 : -1 ;
137+ }
146138 }
147139
148140 auto fileIter = fileIndex_.find (filename);
@@ -262,10 +254,18 @@ void FileCachePool::eviction() {
262254
263255 isFull_ = true ;
264256
265- // Phase 1: evict from idle tier first.
266- actualEvict -= evictIdleWhenFull (actualEvict);
257+ // Evict from cold tiers first in reverse order
258+ for (int i = coldTiers_.size () - 1 ; i >= 0 ; i--) {
259+ auto * tier = coldTiers_[i];
260+ while (actualEvict > 0 && !tier->empty () && !exit_) {
261+ auto name = tier->victim ();
262+ auto freed = truncateAndUnlink (name);
263+ tier->remove (name);
264+ if (freed >= 0 ) actualEvict -= freed;
265+ photon::thread_yield ();
266+ }
267+ }
267268
268- // Phase 2: fall back to LRU eviction when idle tier is exhausted
269269 while (actualEvict > 0 && !lru_.empty () && !exit_) {
270270 auto fileIter = lru_.back ();
271271 const auto & fileName = fileIter->first ;
@@ -347,34 +347,72 @@ int FileCachePool::insertFile(std::string_view file) {
347347 }
348348 auto fileSize = st.st_blocks * kDiskBlockSize ;
349349
350- if (lru_.size () >= demoteThreshold_) {
351- auto idleLruIt = idleLru_.push_front (idleFileIndex_.end ());
352- auto iter = idleFileIndex_.emplace (file, idleLruIt).first ;
353- idleLru_.front () = iter;
354- } else {
355- auto lruIter = lru_.push_front (fileIndex_.end ());
356- auto entry = std::unique_ptr<LruEntry>(new LruEntry{lruIter, 0 , fileSize});
357- auto iter = fileIndex_.emplace (file, std::move (entry)).first ;
358- lru_.front () = iter;
359- }
350+ auto lruIter = lru_.push_front (fileIndex_.end ());
351+ auto entry = std::unique_ptr<LruEntry>(new LruEntry{lruIter, 0 , fileSize});
352+ auto iter = fileIndex_.emplace (file, std::move (entry)).first ;
353+ lru_.front () = iter;
360354 totalUsed_ += fileSize;
355+
356+ demoteToCold ();
361357 return 0 ;
362358}
363359
364- // Demote a LRU entry (openCount must be 0) to the idle container.
365- void FileCachePool::demoteToIdle (FileNameMap::iterator iter) {
366- auto idleLruIt = idleLru_.push_front (idleFileIndex_.end ());
367- auto idleIndexIt = idleFileIndex_.emplace (iter->first , idleLruIt).first ;
368- idleLru_.front () = idleIndexIt;
360+ void FileCachePool::adaptThresholds () {
361+ uint64_t count = std::accumulate (tierHits_.begin (), tierHits_.end (), 0ULL );
362+ for (size_t i = 0 ; i + 1 < tierHits_.size (); i++) {
363+ if (count > 0 ) {
364+ double ratio = static_cast <double >(tierHits_[i]) / count;
365+ // High ratio => this tier absorbs the bulk of hits, it has room to shrink.
366+ // Low ratio => hits leak to colder tiers, grow it.
367+ if (ratio > 0.90 ) thresholds_[i].adapt (0.80 );
368+ else if (ratio < 0.50 ) thresholds_[i].adapt (1.25 );
369+ }
370+ count -= tierHits_[i];
371+ }
369372
370- lru_. remove (iter-> second -> lruIter ) ;
371- fileIndex_. erase (iter );
373+ promoteCount_ = 0 ;
374+ tierHits_. fill ( 0 );
372375}
373376
374- // Promote an idle entry back to the front of LRU.
375- void FileCachePool::promoteFromIdle (IdleFileNameMap::iterator idleIt) {
376- uint32_t idleLruIter = idleIt->second ;
377- const auto & filename = idleIt->first ;
377+ void FileCachePool::demoteToCold () {
378+ while (lru_.size () > thresholds_[0 ].value ) {
379+ auto tailIt = lru_.back ();
380+ if (tailIt->second ->openCount != 0 ) break ;
381+
382+ coldTiers_[0 ]->insert (tailIt->first );
383+ lru_.remove (tailIt->second ->lruIter );
384+ fileIndex_.erase (tailIt);
385+
386+ for (size_t i = 1 ; i < coldTiers_.size (); i++) {
387+ if (coldTiers_[i-1 ]->size () > thresholds_[i].value ) {
388+ auto key = coldTiers_[i-1 ]->victim ();
389+ coldTiers_[i]->insert (key);
390+ coldTiers_[i-1 ]->remove (key);
391+ } else break ;
392+ }
393+ }
394+ }
395+
396+ void FileCachePool::promoteToHot (std::string_view filename) {
397+ auto find = fileIndex_.find (filename);
398+ if (find != fileIndex_.end ()) {
399+ tierHits_[0 ]++;
400+ return ;
401+ }
402+
403+ bool found = false ;
404+ for (size_t i = 0 ; i < coldTiers_.size (); ++i) {
405+ if (coldTiers_[i]->contains (filename)) {
406+ tierHits_[i + 1 ]++;
407+ found = true ;
408+ coldTiers_[i]->remove (filename);
409+ break ;
410+ }
411+ }
412+ if (!found) return ;
413+
414+ promoteCount_++;
415+ if (promoteCount_ >= kPromotesPerAdapt ) adaptThresholds ();
378416
379417 struct stat st = {};
380418 uint64_t fileSize = 0 ;
@@ -386,15 +424,9 @@ void FileCachePool::promoteFromIdle(IdleFileNameMap::iterator idleIt) {
386424 auto entry = std::unique_ptr<LruEntry>(new LruEntry{lruIter, 0 , fileSize});
387425 auto iter = fileIndex_.emplace (filename, std::move (entry)).first ;
388426 lru_.front () = iter;
389-
390- idleLru_.remove (idleLruIter);
391- idleFileIndex_.erase (idleIt);
392427}
393428
394- // Evict the idle entry pointed to by idleIt; return freed bytes or -1 on error.
395- ssize_t FileCachePool::evictIdleEntry (IdleFileNameMap::iterator idleIt) {
396- const auto & filename = idleIt->first ;
397-
429+ ssize_t FileCachePool::truncateAndUnlink (std::string_view filename) {
398430 struct stat st = {};
399431 uint64_t fileSize = 0 ;
400432 if (mediaFs_->stat (filename.data (), &st) == 0 ) {
@@ -414,22 +446,49 @@ ssize_t FileCachePool::evictIdleEntry(IdleFileNameMap::iterator idleIt) {
414446 // we still evict fileSize bytes even if unlink fails
415447 LOG_ERRNO_RETURN (0 , fileSize, " unlink failed, name : `" , filename);
416448 }
417-
418- uint32_t idleLruIter = idleIt->second ;
419- idleLru_.remove (idleLruIter);
420- idleFileIndex_.erase (idleIt);
421449 return static_cast <ssize_t >(fileSize);
422450}
423451
424- uint64_t FileCachePool::evictIdleWhenFull (uint64_t needEvict) {
425- uint64_t evictSize = 0 ;
426- while (evictSize < needEvict && !idleLru_.empty () && !exit_) {
427- auto r = evictIdleEntry (idleLru_.back ());
428- if (r >= 0 ) evictSize += static_cast <uint64_t >(r);
429- photon::thread_yield ();
430- }
431- return evictSize;
452+ // --- InactiveCacheTier ---
453+ bool InactiveCacheTier::contains (std::string_view name) {
454+ return index_.find (name) != index_.end ();
432455}
433456
457+ void InactiveCacheTier::remove (std::string_view name) {
458+ auto it = index_.find (name);
459+ if (it == index_.end ()) return ;
460+ lru_.remove (it->second );
461+ index_.erase (it);
462+ }
463+
464+ void InactiveCacheTier::insert (std::string_view name) {
465+ auto lruIt = lru_.push_front (index_.end ());
466+ auto it = index_.emplace (name, lruIt).first ;
467+ lru_.front () = it;
468+ }
469+
470+ size_t InactiveCacheTier::size () { return index_.size (); }
471+ bool InactiveCacheTier::empty () { return lru_.empty (); }
472+ std::string_view InactiveCacheTier::victim () { return lru_.back ()->first ; }
473+
474+ // --- IdleCacheTier ---
475+ bool IdleCacheTier::contains (std::string_view name) {
476+ return index_.find (std::string (name)) != index_.end ();
477+ }
478+
479+ void IdleCacheTier::remove (std::string_view name) {
480+ auto it = index_.find (std::string (name));
481+ if (it == index_.end ()) return ;
482+ index_.erase (it);
483+ }
484+
485+ void IdleCacheTier::insert (std::string_view name) {
486+ index_.emplace (name);
487+ }
488+
489+ size_t IdleCacheTier::size () { return index_.size (); }
490+ bool IdleCacheTier::empty () { return index_.empty (); }
491+ std::string_view IdleCacheTier::victim () { return *index_.begin (); }
492+
434493}
435494}
0 commit comments