secondary_cache_adapter.cc 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750
  1. // Copyright (c) Meta Platforms, Inc. and affiliates.
  2. // This source code is licensed under both the GPLv2 (found in the
  3. // COPYING file in the root directory) and Apache 2.0 License
  4. // (found in the LICENSE.Apache file in the root directory).
  5. #include "cache/secondary_cache_adapter.h"
  6. #include <atomic>
  7. #include "cache/tiered_secondary_cache.h"
  8. #include "monitoring/perf_context_imp.h"
  9. #include "test_util/sync_point.h"
  10. #include "util/cast_util.h"
  11. namespace ROCKSDB_NAMESPACE {
  12. namespace {
  13. // A distinct pointer value for marking "dummy" cache entries
  14. struct Dummy {
  15. char val[7] = "kDummy";
  16. };
  17. const Dummy kDummy{};
  18. Cache::ObjectPtr const kDummyObj = const_cast<Dummy*>(&kDummy);
  19. const char* kTieredCacheName = "TieredCache";
  20. } // namespace
  21. // When CacheWithSecondaryAdapter is constructed with the distribute_cache_res
  22. // parameter set to true, it manages the entire memory budget across the
  23. // primary and secondary cache. The secondary cache is assumed to be in
  24. // memory, such as the CompressedSecondaryCache. When a placeholder entry
  25. // is inserted by a CacheReservationManager instance to reserve memory,
  26. // the CacheWithSecondaryAdapter ensures that the reservation is distributed
  27. // proportionally across the primary/secondary caches.
  28. //
  29. // The primary block cache is initially sized to the sum of the primary cache
  30. // budget + the secondary cache budget, as follows -
  31. // |--------- Primary Cache Configured Capacity -----------|
  32. // |---Secondary Cache Budget----|----Primary Cache Budget-----|
  33. //
  34. // A ConcurrentCacheReservationManager member in the CacheWithSecondaryAdapter,
  35. // pri_cache_res_,
  36. // is used to help with tracking the distribution of memory reservations.
  37. // Initially, it accounts for the entire secondary cache budget as a
  38. // reservation against the primary cache. This shrinks the usable capacity of
  39. // the primary cache to the budget that the user originally desired.
  40. //
  41. // |--Reservation for Sec Cache--|-Pri Cache Usable Capacity---|
  42. //
  43. // When a reservation placeholder is inserted into the adapter, it is inserted
  44. // directly into the primary cache. This means the entire charge of the
  45. // placeholder is counted against the primary cache. To compensate and count
  46. // a portion of it against the secondary cache, the secondary cache Deflate()
  47. // method is called to shrink it. Since the Deflate() causes the secondary
  48. // actual usage to shrink, it is reflected here by releasing an equal amount
  49. // from the pri_cache_res_ reservation. The Deflate() in the secondary cache
  50. // can be, but is not required to be, implemented using its own cache
  51. // reservation manager.
  52. //
  53. // For example, if the pri/sec ratio is 70/30, and the combined capacity is
  54. // 100MB, the intermediate and final state after inserting a reservation
  55. // placeholder for 10MB would be as follows -
  56. //
  57. // |-Reservation for Sec Cache-|-Pri Cache Usable Capacity-|---R---|
  58. // 1. After inserting the placeholder in primary
  59. // |------- 30MB -------------|------- 60MB -------------|-10MB--|
  60. // 2. After deflating the secondary and adjusting the reservation for
  61. // secondary against the primary
  62. // |------- 27MB -------------|------- 63MB -------------|-10MB--|
  63. //
  64. // Likewise, when the user inserted placeholder is released, the secondary
  65. // cache Inflate() method is called to grow it, and the pri_cache_res_
  66. // reservation is increased by an equal amount.
  67. //
  68. // Another way of implementing this would have been to simply split the user
  69. // reservation into primary and secondary components. However, this would
  70. // require allocating a structure to track the associated secondary cache
  71. // reservation, which adds some complexity and overhead.
  72. //
  73. CacheWithSecondaryAdapter::CacheWithSecondaryAdapter(
  74. std::shared_ptr<Cache> target,
  75. std::shared_ptr<SecondaryCache> secondary_cache,
  76. TieredAdmissionPolicy adm_policy, bool distribute_cache_res)
  77. : CacheWrapper(std::move(target)),
  78. secondary_cache_(std::move(secondary_cache)),
  79. adm_policy_(adm_policy),
  80. distribute_cache_res_(distribute_cache_res),
  81. placeholder_usage_(0),
  82. reserved_usage_(0),
  83. sec_reserved_(0) {
  84. target_->SetEvictionCallback(
  85. [this](const Slice& key, Handle* handle, bool was_hit) {
  86. return EvictionHandler(key, handle, was_hit);
  87. });
  88. if (distribute_cache_res_) {
  89. size_t sec_capacity = 0;
  90. pri_cache_res_ = std::make_shared<ConcurrentCacheReservationManager>(
  91. std::make_shared<CacheReservationManagerImpl<CacheEntryRole::kMisc>>(
  92. target_));
  93. Status s = secondary_cache_->GetCapacity(sec_capacity);
  94. assert(s.ok());
  95. // Initially, the primary cache is sized to uncompressed cache budget plsu
  96. // compressed secondary cache budget. The secondary cache budget is then
  97. // taken away from the primary cache through cache reservations. Later,
  98. // when a placeholder entry is inserted by the caller, its inserted
  99. // into the primary cache and the portion that should be assigned to the
  100. // secondary cache is freed from the reservation.
  101. s = pri_cache_res_->UpdateCacheReservation(sec_capacity);
  102. assert(s.ok());
  103. sec_cache_res_ratio_ = (double)sec_capacity / target_->GetCapacity();
  104. }
  105. }
  106. CacheWithSecondaryAdapter::~CacheWithSecondaryAdapter() {
  107. // `*this` will be destroyed before `*target_`, so we have to prevent
  108. // use after free
  109. target_->SetEvictionCallback({});
  110. #ifndef NDEBUG
  111. if (distribute_cache_res_) {
  112. size_t sec_capacity = 0;
  113. Status s = secondary_cache_->GetCapacity(sec_capacity);
  114. assert(s.ok());
  115. assert(placeholder_usage_ == 0);
  116. assert(reserved_usage_ == 0);
  117. if (pri_cache_res_->GetTotalMemoryUsed() != sec_capacity) {
  118. fprintf(stdout,
  119. "~CacheWithSecondaryAdapter: Primary cache reservation: "
  120. "%zu, Secondary cache capacity: %zu, "
  121. "Secondary cache reserved: %zu\n",
  122. pri_cache_res_->GetTotalMemoryUsed(), sec_capacity,
  123. sec_reserved_);
  124. }
  125. }
  126. #endif // NDEBUG
  127. }
  128. bool CacheWithSecondaryAdapter::EvictionHandler(const Slice& key,
  129. Handle* handle, bool was_hit) {
  130. auto helper = GetCacheItemHelper(handle);
  131. if (helper->IsSecondaryCacheCompatible() &&
  132. adm_policy_ != TieredAdmissionPolicy::kAdmPolicyThreeQueue) {
  133. auto obj = target_->Value(handle);
  134. // Ignore dummy entry
  135. if (obj != kDummyObj) {
  136. bool force = false;
  137. if (adm_policy_ == TieredAdmissionPolicy::kAdmPolicyAllowCacheHits) {
  138. force = was_hit;
  139. } else if (adm_policy_ == TieredAdmissionPolicy::kAdmPolicyAllowAll) {
  140. force = true;
  141. }
  142. // Spill into secondary cache.
  143. secondary_cache_->Insert(key, obj, helper, force).PermitUncheckedError();
  144. }
  145. }
  146. // Never takes ownership of obj
  147. return false;
  148. }
  149. bool CacheWithSecondaryAdapter::ProcessDummyResult(Cache::Handle** handle,
  150. bool erase) {
  151. if (*handle && target_->Value(*handle) == kDummyObj) {
  152. target_->Release(*handle, erase);
  153. *handle = nullptr;
  154. return true;
  155. } else {
  156. return false;
  157. }
  158. }
  159. void CacheWithSecondaryAdapter::CleanupCacheObject(
  160. ObjectPtr obj, const CacheItemHelper* helper) {
  161. if (helper->del_cb) {
  162. helper->del_cb(obj, memory_allocator());
  163. }
  164. }
  165. Cache::Handle* CacheWithSecondaryAdapter::Promote(
  166. std::unique_ptr<SecondaryCacheResultHandle>&& secondary_handle,
  167. const Slice& key, const CacheItemHelper* helper, Priority priority,
  168. Statistics* stats, bool found_dummy_entry, bool kept_in_sec_cache) {
  169. assert(secondary_handle->IsReady());
  170. ObjectPtr obj = secondary_handle->Value();
  171. if (!obj) {
  172. // Nothing found.
  173. return nullptr;
  174. }
  175. // Found something.
  176. switch (helper->role) {
  177. case CacheEntryRole::kFilterBlock:
  178. RecordTick(stats, SECONDARY_CACHE_FILTER_HITS);
  179. break;
  180. case CacheEntryRole::kIndexBlock:
  181. RecordTick(stats, SECONDARY_CACHE_INDEX_HITS);
  182. break;
  183. case CacheEntryRole::kDataBlock:
  184. RecordTick(stats, SECONDARY_CACHE_DATA_HITS);
  185. break;
  186. default:
  187. break;
  188. }
  189. PERF_COUNTER_ADD(secondary_cache_hit_count, 1);
  190. RecordTick(stats, SECONDARY_CACHE_HITS);
  191. // Note: SecondaryCache::Size() is really charge (from the CreateCallback)
  192. size_t charge = secondary_handle->Size();
  193. Handle* result = nullptr;
  194. // Insert into primary cache, possibly as a standalone+dummy entries.
  195. if (secondary_cache_->SupportForceErase() && !found_dummy_entry) {
  196. // Create standalone and insert dummy
  197. // Allow standalone to be created even if cache is full, to avoid
  198. // reading the entry from storage.
  199. result =
  200. CreateStandalone(key, obj, helper, charge, /*allow_uncharged*/ true);
  201. assert(result);
  202. PERF_COUNTER_ADD(block_cache_standalone_handle_count, 1);
  203. // Insert dummy to record recent use
  204. // TODO: try to avoid case where inserting this dummy could overwrite a
  205. // regular entry
  206. Status s = Insert(key, kDummyObj, &kNoopCacheItemHelper, /*charge=*/0,
  207. /*handle=*/nullptr, priority);
  208. s.PermitUncheckedError();
  209. // Nothing to do or clean up on dummy insertion failure
  210. } else {
  211. // Insert regular entry into primary cache.
  212. // Don't allow it to spill into secondary cache again if it was kept there.
  213. Status s = Insert(
  214. key, obj, kept_in_sec_cache ? helper->without_secondary_compat : helper,
  215. charge, &result, priority);
  216. if (s.ok()) {
  217. assert(result);
  218. PERF_COUNTER_ADD(block_cache_real_handle_count, 1);
  219. } else {
  220. // Create standalone result instead, even if cache is full, to avoid
  221. // reading the entry from storage.
  222. result =
  223. CreateStandalone(key, obj, helper, charge, /*allow_uncharged*/ true);
  224. assert(result);
  225. PERF_COUNTER_ADD(block_cache_standalone_handle_count, 1);
  226. }
  227. }
  228. return result;
  229. }
  230. Status CacheWithSecondaryAdapter::Insert(const Slice& key, ObjectPtr value,
  231. const CacheItemHelper* helper,
  232. size_t charge, Handle** handle,
  233. Priority priority,
  234. const Slice& compressed_value,
  235. CompressionType type) {
  236. Status s = target_->Insert(key, value, helper, charge, handle, priority);
  237. if (s.ok() && value == nullptr && distribute_cache_res_ && handle) {
  238. charge = target_->GetCharge(*handle);
  239. MutexLock l(&cache_res_mutex_);
  240. placeholder_usage_ += charge;
  241. // Check if total placeholder reservation is more than the overall
  242. // cache capacity. If it is, then we don't try to charge the
  243. // secondary cache because we don't want to overcharge it (beyond
  244. // its capacity).
  245. // In order to make this a bit more lightweight, we also check if
  246. // the difference between placeholder_usage_ and reserved_usage_ is
  247. // atleast kReservationChunkSize and avoid any adjustments if not.
  248. if ((placeholder_usage_ <= target_->GetCapacity()) &&
  249. ((placeholder_usage_ - reserved_usage_) >= kReservationChunkSize)) {
  250. reserved_usage_ = placeholder_usage_ & ~(kReservationChunkSize - 1);
  251. size_t new_sec_reserved =
  252. static_cast<size_t>(reserved_usage_ * sec_cache_res_ratio_);
  253. size_t sec_charge = new_sec_reserved - sec_reserved_;
  254. s = secondary_cache_->Deflate(sec_charge);
  255. assert(s.ok());
  256. s = pri_cache_res_->UpdateCacheReservation(sec_charge,
  257. /*increase=*/false);
  258. assert(s.ok());
  259. sec_reserved_ += sec_charge;
  260. }
  261. }
  262. // Warm up the secondary cache with the compressed block. The secondary
  263. // cache may choose to ignore it based on the admission policy.
  264. if (value != nullptr && !compressed_value.empty() &&
  265. adm_policy_ == TieredAdmissionPolicy::kAdmPolicyThreeQueue &&
  266. helper->IsSecondaryCacheCompatible()) {
  267. Status status = secondary_cache_->InsertSaved(key, compressed_value, type);
  268. assert(status.ok() || status.IsNotSupported());
  269. }
  270. return s;
  271. }
  272. Cache::Handle* CacheWithSecondaryAdapter::Lookup(const Slice& key,
  273. const CacheItemHelper* helper,
  274. CreateContext* create_context,
  275. Priority priority,
  276. Statistics* stats) {
  277. // NOTE: we could just StartAsyncLookup() and Wait(), but this should be a bit
  278. // more efficient
  279. Handle* result =
  280. target_->Lookup(key, helper, create_context, priority, stats);
  281. bool secondary_compatible = helper && helper->IsSecondaryCacheCompatible();
  282. bool found_dummy_entry =
  283. ProcessDummyResult(&result, /*erase=*/secondary_compatible);
  284. if (!result && secondary_compatible) {
  285. // Try our secondary cache
  286. bool kept_in_sec_cache = false;
  287. std::unique_ptr<SecondaryCacheResultHandle> secondary_handle =
  288. secondary_cache_->Lookup(key, helper, create_context, /*wait*/ true,
  289. found_dummy_entry, stats,
  290. /*out*/ kept_in_sec_cache);
  291. if (secondary_handle) {
  292. result = Promote(std::move(secondary_handle), key, helper, priority,
  293. stats, found_dummy_entry, kept_in_sec_cache);
  294. }
  295. }
  296. return result;
  297. }
  298. bool CacheWithSecondaryAdapter::Release(Handle* handle,
  299. bool erase_if_last_ref) {
  300. if (erase_if_last_ref) {
  301. ObjectPtr v = target_->Value(handle);
  302. if (v == nullptr && distribute_cache_res_) {
  303. size_t charge = target_->GetCharge(handle);
  304. MutexLock l(&cache_res_mutex_);
  305. placeholder_usage_ -= charge;
  306. // Check if total placeholder reservation is more than the overall
  307. // cache capacity. If it is, then we do nothing as reserved_usage_ must
  308. // be already maxed out
  309. if ((placeholder_usage_ <= target_->GetCapacity()) &&
  310. (placeholder_usage_ < reserved_usage_)) {
  311. // Adjust reserved_usage_ in chunks of kReservationChunkSize, so
  312. // we don't hit this slow path too often.
  313. reserved_usage_ = placeholder_usage_ & ~(kReservationChunkSize - 1);
  314. size_t new_sec_reserved =
  315. static_cast<size_t>(reserved_usage_ * sec_cache_res_ratio_);
  316. size_t sec_charge = sec_reserved_ - new_sec_reserved;
  317. Status s = secondary_cache_->Inflate(sec_charge);
  318. assert(s.ok());
  319. s = pri_cache_res_->UpdateCacheReservation(sec_charge,
  320. /*increase=*/true);
  321. assert(s.ok());
  322. sec_reserved_ -= sec_charge;
  323. }
  324. }
  325. }
  326. return target_->Release(handle, erase_if_last_ref);
  327. }
  328. Cache::ObjectPtr CacheWithSecondaryAdapter::Value(Handle* handle) {
  329. ObjectPtr v = target_->Value(handle);
  330. // TODO with stacked secondaries: might fail in EvictionHandler
  331. assert(v != kDummyObj);
  332. return v;
  333. }
  334. void CacheWithSecondaryAdapter::StartAsyncLookupOnMySecondary(
  335. AsyncLookupHandle& async_handle) {
  336. assert(!async_handle.IsPending());
  337. assert(async_handle.result_handle == nullptr);
  338. std::unique_ptr<SecondaryCacheResultHandle> secondary_handle =
  339. secondary_cache_->Lookup(
  340. async_handle.key, async_handle.helper, async_handle.create_context,
  341. /*wait*/ false, async_handle.found_dummy_entry, async_handle.stats,
  342. /*out*/ async_handle.kept_in_sec_cache);
  343. if (secondary_handle) {
  344. // TODO with stacked secondaries: Check & process if already ready?
  345. async_handle.pending_handle = secondary_handle.release();
  346. async_handle.pending_cache = secondary_cache_.get();
  347. }
  348. }
  349. void CacheWithSecondaryAdapter::StartAsyncLookup(
  350. AsyncLookupHandle& async_handle) {
  351. target_->StartAsyncLookup(async_handle);
  352. if (!async_handle.IsPending()) {
  353. bool secondary_compatible =
  354. async_handle.helper &&
  355. async_handle.helper->IsSecondaryCacheCompatible();
  356. async_handle.found_dummy_entry |= ProcessDummyResult(
  357. &async_handle.result_handle, /*erase=*/secondary_compatible);
  358. if (async_handle.Result() == nullptr && secondary_compatible) {
  359. // Not found and not pending on another secondary cache
  360. StartAsyncLookupOnMySecondary(async_handle);
  361. }
  362. }
  363. }
  364. void CacheWithSecondaryAdapter::WaitAll(AsyncLookupHandle* async_handles,
  365. size_t count) {
  366. if (count == 0) {
  367. // Nothing to do
  368. return;
  369. }
  370. // Requests that are pending on *my* secondary cache, at the start of this
  371. // function
  372. std::vector<AsyncLookupHandle*> my_pending;
  373. // Requests that are pending on an "inner" secondary cache (managed somewhere
  374. // under target_), as of the start of this function
  375. std::vector<AsyncLookupHandle*> inner_pending;
  376. // Initial accounting of pending handles, excluding those already handled
  377. // by "outer" secondary caches. (See cur->pending_cache = nullptr.)
  378. for (size_t i = 0; i < count; ++i) {
  379. AsyncLookupHandle* cur = async_handles + i;
  380. if (cur->pending_cache) {
  381. assert(cur->IsPending());
  382. assert(cur->helper);
  383. assert(cur->helper->IsSecondaryCacheCompatible());
  384. if (cur->pending_cache == secondary_cache_.get()) {
  385. my_pending.push_back(cur);
  386. // Mark as "to be handled by this caller"
  387. cur->pending_cache = nullptr;
  388. } else {
  389. // Remember as potentially needing a lookup in my secondary
  390. inner_pending.push_back(cur);
  391. }
  392. }
  393. }
  394. // Wait on inner-most cache lookups first
  395. // TODO with stacked secondaries: because we are not using proper
  396. // async/await constructs here yet, there is a false synchronization point
  397. // here where all the results at one level are needed before initiating
  398. // any lookups at the next level. Probably not a big deal, but worth noting.
  399. if (!inner_pending.empty()) {
  400. target_->WaitAll(async_handles, count);
  401. }
  402. // For those that failed to find something, convert to lookup in my
  403. // secondary cache.
  404. for (AsyncLookupHandle* cur : inner_pending) {
  405. if (cur->Result() == nullptr) {
  406. // Not found, try my secondary
  407. StartAsyncLookupOnMySecondary(*cur);
  408. if (cur->IsPending()) {
  409. assert(cur->pending_cache == secondary_cache_.get());
  410. my_pending.push_back(cur);
  411. // Mark as "to be handled by this caller"
  412. cur->pending_cache = nullptr;
  413. }
  414. }
  415. }
  416. // Wait on all lookups on my secondary cache
  417. {
  418. std::vector<SecondaryCacheResultHandle*> my_secondary_handles;
  419. for (AsyncLookupHandle* cur : my_pending) {
  420. my_secondary_handles.push_back(cur->pending_handle);
  421. }
  422. secondary_cache_->WaitAll(std::move(my_secondary_handles));
  423. }
  424. // Process results
  425. for (AsyncLookupHandle* cur : my_pending) {
  426. std::unique_ptr<SecondaryCacheResultHandle> secondary_handle(
  427. cur->pending_handle);
  428. cur->pending_handle = nullptr;
  429. cur->result_handle = Promote(
  430. std::move(secondary_handle), cur->key, cur->helper, cur->priority,
  431. cur->stats, cur->found_dummy_entry, cur->kept_in_sec_cache);
  432. assert(cur->pending_cache == nullptr);
  433. }
  434. }
  435. std::string CacheWithSecondaryAdapter::GetPrintableOptions() const {
  436. std::string str = target_->GetPrintableOptions();
  437. str.append(" secondary_cache:\n");
  438. str.append(secondary_cache_->GetPrintableOptions());
  439. return str;
  440. }
  441. const char* CacheWithSecondaryAdapter::Name() const {
  442. if (distribute_cache_res_) {
  443. return kTieredCacheName;
  444. } else {
  445. // To the user, at least for now, configure the underlying cache with
  446. // a secondary cache. So we pretend to be that cache
  447. return target_->Name();
  448. }
  449. }
  450. // Update the total cache capacity. If we're distributing cache reservations
  451. // to both primary and secondary, then update the pri_cache_res_reservation
  452. // as well. At the moment, we don't have a good way of handling the case
  453. // where the new capacity < total cache reservations.
  454. void CacheWithSecondaryAdapter::SetCapacity(size_t capacity) {
  455. if (distribute_cache_res_) {
  456. MutexLock m(&cache_res_mutex_);
  457. size_t sec_capacity = static_cast<size_t>(capacity * sec_cache_res_ratio_);
  458. size_t old_sec_capacity = 0;
  459. Status s = secondary_cache_->GetCapacity(old_sec_capacity);
  460. if (!s.ok()) {
  461. return;
  462. }
  463. if (old_sec_capacity > sec_capacity) {
  464. // We're shrinking the cache. We do things in the following order to
  465. // avoid a temporary spike in usage over the configured capacity -
  466. // 1. Lower the secondary cache capacity
  467. // 2. Credit an equal amount (by decreasing pri_cache_res_) to the
  468. // primary cache
  469. // 3. Decrease the primary cache capacity to the total budget
  470. s = secondary_cache_->SetCapacity(sec_capacity);
  471. if (s.ok()) {
  472. if (placeholder_usage_ > capacity) {
  473. // Adjust reserved_usage_ down
  474. reserved_usage_ = capacity & ~(kReservationChunkSize - 1);
  475. }
  476. size_t new_sec_reserved =
  477. static_cast<size_t>(reserved_usage_ * sec_cache_res_ratio_);
  478. s = pri_cache_res_->UpdateCacheReservation(
  479. (old_sec_capacity - sec_capacity) -
  480. (sec_reserved_ - new_sec_reserved),
  481. /*increase=*/false);
  482. sec_reserved_ = new_sec_reserved;
  483. assert(s.ok());
  484. target_->SetCapacity(capacity);
  485. }
  486. } else {
  487. // We're expanding the cache. Do it in the following order to avoid
  488. // unnecessary evictions -
  489. // 1. Increase the primary cache capacity to total budget
  490. // 2. Reserve additional memory in primary on behalf of secondary (by
  491. // increasing pri_cache_res_ reservation)
  492. // 3. Increase secondary cache capacity
  493. target_->SetCapacity(capacity);
  494. s = pri_cache_res_->UpdateCacheReservation(
  495. sec_capacity - old_sec_capacity,
  496. /*increase=*/true);
  497. assert(s.ok());
  498. s = secondary_cache_->SetCapacity(sec_capacity);
  499. assert(s.ok());
  500. }
  501. } else {
  502. // No cache reservation distribution. Just set the primary cache capacity.
  503. target_->SetCapacity(capacity);
  504. }
  505. }
  506. Status CacheWithSecondaryAdapter::GetSecondaryCacheCapacity(
  507. size_t& size) const {
  508. return secondary_cache_->GetCapacity(size);
  509. }
  510. Status CacheWithSecondaryAdapter::GetSecondaryCachePinnedUsage(
  511. size_t& size) const {
  512. Status s;
  513. if (distribute_cache_res_) {
  514. MutexLock m(&cache_res_mutex_);
  515. size_t capacity = 0;
  516. s = secondary_cache_->GetCapacity(capacity);
  517. if (s.ok()) {
  518. size = capacity - pri_cache_res_->GetTotalMemoryUsed();
  519. } else {
  520. size = 0;
  521. }
  522. } else {
  523. size = 0;
  524. }
  525. return s;
  526. }
  527. // Update the secondary/primary allocation ratio (remember, the primary
  528. // capacity is the total memory budget when distribute_cache_res_ is true).
  529. // When the ratio changes, we may accumulate some error in the calculations
  530. // for secondary cache inflate/deflate and pri_cache_res_ reservations.
  531. // This is due to the rounding of the reservation amount.
  532. //
  533. // We rely on the current pri_cache_res_ total memory used to estimate the
  534. // new secondary cache reservation after the ratio change. For this reason,
  535. // once the ratio is lowered to 0.0 (effectively disabling the secondary
  536. // cache and pri_cache_res_ total mem used going down to 0), we cannot
  537. // increase the ratio and re-enable it, We might remove this limitation
  538. // in the future.
  539. Status CacheWithSecondaryAdapter::UpdateCacheReservationRatio(
  540. double compressed_secondary_ratio) {
  541. if (!distribute_cache_res_) {
  542. return Status::NotSupported();
  543. }
  544. MutexLock m(&cache_res_mutex_);
  545. size_t pri_capacity = target_->GetCapacity();
  546. size_t sec_capacity =
  547. static_cast<size_t>(pri_capacity * compressed_secondary_ratio);
  548. size_t old_sec_capacity = 0;
  549. Status s = secondary_cache_->GetCapacity(old_sec_capacity);
  550. if (!s.ok()) {
  551. return s;
  552. }
  553. // Calculate the new secondary cache reservation
  554. // reserved_usage_ will never be > the cache capacity, so we don't
  555. // have to worry about adjusting it here.
  556. sec_cache_res_ratio_ = compressed_secondary_ratio;
  557. size_t new_sec_reserved =
  558. static_cast<size_t>(reserved_usage_ * sec_cache_res_ratio_);
  559. if (sec_capacity > old_sec_capacity) {
  560. // We're increasing the ratio, thus ending up with a larger secondary
  561. // cache and a smaller usable primary cache capacity. Similar to
  562. // SetCapacity(), we try to avoid a temporary increase in total usage
  563. // beyond the configured capacity -
  564. // 1. A higher secondary cache ratio means it gets a higher share of
  565. // cache reservations. So first account for that by deflating the
  566. // secondary cache
  567. // 2. Increase pri_cache_res_ reservation to reflect the new secondary
  568. // cache utilization (increase in capacity - increase in share of cache
  569. // reservation)
  570. // 3. Increase secondary cache capacity
  571. assert(new_sec_reserved >= sec_reserved_);
  572. s = secondary_cache_->Deflate(new_sec_reserved - sec_reserved_);
  573. assert(s.ok());
  574. s = pri_cache_res_->UpdateCacheReservation(
  575. (sec_capacity - old_sec_capacity) - (new_sec_reserved - sec_reserved_),
  576. /*increase=*/true);
  577. assert(s.ok());
  578. sec_reserved_ = new_sec_reserved;
  579. s = secondary_cache_->SetCapacity(sec_capacity);
  580. assert(s.ok());
  581. } else {
  582. // We're shrinking the ratio. Try to avoid unnecessary evictions -
  583. // 1. Lower the secondary cache capacity
  584. // 2. Decrease pri_cache_res_ reservation to reflect lower secondary
  585. // cache utilization (decrease in capacity - decrease in share of cache
  586. // reservations)
  587. // 3. Inflate the secondary cache to give it back the reduction in its
  588. // share of cache reservations
  589. s = secondary_cache_->SetCapacity(sec_capacity);
  590. if (s.ok()) {
  591. s = pri_cache_res_->UpdateCacheReservation(
  592. (old_sec_capacity - sec_capacity) -
  593. (sec_reserved_ - new_sec_reserved),
  594. /*increase=*/false);
  595. assert(s.ok());
  596. s = secondary_cache_->Inflate(sec_reserved_ - new_sec_reserved);
  597. assert(s.ok());
  598. sec_reserved_ = new_sec_reserved;
  599. }
  600. }
  601. return s;
  602. }
  603. Status CacheWithSecondaryAdapter::UpdateAdmissionPolicy(
  604. TieredAdmissionPolicy adm_policy) {
  605. adm_policy_ = adm_policy;
  606. return Status::OK();
  607. }
  608. std::shared_ptr<Cache> NewTieredCache(const TieredCacheOptions& _opts) {
  609. if (!_opts.cache_opts) {
  610. return nullptr;
  611. }
  612. TieredCacheOptions opts = _opts;
  613. {
  614. bool valid_adm_policy = true;
  615. switch (_opts.adm_policy) {
  616. case TieredAdmissionPolicy::kAdmPolicyAuto:
  617. // Select an appropriate default policy
  618. if (opts.adm_policy == TieredAdmissionPolicy::kAdmPolicyAuto) {
  619. if (opts.nvm_sec_cache) {
  620. opts.adm_policy = TieredAdmissionPolicy::kAdmPolicyThreeQueue;
  621. } else {
  622. opts.adm_policy = TieredAdmissionPolicy::kAdmPolicyPlaceholder;
  623. }
  624. }
  625. break;
  626. case TieredAdmissionPolicy::kAdmPolicyPlaceholder:
  627. case TieredAdmissionPolicy::kAdmPolicyAllowCacheHits:
  628. case TieredAdmissionPolicy::kAdmPolicyAllowAll:
  629. if (opts.nvm_sec_cache) {
  630. valid_adm_policy = false;
  631. }
  632. break;
  633. case TieredAdmissionPolicy::kAdmPolicyThreeQueue:
  634. if (!opts.nvm_sec_cache) {
  635. valid_adm_policy = false;
  636. }
  637. break;
  638. default:
  639. valid_adm_policy = false;
  640. }
  641. if (!valid_adm_policy) {
  642. return nullptr;
  643. }
  644. }
  645. std::shared_ptr<Cache> cache;
  646. if (opts.cache_type == PrimaryCacheType::kCacheTypeLRU) {
  647. LRUCacheOptions cache_opts =
  648. *(static_cast_with_check<LRUCacheOptions, ShardedCacheOptions>(
  649. opts.cache_opts));
  650. cache_opts.capacity = opts.total_capacity;
  651. cache_opts.secondary_cache = nullptr;
  652. cache = cache_opts.MakeSharedCache();
  653. } else if (opts.cache_type == PrimaryCacheType::kCacheTypeHCC) {
  654. HyperClockCacheOptions cache_opts =
  655. *(static_cast_with_check<HyperClockCacheOptions, ShardedCacheOptions>(
  656. opts.cache_opts));
  657. cache_opts.capacity = opts.total_capacity;
  658. cache_opts.secondary_cache = nullptr;
  659. cache = cache_opts.MakeSharedCache();
  660. } else {
  661. return nullptr;
  662. }
  663. std::shared_ptr<SecondaryCache> sec_cache;
  664. opts.comp_cache_opts.capacity = static_cast<size_t>(
  665. opts.total_capacity * opts.compressed_secondary_ratio);
  666. sec_cache = NewCompressedSecondaryCache(opts.comp_cache_opts);
  667. if (opts.nvm_sec_cache) {
  668. if (opts.adm_policy == TieredAdmissionPolicy::kAdmPolicyThreeQueue) {
  669. sec_cache = std::make_shared<TieredSecondaryCache>(
  670. sec_cache, opts.nvm_sec_cache,
  671. TieredAdmissionPolicy::kAdmPolicyThreeQueue);
  672. } else {
  673. return nullptr;
  674. }
  675. }
  676. return std::make_shared<CacheWithSecondaryAdapter>(
  677. cache, sec_cache, opts.adm_policy, /*distribute_cache_res=*/true);
  678. }
  679. Status UpdateTieredCache(const std::shared_ptr<Cache>& cache,
  680. int64_t total_capacity,
  681. double compressed_secondary_ratio,
  682. TieredAdmissionPolicy adm_policy) {
  683. if (!cache || strcmp(cache->Name(), kTieredCacheName)) {
  684. return Status::InvalidArgument();
  685. }
  686. CacheWithSecondaryAdapter* tiered_cache =
  687. static_cast<CacheWithSecondaryAdapter*>(cache.get());
  688. Status s;
  689. if (total_capacity > 0) {
  690. tiered_cache->SetCapacity(total_capacity);
  691. }
  692. if (compressed_secondary_ratio >= 0.0 && compressed_secondary_ratio <= 1.0) {
  693. s = tiered_cache->UpdateCacheReservationRatio(compressed_secondary_ratio);
  694. }
  695. if (adm_policy < TieredAdmissionPolicy::kAdmPolicyMax) {
  696. s = tiered_cache->UpdateAdmissionPolicy(adm_policy);
  697. }
  698. return s;
  699. }
  700. } // namespace ROCKSDB_NAMESPACE