From 10a5d2a1d3cd2aeb30d6ecc1e3a31a678403a694 Mon Sep 17 00:00:00 2001 From: Chameleon Cloud User Date: Wed, 19 Nov 2025 17:36:48 +0000 Subject: [PATCH 01/15] s3fifo skeleton --- cachelib/allocator/CMakeLists.txt | 1 + cachelib/allocator/CacheAllocator.h | 9 + .../allocator/CacheAllocatorS3FIFOCache.cpp | 21 + cachelib/allocator/CacheTraits.h | 8 + cachelib/allocator/ContainerTypes.cpp | 2 + cachelib/allocator/MMS3FIFO.h | 997 ++++++++++++++++++ cachelib/allocator/serialize/objects.thrift | 31 + 7 files changed, 1069 insertions(+) create mode 100644 cachelib/allocator/CacheAllocatorS3FIFOCache.cpp create mode 100644 cachelib/allocator/MMS3FIFO.h diff --git a/cachelib/allocator/CMakeLists.txt b/cachelib/allocator/CMakeLists.txt index aad79b0a52..ab8a4052d0 100644 --- a/cachelib/allocator/CMakeLists.txt +++ b/cachelib/allocator/CMakeLists.txt @@ -33,6 +33,7 @@ add_library (cachelib_allocator CacheAllocatorLru5BCacheWithSpinBuckets.cpp CacheAllocatorLruCache.cpp CacheAllocatorLruCacheWithSpinBuckets.cpp + CacheAllocatorS3FIFOCache.cpp CacheAllocatorTinyLFU5BCache.cpp CacheAllocatorTinyLFUCache.cpp CacheAllocatorWTinyLFU5BCache.cpp diff --git a/cachelib/allocator/CacheAllocator.h b/cachelib/allocator/CacheAllocator.h index 6082dee06d..807a23f511 100644 --- a/cachelib/allocator/CacheAllocator.h +++ b/cachelib/allocator/CacheAllocator.h @@ -6077,6 +6077,7 @@ namespace facebook::cachelib { extern template class CacheAllocator; extern template class CacheAllocator; extern template class CacheAllocator; +extern template class CacheAllocator; extern template class CacheAllocator; extern template class CacheAllocator; @@ -6098,6 +6099,14 @@ using LruAllocatorSpinBuckets = CacheAllocator; using Lru2QAllocator = CacheAllocator; using Lru5B2QAllocator = CacheAllocator; +// CacheAllocator with S3 FIFO eviction policy +// It maintains 2 queues, one for for probation and one for the main queue. +// New items are added to the probation queue, and if not accessed +// will be quickly removed from the cache to remove 1 hit wonders. +// If accessed while in probation, it will eventually be promoted to the main queue. +// Items in the tail of main queue will be reinserted if accessed. +using S3FIFOAllocator = CacheAllocator; + // CacheAllocator with Tiny LFU eviction policy // It has a window initially to gauage the frequency of accesses of newly // inserted items. And eventually it will onl admit items that are accessed diff --git a/cachelib/allocator/CacheAllocatorS3FIFOCache.cpp b/cachelib/allocator/CacheAllocatorS3FIFOCache.cpp new file mode 100644 index 0000000000..59e9431a99 --- /dev/null +++ b/cachelib/allocator/CacheAllocatorS3FIFOCache.cpp @@ -0,0 +1,21 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "cachelib/allocator/CacheAllocator.h" + +namespace facebook::cachelib { +template class CacheAllocator; +} diff --git a/cachelib/allocator/CacheTraits.h b/cachelib/allocator/CacheTraits.h index 26605ae989..674d76e910 100644 --- a/cachelib/allocator/CacheTraits.h +++ b/cachelib/allocator/CacheTraits.h @@ -18,6 +18,7 @@ #include "cachelib/allocator/ChainedHashTable.h" #include "cachelib/allocator/MM2Q.h" #include "cachelib/allocator/MMLru.h" +#include "cachelib/allocator/MMS3FIFO.h" #include "cachelib/allocator/MMTinyLFU.h" #include "cachelib/allocator/MMWTinyLFU.h" #include "cachelib/allocator/memory/CompressedPtr.h" @@ -54,6 +55,13 @@ struct Lru2QCacheTrait { using CompressedPtrType = CompressedPtr4B; }; +struct S3FIFOCacheTrait { + using MMType = MMS3FIFO; + using AccessType = ChainedHashTable; + using AccessTypeLocks = SharedMutexBuckets; + using CompressedPtrType = CompressedPtr4B; +}; + struct TinyLFUCacheTrait { using MMType = MMTinyLFU; using AccessType = ChainedHashTable; diff --git a/cachelib/allocator/ContainerTypes.cpp b/cachelib/allocator/ContainerTypes.cpp index e70f051b84..55b62a0dae 100644 --- a/cachelib/allocator/ContainerTypes.cpp +++ b/cachelib/allocator/ContainerTypes.cpp @@ -17,6 +17,7 @@ #include "cachelib/allocator/ChainedHashTable.h" #include "cachelib/allocator/MM2Q.h" #include "cachelib/allocator/MMLru.h" +#include "cachelib/allocator/MMS3FIFO.h" #include "cachelib/allocator/MMTinyLFU.h" #include "cachelib/allocator/MMWTinyLFU.h" namespace facebook::cachelib { @@ -26,6 +27,7 @@ const int MMLru::kId = 1; const int MM2Q::kId = 2; const int MMTinyLFU::kId = 3; const int MMWTinyLFU::kId = 4; +const int MMS3FIFO::kId = 5; // AccessType const int ChainedHashTable::kId = 1; diff --git a/cachelib/allocator/MMS3FIFO.h b/cachelib/allocator/MMS3FIFO.h new file mode 100644 index 0000000000..093f0f09c4 --- /dev/null +++ b/cachelib/allocator/MMS3FIFO.h @@ -0,0 +1,997 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +#include +#include +#pragma GCC diagnostic pop + +#include "cachelib/allocator/Cache.h" +#include "cachelib/allocator/CacheStats.h" +#include "cachelib/allocator/Util.h" +#include "cachelib/allocator/datastruct/MultiDList.h" +#include "cachelib/allocator/memory/serialize/gen-cpp2/objects_types.h" +#include "cachelib/common/CompilerUtils.h" +#include "cachelib/common/CountMinSketch.h" +#include "cachelib/common/Mutex.h" + +namespace facebook::cachelib { +// Implements the S3-FIFO cache eviction policy as described in +class MMS3FIFO { + public: + // unique identifier per MMType + static const int kId; + + // forward declaration; + template + using Hook = DListHook; + using SerializationType = serialization::MMS3FIFOObject; + using SerializationConfigType = serialization::MMS3FIFOConfig; + using SerializationTypeContainer = serialization::MMS3FIFOCollection; + + enum LruType { Main, Tiny, NumTypes }; + + // Config class for MMS3FIFO + struct Config { + // create from serialized config + explicit Config(SerializationConfigType configState) + : Config(*configState.lruRefreshTime(), + *configState.lruRefreshRatio(), + *configState.updateOnWrite(), + *configState.updateOnRead(), + *configState.tryLockUpdate(), + *configState.windowToCacheSizeRatio(), + *configState.tinySizePercent(), + *configState.mmReconfigureIntervalSecs(), + *configState.newcomerWinsOnTie()) {} + + // @param time the LRU refresh time in seconds. + // An item will be promoted only once in each lru refresh + // time depite the number of accesses it gets. + // @param udpateOnW whether to promote the item on write + // @param updateOnR whether to promote the item on read + Config(uint32_t time, bool updateOnW, bool updateOnR) + : Config(time, + updateOnW, + updateOnR, + /* try lock update */ false, + 16, + 1) {} + + // @param time the LRU refresh time in seconds. + // An item will be promoted only once in each lru + // refresh time depite the number of accesses it + // gets. + // @param udpateOnW whether to promote the item on write + // @param updateOnR whether to promote the item on read + // @param windowToCacheSize multiplier of window size to cache size + // @param tinySizePct percentage number of tiny size to overall size + Config(uint32_t time, + bool updateOnW, + bool updateOnR, + size_t windowToCacheSize, + size_t tinySizePct) + : Config(time, + updateOnW, + updateOnR, + /* try lock update */ false, + windowToCacheSize, + tinySizePct) {} + + // @param time the LRU refresh time in seconds. + // An item will be promoted only once in each lru + // refresh time depite the number of accesses it + // gets. + // @param udpateOnW whether to promote the item on write + // @param updateOnR whether to promote the item on read + // @param tryLockU whether to use a try lock when doing update. + // @param windowToCacheSize multiplier of window size to cache size + // @param tinySizePct percentage number of tiny size to overall size + Config(uint32_t time, + bool updateOnW, + bool updateOnR, + bool tryLockU, + size_t windowToCacheSize, + size_t tinySizePct) + : Config(time, + 0., + updateOnW, + updateOnR, + tryLockU, + windowToCacheSize, + tinySizePct) {} + + // @param time the LRU refresh time in seconds. + // An item will be promoted only once in each + // lru refresh time depite the number of + // accesses it gets. + // @param ratio the lru refresh ratio. The ratio times the + // oldest element's lifetime in warm queue + // would be the minimum value of LRU refresh + // time. + // @param udpateOnW whether to promote the item on write + // @param updateOnR whether to promote the item on read + // @param tryLockU whether to use a try lock when doing + // update. + // @param windowToCacheSize multiplier of window size to cache size + // @param tinySizePct percentage number of tiny size to overall + // size + Config(uint32_t time, + double ratio, + bool updateOnW, + bool updateOnR, + bool tryLockU, + size_t windowToCacheSize, + size_t tinySizePct) + : Config(time, + ratio, + updateOnW, + updateOnR, + tryLockU, + windowToCacheSize, + tinySizePct, + 0) {} + + // @param time the LRU refresh time in seconds. + // An item will be promoted only once in each + // lru refresh time depite the number of + // accesses it gets. + // @param ratio the lru refresh ratio. The ratio times the + // oldest element's lifetime in warm queue + // would be the minimum value of LRU refresh + // time. + // @param udpateOnW whether to promote the item on write + // @param updateOnR whether to promote the item on read + // @param tryLockU whether to use a try lock when doing + // update. + // @param windowToCacheSize multiplier of window size to cache size + // @param tinySizePct percentage number of tiny size to overall + // size + // @param mmReconfigureInterval Time interval for recalculating lru + // refresh time according to the ratio. + Config(uint32_t time, + double ratio, + bool updateOnW, + bool updateOnR, + bool tryLockU, + size_t windowToCacheSize, + size_t tinySizePct, + uint32_t mmReconfigureInterval) + : defaultLruRefreshTime(time), + lruRefreshRatio(ratio), + updateOnWrite(updateOnW), + updateOnRead(updateOnR), + tryLockUpdate(tryLockU), + windowToCacheSizeRatio(windowToCacheSize), + tinySizePercent(tinySizePct), + mmReconfigureIntervalSecs( + std::chrono::seconds(mmReconfigureInterval)) { + checkConfig(); + } + + // @param time the LRU refresh time in seconds. + // An item will be promoted only once in each + // lru refresh time depite the number of + // accesses it gets. + // @param ratio the lru refresh ratio. The ratio times the + // oldest element's lifetime in warm queue + // would be the minimum value of LRU refresh + // time. + // @param udpateOnW whether to promote the item on write + // @param updateOnR whether to promote the item on read + // @param tryLockU whether to use a try lock when doing + // update. + // @param windowToCacheSize multiplier of window size to cache size + // @param tinySizePct percentage number of tiny size to overall + // size + // @param mmReconfigureInterval Time interval for recalculating lru + // refresh time according to the ratio. + // @param newcomerWinsOnTie If true, new comer will replace existing + // item if their access frequencies tie. + Config(uint32_t time, + double ratio, + bool updateOnW, + bool updateOnR, + bool tryLockU, + size_t windowToCacheSize, + size_t tinySizePct, + uint32_t mmReconfigureInterval, + bool _newcomerWinsOnTie) + : defaultLruRefreshTime(time), + lruRefreshRatio(ratio), + updateOnWrite(updateOnW), + updateOnRead(updateOnR), + tryLockUpdate(tryLockU), + windowToCacheSizeRatio(windowToCacheSize), + tinySizePercent(tinySizePct), + mmReconfigureIntervalSecs( + std::chrono::seconds(mmReconfigureInterval)), + newcomerWinsOnTie(_newcomerWinsOnTie) { + checkConfig(); + } + + Config() = default; + Config(const Config& rhs) = default; + Config(Config&& rhs) = default; + + Config& operator=(const Config& rhs) = default; + Config& operator=(Config&& rhs) = default; + + void checkConfig() { + if (tinySizePercent < 1 || tinySizePercent > 50) { + throw std::invalid_argument( + folly::sformat("Invalid tiny cache size {}. Tiny cache size " + "must be between 1% and 50% of total cache size ", + tinySizePercent)); + } + if (windowToCacheSizeRatio < 2 || windowToCacheSizeRatio > 128) { + throw std::invalid_argument( + folly::sformat("Invalid window to cache size ratio {}. The ratio " + "must be between 2 and 128", + windowToCacheSizeRatio)); + } + } + + template + void addExtraConfig(Args...) {} + + // threshold value in seconds to compare with a node's update time to + // determine if we need to update the position of the node in the linked + // list. By default this is 60s to reduce the contention on the lru lock. + uint32_t defaultLruRefreshTime{60}; + uint32_t lruRefreshTime{defaultLruRefreshTime}; + + // ratio of LRU refresh time to the tail age. If a refresh time computed + // according to this ratio is larger than lruRefreshtime, we will adopt + // this one instead of the lruRefreshTime set. + double lruRefreshRatio{0.}; + + // whether the lru needs to be updated on writes for recordAccess. If + // false, accessing the cache for writes does not promote the cached item + // to the head of the lru. + bool updateOnWrite{false}; + + // whether the lru needs to be updated on reads for recordAccess. If + // false, accessing the cache for reads does not promote the cached item + // to the head of the lru. + bool updateOnRead{true}; + + // whether to tryLock or lock the lru lock when attempting promotion on + // access. If set, and tryLock fails, access will not result in promotion. + bool tryLockUpdate{false}; + + // The multiplier for window size given the cache size. + size_t windowToCacheSizeRatio{32}; + + // The size of tiny cache, as a percentage of the total size. + size_t tinySizePercent{10}; + + // Minimum interval between reconfigurations. If 0, reconfigure is never + // called. + std::chrono::seconds mmReconfigureIntervalSecs{}; + + // If true, then if an item in the tail of the Tiny queue ties with the + // item in the tail of the main queue, the item from Tiny (newcomer) will + // replace the item from Main. This is fine for a default, but for + // strictly scan patterns (access a key exactly once and move on), this + // is not a desirable behavior (we'll always cache miss). + bool newcomerWinsOnTie{true}; + }; + + // The container object which can be used to keep track of objects of type + // T. T must have a public member of type Hook. This object is wrapper + // around DList, is thread safe and can be accessed from multiple threads. + // The current implementation models an LRU using the above DList + // implementation. + template T::* HookPtr> + struct Container { + private: + using LruList = MultiDList; + using Mutex = folly::SpinLock; + using LockHolder = std::unique_lock; + using PtrCompressor = typename T::PtrCompressor; + using Time = typename Hook::Time; + using CompressedPtrType = typename T::CompressedPtrType; + using RefFlags = typename T::Flags; + + public: + Container() = default; + Container(Config c, PtrCompressor compressor) + : lru_(LruType::NumTypes, std::move(compressor)), + config_(std::move(c)) { + maybeGrowGhostLocked(); + lruRefreshTime_ = config_.lruRefreshTime; + nextReconfigureTime_ = + config_.mmReconfigureIntervalSecs.count() == 0 + ? std::numeric_limits