Summary: Add mid-point insertion functionality to LRU cache. Caller of `Cache::Insert()` can set an additional parameter to make a cache entry have higher priority. The LRU cache will reserve at most `capacity * high_pri_pool_pct` bytes for high-pri cache entries. If `high_pri_pool_pct` is zero, the cache degenerates to normal LRU cache. Context: If we are to put index and filter blocks into RocksDB block cache, index/filter block can be swap out too early. We want to add an option to RocksDB to reserve some capacity in block cache just for index/filter blocks, to mitigate the issue. In later diffs I'll update block based table reader to use the interface to cache index/filter blocks at high priority, and expose the option to `DBOptions` and make it dynamic changeable. Test Plan: unit test. Reviewers: IslamAbdelRahman, sdong, lightmark Reviewed By: lightmark Subscribers: andrewkr, dhruba, march, leveldb Differential Revision: https://reviews.facebook.net/D61977main
parent
6a17b07ca8
commit
72f8cc703c
@ -0,0 +1,163 @@ |
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#include "util/lru_cache.h" |
||||
|
||||
#include <string> |
||||
#include <vector> |
||||
#include "util/testharness.h" |
||||
|
||||
namespace rocksdb { |
||||
|
||||
class LRUCacheTest : public testing::Test { |
||||
public: |
||||
LRUCacheTest() {} |
||||
~LRUCacheTest() {} |
||||
|
||||
void NewCache(size_t capacity, double high_pri_pool_ratio = 0.0) { |
||||
cache_.reset(new LRUCacheShard()); |
||||
cache_->SetCapacity(capacity); |
||||
cache_->SetStrictCapacityLimit(false); |
||||
cache_->SetHighPriorityPoolRatio(high_pri_pool_ratio); |
||||
} |
||||
|
||||
void Insert(const std::string& key, |
||||
Cache::Priority priority = Cache::Priority::LOW) { |
||||
cache_->Insert(key, 0 /*hash*/, nullptr /*value*/, 1 /*charge*/, |
||||
nullptr /*deleter*/, nullptr /*handle*/, priority); |
||||
} |
||||
|
||||
void Insert(char key, Cache::Priority priority = Cache::Priority::LOW) { |
||||
Insert(std::string(1, key), priority); |
||||
} |
||||
|
||||
bool Lookup(const std::string& key) { |
||||
auto handle = cache_->Lookup(key, 0 /*hash*/); |
||||
if (handle) { |
||||
cache_->Release(handle); |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
bool Lookup(char key) { return Lookup(std::string(1, key)); } |
||||
|
||||
void Erase(const std::string& key) { cache_->Erase(key, 0 /*hash*/); } |
||||
|
||||
void ValidateLRUList(std::vector<std::string> keys, |
||||
size_t num_high_pri_pool_keys = 0) { |
||||
LRUHandle* lru; |
||||
LRUHandle* lru_low_pri; |
||||
cache_->TEST_GetLRUList(&lru, &lru_low_pri); |
||||
LRUHandle* iter = lru; |
||||
bool in_high_pri_pool = false; |
||||
size_t high_pri_pool_keys = 0; |
||||
if (iter == lru_low_pri) { |
||||
in_high_pri_pool = true; |
||||
} |
||||
for (const auto& key : keys) { |
||||
iter = iter->next; |
||||
ASSERT_NE(lru, iter); |
||||
ASSERT_EQ(key, iter->key().ToString()); |
||||
ASSERT_EQ(in_high_pri_pool, iter->InHighPriPool()); |
||||
if (in_high_pri_pool) { |
||||
high_pri_pool_keys++; |
||||
} |
||||
if (iter == lru_low_pri) { |
||||
ASSERT_FALSE(in_high_pri_pool); |
||||
in_high_pri_pool = true; |
||||
} |
||||
} |
||||
ASSERT_EQ(lru, iter->next); |
||||
ASSERT_TRUE(in_high_pri_pool); |
||||
ASSERT_EQ(num_high_pri_pool_keys, high_pri_pool_keys); |
||||
} |
||||
|
||||
private: |
||||
std::unique_ptr<LRUCacheShard> cache_; |
||||
}; |
||||
|
||||
TEST_F(LRUCacheTest, BasicLRU) { |
||||
NewCache(5); |
||||
for (char ch = 'a'; ch <= 'e'; ch++) { |
||||
Insert(ch); |
||||
} |
||||
ValidateLRUList({"a", "b", "c", "d", "e"}); |
||||
for (char ch = 'x'; ch <= 'z'; ch++) { |
||||
Insert(ch); |
||||
} |
||||
ValidateLRUList({"d", "e", "x", "y", "z"}); |
||||
ASSERT_FALSE(Lookup("b")); |
||||
ValidateLRUList({"d", "e", "x", "y", "z"}); |
||||
ASSERT_TRUE(Lookup("e")); |
||||
ValidateLRUList({"d", "x", "y", "z", "e"}); |
||||
ASSERT_TRUE(Lookup("z")); |
||||
ValidateLRUList({"d", "x", "y", "e", "z"}); |
||||
Erase("x"); |
||||
ValidateLRUList({"d", "y", "e", "z"}); |
||||
ASSERT_TRUE(Lookup("d")); |
||||
ValidateLRUList({"y", "e", "z", "d"}); |
||||
Insert("u"); |
||||
ValidateLRUList({"y", "e", "z", "d", "u"}); |
||||
Insert("v"); |
||||
ValidateLRUList({"e", "z", "d", "u", "v"}); |
||||
} |
||||
|
||||
TEST_F(LRUCacheTest, MidPointInsertion) { |
||||
// Allocate 2 cache entries to high-pri pool.
|
||||
NewCache(5, 0.45); |
||||
|
||||
Insert("a", Cache::Priority::LOW); |
||||
Insert("b", Cache::Priority::LOW); |
||||
Insert("c", Cache::Priority::LOW); |
||||
ValidateLRUList({"a", "b", "c"}, 0); |
||||
|
||||
// Low-pri entries can take high-pri pool capacity if available
|
||||
Insert("u", Cache::Priority::LOW); |
||||
Insert("v", Cache::Priority::LOW); |
||||
ValidateLRUList({"a", "b", "c", "u", "v"}, 0); |
||||
|
||||
Insert("X", Cache::Priority::HIGH); |
||||
Insert("Y", Cache::Priority::HIGH); |
||||
ValidateLRUList({"c", "u", "v", "X", "Y"}, 2); |
||||
|
||||
// High-pri entries can overflow to low-pri pool.
|
||||
Insert("Z", Cache::Priority::HIGH); |
||||
ValidateLRUList({"u", "v", "X", "Y", "Z"}, 2); |
||||
|
||||
// Low-pri entries will be inserted to head of low-pri pool.
|
||||
Insert("a", Cache::Priority::LOW); |
||||
ValidateLRUList({"v", "X", "a", "Y", "Z"}, 2); |
||||
|
||||
// Low-pri entries will be inserted to head of low-pri pool after lookup.
|
||||
ASSERT_TRUE(Lookup("v")); |
||||
ValidateLRUList({"X", "a", "v", "Y", "Z"}, 2); |
||||
|
||||
// High-pri entries will be inserted to the head of the list after lookup.
|
||||
ASSERT_TRUE(Lookup("X")); |
||||
ValidateLRUList({"a", "v", "Y", "Z", "X"}, 2); |
||||
ASSERT_TRUE(Lookup("Z")); |
||||
ValidateLRUList({"a", "v", "Y", "X", "Z"}, 2); |
||||
|
||||
Erase("Y"); |
||||
ValidateLRUList({"a", "v", "X", "Z"}, 2); |
||||
Erase("X"); |
||||
ValidateLRUList({"a", "v", "Z"}, 1); |
||||
Insert("d", Cache::Priority::LOW); |
||||
Insert("e", Cache::Priority::LOW); |
||||
ValidateLRUList({"a", "v", "d", "e", "Z"}, 1); |
||||
Insert("f", Cache::Priority::LOW); |
||||
Insert("g", Cache::Priority::LOW); |
||||
ValidateLRUList({"d", "e", "f", "g", "Z"}, 1); |
||||
ASSERT_TRUE(Lookup("d")); |
||||
ValidateLRUList({"e", "f", "g", "d", "Z"}, 1); |
||||
} |
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) { |
||||
::testing::InitGoogleTest(&argc, argv); |
||||
return RUN_ALL_TESTS(); |
||||
} |
Loading…
Reference in new issue