#include "cache/lru_cache.h"
#include <memory>
#include <string>
#include <vector>
#include "cache/cache_key.h"
#include "cache/clock_cache.h"
#include "cache_helpers.h"
#include "db/db_test_util.h"
#include "file/sst_file_manager_impl.h"
#include "port/port.h"
#include "port/stack_trace.h"
#include "rocksdb/cache.h"
#include "rocksdb/io_status.h"
#include "rocksdb/sst_file_manager.h"
#include "rocksdb/utilities/cache_dump_load.h"
#include "test_util/secondary_cache_test_util.h"
#include "test_util/testharness.h"
#include "typed_cache.h"
#include "util/coding.h"
#include "util/random.h"
#include "utilities/cache_dump_load_impl.h"
#include "utilities/fault_injection_fs.h"
namespace ROCKSDB_NAMESPACE {
class LRUCacheTest : public testing::Test {
public:
LRUCacheTest() = default;
~LRUCacheTest() override { DeleteCache(); }
void DeleteCache() {
if (cache_ != nullptr) {
cache_->~LRUCacheShard();
port::cacheline_aligned_free(cache_);
cache_ = nullptr;
}
}
void NewCache(size_t capacity, double high_pri_pool_ratio = 0.0,
double low_pri_pool_ratio = 1.0,
bool use_adaptive_mutex = kDefaultToAdaptiveMutex) {
DeleteCache();
cache_ = static_cast<LRUCacheShard*>(
port::cacheline_aligned_alloc(sizeof(LRUCacheShard)));
new (cache_) LRUCacheShard(capacity, false,
high_pri_pool_ratio, low_pri_pool_ratio,
use_adaptive_mutex, kDontChargeCacheMetadata,
24,
nullptr, &eviction_callback_);
}
void Insert(const std::string& key,
Cache::Priority priority = Cache::Priority::LOW,
size_t charge = 1) {
EXPECT_OK(cache_->Insert(key, 0 , nullptr ,
&kNoopCacheItemHelper, charge, nullptr ,
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 , nullptr, nullptr,
Cache::Priority::LOW, nullptr);
if (handle) {
cache_->Release(handle, true , false );
return true;
}
return false;
}
bool Lookup(char key) { return Lookup(std::string(1, key)); }
void Erase(const std::string& key) { cache_->Erase(key, 0 ); }
void ValidateLRUList(std::vector<std::string> keys,
size_t num_high_pri_pool_keys = 0,
size_t num_low_pri_pool_keys = 0,
size_t num_bottom_pri_pool_keys = 0) {
LRUHandle* lru;
LRUHandle* lru_low_pri;
LRUHandle* lru_bottom_pri;
cache_->TEST_GetLRUList(&lru, &lru_low_pri, &lru_bottom_pri);
LRUHandle* iter = lru;
bool in_low_pri_pool = false;
bool in_high_pri_pool = false;
size_t high_pri_pool_keys = 0;
size_t low_pri_pool_keys = 0;
size_t bottom_pri_pool_keys = 0;
if (iter == lru_bottom_pri) {
in_low_pri_pool = true;
in_high_pri_pool = false;
}
if (iter == lru_low_pri) {
in_low_pri_pool = false;
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());
ASSERT_EQ(in_low_pri_pool, iter->InLowPriPool());
if (in_high_pri_pool) {
ASSERT_FALSE(iter->InLowPriPool());
high_pri_pool_keys++;
} else if (in_low_pri_pool) {
ASSERT_FALSE(iter->InHighPriPool());
low_pri_pool_keys++;
} else {
bottom_pri_pool_keys++;
}
if (iter == lru_bottom_pri) {
ASSERT_FALSE(in_low_pri_pool);
ASSERT_FALSE(in_high_pri_pool);
in_low_pri_pool = true;
in_high_pri_pool = false;
}
if (iter == lru_low_pri) {
ASSERT_TRUE(in_low_pri_pool);
ASSERT_FALSE(in_high_pri_pool);
in_low_pri_pool = false;
in_high_pri_pool = true;
}
}
ASSERT_EQ(lru, iter->next);
ASSERT_FALSE(in_low_pri_pool);
ASSERT_TRUE(in_high_pri_pool);
ASSERT_EQ(num_high_pri_pool_keys, high_pri_pool_keys);
ASSERT_EQ(num_low_pri_pool_keys, low_pri_pool_keys);
ASSERT_EQ(num_bottom_pri_pool_keys, bottom_pri_pool_keys);
}
protected:
LRUCacheShard* cache_ = nullptr;
private:
Cache::EvictionCallback eviction_callback_;
};
TEST_F(LRUCacheTest, BasicLRU) {
NewCache(5);
for (char ch = 'a'; ch <= 'e'; ch++) {
Insert(ch);
}
ValidateLRUList({"a", "b", "c", "d", "e"}, 0, 5);
for (char ch = 'x'; ch <= 'z'; ch++) {
Insert(ch);
}
ValidateLRUList({"d", "e", "x", "y", "z"}, 0, 5);
ASSERT_FALSE(Lookup("b"));
ValidateLRUList({"d", "e", "x", "y", "z"}, 0, 5);
ASSERT_TRUE(Lookup("e"));
ValidateLRUList({"d", "x", "y", "z", "e"}, 0, 5);
ASSERT_TRUE(Lookup("z"));
ValidateLRUList({"d", "x", "y", "e", "z"}, 0, 5);
Erase("x");
ValidateLRUList({"d", "y", "e", "z"}, 0, 4);
ASSERT_TRUE(Lookup("d"));
ValidateLRUList({"y", "e", "z", "d"}, 0, 4);
Insert("u");
ValidateLRUList({"y", "e", "z", "d", "u"}, 0, 5);
Insert("v");
ValidateLRUList({"e", "z", "d", "u", "v"}, 0, 5);
}
TEST_F(LRUCacheTest, LowPriorityMidpointInsertion) {
NewCache(5, 0.40, 0.60);
Insert("a", Cache::Priority::LOW);
Insert("b", Cache::Priority::LOW);
Insert("c", Cache::Priority::LOW);
Insert("x", Cache::Priority::HIGH);
Insert("y", Cache::Priority::HIGH);
ValidateLRUList({"a", "b", "c", "x", "y"}, 2, 3);
Insert("d", Cache::Priority::LOW);
ValidateLRUList({"b", "c", "d", "x", "y"}, 2, 3);
ASSERT_TRUE(Lookup("d"));
ValidateLRUList({"b", "c", "x", "y", "d"}, 2, 3);
Insert("z", Cache::Priority::HIGH);
ValidateLRUList({"c", "x", "y", "d", "z"}, 2, 3);
}
TEST_F(LRUCacheTest, BottomPriorityMidpointInsertion) {
NewCache(6, 0.35, 0.35);
Insert("a", Cache::Priority::BOTTOM);
Insert("b", Cache::Priority::BOTTOM);
Insert("i", Cache::Priority::LOW);
Insert("j", Cache::Priority::LOW);
Insert("x", Cache::Priority::HIGH);
Insert("y", Cache::Priority::HIGH);
ValidateLRUList({"a", "b", "i", "j", "x", "y"}, 2, 2, 2);
Insert("k", Cache::Priority::LOW);
ValidateLRUList({"b", "i", "j", "k", "x", "y"}, 2, 2, 2);
ASSERT_TRUE(Lookup("k"));
ValidateLRUList({"b", "i", "j", "x", "y", "k"}, 2, 2, 2);
Insert("z", Cache::Priority::HIGH);
ValidateLRUList({"i", "j", "x", "y", "k", "z"}, 2, 2, 2);
Erase("x");
ValidateLRUList({"i", "j", "y", "k", "z"}, 2, 1, 2);
Erase("y");
ValidateLRUList({"i", "j", "k", "z"}, 2, 0, 2);
Insert("c", Cache::Priority::BOTTOM);
ValidateLRUList({"i", "j", "c", "k", "z"}, 2, 0, 3);
Insert("d", Cache::Priority::BOTTOM);
ValidateLRUList({"i", "j", "c", "d", "k", "z"}, 2, 0, 4);
Insert("e", Cache::Priority::BOTTOM);
ValidateLRUList({"j", "c", "d", "e", "k", "z"}, 2, 0, 4);
Insert("l", Cache::Priority::LOW);
ValidateLRUList({"c", "d", "e", "l", "k", "z"}, 2, 1, 3);
Insert("m", Cache::Priority::LOW);
ValidateLRUList({"d", "e", "l", "m", "k", "z"}, 2, 2, 2);
Erase("k");
ValidateLRUList({"d", "e", "l", "m", "z"}, 1, 2, 2);
Erase("z");
ValidateLRUList({"d", "e", "l", "m"}, 0, 2, 2);
Insert("f", Cache::Priority::BOTTOM);
ValidateLRUList({"d", "e", "f", "l", "m"}, 0, 2, 3);
Insert("g", Cache::Priority::BOTTOM);
ValidateLRUList({"d", "e", "f", "g", "l", "m"}, 0, 2, 4);
Insert("o", Cache::Priority::HIGH);
ValidateLRUList({"e", "f", "g", "l", "m", "o"}, 1, 2, 3);
Insert("p", Cache::Priority::HIGH);
ValidateLRUList({"f", "g", "l", "m", "o", "p"}, 2, 2, 2);
}
TEST_F(LRUCacheTest, EntriesWithPriority) {
NewCache(6, 0.35, 0.35);
Insert("a", Cache::Priority::LOW);
Insert("b", Cache::Priority::LOW);
ValidateLRUList({"a", "b"}, 0, 2, 0);
Insert("c", Cache::Priority::LOW);
ValidateLRUList({"a", "b", "c"}, 0, 2, 1);
Insert("t", Cache::Priority::LOW);
Insert("u", Cache::Priority::LOW);
ValidateLRUList({"a", "b", "c", "t", "u"}, 0, 2, 3);
Insert("v", Cache::Priority::LOW);
ValidateLRUList({"a", "b", "c", "t", "u", "v"}, 0, 2, 4);
Insert("w", Cache::Priority::LOW);
ValidateLRUList({"b", "c", "t", "u", "v", "w"}, 0, 2, 4);
Insert("X", Cache::Priority::HIGH);
Insert("Y", Cache::Priority::HIGH);
ValidateLRUList({"t", "u", "v", "w", "X", "Y"}, 2, 2, 2);
Insert("Z", Cache::Priority::HIGH);
ValidateLRUList({"u", "v", "w", "X", "Y", "Z"}, 2, 2, 2);
Insert("a", Cache::Priority::LOW);
ValidateLRUList({"v", "w", "X", "a", "Y", "Z"}, 2, 2, 2);
ASSERT_TRUE(Lookup("v"));
ValidateLRUList({"w", "X", "a", "Y", "Z", "v"}, 2, 2, 2);
ASSERT_TRUE(Lookup("X"));
ValidateLRUList({"w", "a", "Y", "Z", "v", "X"}, 2, 2, 2);
ASSERT_TRUE(Lookup("Z"));
ValidateLRUList({"w", "a", "Y", "v", "X", "Z"}, 2, 2, 2);
Erase("Y");
ValidateLRUList({"w", "a", "v", "X", "Z"}, 2, 1, 2);
Erase("X");
ValidateLRUList({"w", "a", "v", "Z"}, 1, 1, 2);
Insert("d", Cache::Priority::LOW);
Insert("e", Cache::Priority::LOW);
ValidateLRUList({"w", "a", "v", "d", "e", "Z"}, 1, 2, 3);
Insert("f", Cache::Priority::LOW);
Insert("g", Cache::Priority::LOW);
ValidateLRUList({"v", "d", "e", "f", "g", "Z"}, 1, 2, 3);
ASSERT_TRUE(Lookup("d"));
ValidateLRUList({"v", "e", "f", "g", "Z", "d"}, 2, 2, 2);
Erase("e");
Erase("f");
Erase("Z");
ValidateLRUList({"v", "g", "d"}, 1, 1, 1);
Insert("o", Cache::Priority::BOTTOM);
ValidateLRUList({"v", "o", "g", "d"}, 1, 1, 2);
Insert("p", Cache::Priority::BOTTOM);
ValidateLRUList({"v", "o", "p", "g", "d"}, 1, 1, 3);
Insert("q", Cache::Priority::BOTTOM);
ValidateLRUList({"v", "o", "p", "q", "g", "d"}, 1, 1, 4);
Insert("x", Cache::Priority::HIGH);
ValidateLRUList({"o", "p", "q", "g", "d", "x"}, 2, 1, 3);
Insert("y", Cache::Priority::HIGH);
ValidateLRUList({"p", "q", "g", "d", "x", "y"}, 2, 2, 2);
Insert("z", Cache::Priority::HIGH);
ValidateLRUList({"q", "g", "d", "x", "y", "z"}, 2, 2, 2);
ASSERT_TRUE(Lookup("g"));
ValidateLRUList({"q", "d", "x", "y", "z", "g"}, 2, 2, 2);
ASSERT_TRUE(Lookup("z"));
ValidateLRUList({"q", "d", "x", "y", "g", "z"}, 2, 2, 2);
ASSERT_TRUE(Lookup("d"));
ValidateLRUList({"q", "x", "y", "g", "z", "d"}, 2, 2, 2);
Insert("m", Cache::Priority::BOTTOM);
ValidateLRUList({"x", "m", "y", "g", "z", "d"}, 2, 2, 2);
ASSERT_TRUE(Lookup("m"));
ValidateLRUList({"x", "y", "g", "z", "d", "m"}, 2, 2, 2);
}
namespace clock_cache {
template <class ClockCache>
class ClockCacheTest : public testing::Test {
public:
using Shard = typename ClockCache::Shard;
using Table = typename Shard::Table;
using TableOpts = typename Table::Opts;
ClockCacheTest() = default;
~ClockCacheTest() override { DeleteShard(); }
void DeleteShard() {
if (shard_ != nullptr) {
shard_->~ClockCacheShard();
port::cacheline_aligned_free(shard_);
shard_ = nullptr;
}
}
void NewShard(size_t capacity, bool strict_capacity_limit = true,
int eviction_effort_cap = 30) {
DeleteShard();
shard_ = static_cast<Shard*>(port::cacheline_aligned_alloc(sizeof(Shard)));
TableOpts opts{1 , eviction_effort_cap};
new (shard_)
Shard(capacity, strict_capacity_limit, kDontChargeCacheMetadata,
nullptr, &eviction_callback_, &hash_seed_, opts);
}
Status Insert(const UniqueId64x2& hashed_key,
Cache::Priority priority = Cache::Priority::LOW) {
return shard_->Insert(TestKey(hashed_key), hashed_key, nullptr ,
&kNoopCacheItemHelper, 1 ,
nullptr , priority);
}
Status Insert(char key, Cache::Priority priority = Cache::Priority::LOW) {
return Insert(TestHashedKey(key), priority);
}
Status InsertWithLen(char key, size_t len) {
std::string skey(len, key);
return shard_->Insert(skey, TestHashedKey(key), nullptr ,
&kNoopCacheItemHelper, 1 ,
nullptr , Cache::Priority::LOW);
}
bool Lookup(const Slice& key, const UniqueId64x2& hashed_key,
bool useful = true) {
auto handle = shard_->Lookup(key, hashed_key);
if (handle) {
shard_->Release(handle, useful, false);
return true;
}
return false;
}
bool Lookup(const UniqueId64x2& hashed_key, bool useful = true) {
return Lookup(TestKey(hashed_key), hashed_key, useful);
}
bool Lookup(char key, bool useful = true) {
return Lookup(TestHashedKey(key), useful);
}
void Erase(char key) {
UniqueId64x2 hashed_key = TestHashedKey(key);
shard_->Erase(TestKey(hashed_key), hashed_key);
}
static inline Slice TestKey(const UniqueId64x2& hashed_key) {
return Slice(reinterpret_cast<const char*>(&hashed_key), 16U);
}
static inline UniqueId64x2 TestHashedKey(char key) {
return {(static_cast<uint64_t>(key) << 56) + 1234U, 5678U};
}
template <typename T>
static inline UniqueId64x2 CheapHash(T i) {
return {static_cast<uint64_t>(i) * uint64_t{0x85EBCA77C2B2AE63},
static_cast<uint64_t>(i) * uint64_t{0xC2B2AE3D27D4EB4F}};
}
Shard* shard_ = nullptr;
private:
Cache::EvictionCallback eviction_callback_;
uint32_t hash_seed_ = 0;
};
using ClockCacheTypes =
::testing::Types<AutoHyperClockCache, FixedHyperClockCache>;
TYPED_TEST_CASE(ClockCacheTest, ClockCacheTypes);
TYPED_TEST(ClockCacheTest, Misc) {
this->NewShard(3);
auto& shard = *this->shard_;
EXPECT_OK(this->InsertWithLen('a', 16));
EXPECT_NOK(this->InsertWithLen('b', 15));
EXPECT_OK(this->InsertWithLen('b', 16));
EXPECT_NOK(this->InsertWithLen('c', 17));
EXPECT_NOK(this->InsertWithLen('d', 1000));
EXPECT_NOK(this->InsertWithLen('e', 11));
EXPECT_NOK(this->InsertWithLen('f', 0));
std::string wrong_size_key(15, 'x');
EXPECT_FALSE(this->Lookup(wrong_size_key, this->TestHashedKey('x')));
EXPECT_FALSE(shard.Ref(nullptr));
EXPECT_FALSE(shard.Release(nullptr));
shard.Erase(wrong_size_key, this->TestHashedKey('x')); }
TYPED_TEST(ClockCacheTest, Limits) {
constexpr size_t kCapacity = 64;
this->NewShard(kCapacity, false );
auto& shard = *this->shard_;
using HandleImpl = typename ClockCacheTest<TypeParam>::Shard::HandleImpl;
for (bool strict_capacity_limit : {false, true, false}) {
SCOPED_TRACE("strict_capacity_limit = " +
std::to_string(strict_capacity_limit));
shard.SetStrictCapacityLimit(strict_capacity_limit);
UniqueId64x2 hkey = this->TestHashedKey('x');
{
Status s = shard.Insert(this->TestKey(hkey), hkey, nullptr ,
&kNoopCacheItemHelper, kCapacity + 2 ,
nullptr , Cache::Priority::LOW);
if (strict_capacity_limit) {
EXPECT_TRUE(s.IsMemoryLimit());
} else {
EXPECT_OK(s);
}
}
{
HandleImpl* h;
ASSERT_OK(shard.Insert(this->TestKey(hkey), hkey, nullptr ,
&kNoopCacheItemHelper, kCapacity , &h,
Cache::Priority::LOW));
Status s = this->Insert('a');
if (strict_capacity_limit) {
EXPECT_TRUE(s.IsMemoryLimit());
} else {
EXPECT_OK(s);
}
shard.Release(h, false , false );
}
{
size_t n = kCapacity * 5 + 1;
std::unique_ptr<HandleImpl*[]> ha{new HandleImpl* [n] {}};
Status s;
for (size_t i = 0; i < n && s.ok(); ++i) {
hkey[1] = i;
s = shard.Insert(this->TestKey(hkey), hkey, nullptr ,
&kNoopCacheItemHelper,
(i + kCapacity < n) ? 0 : 1 , &ha[i],
Cache::Priority::LOW);
if (i == 0) {
EXPECT_OK(s);
}
}
if (strict_capacity_limit) {
EXPECT_TRUE(s.IsMemoryLimit());
} else {
EXPECT_OK(s);
}
s = this->Insert('a');
if (strict_capacity_limit) {
EXPECT_TRUE(s.IsMemoryLimit());
} else {
EXPECT_OK(s);
}
EXPECT_EQ(shard.GetOccupancyCount(), shard.GetOccupancyLimit());
EXPECT_LT(shard.GetOccupancyCount(), shard.GetTableAddressCount());
for (size_t i = 0; i < n; ++i) {
if (ha[i]) {
shard.Release(ha[i]);
}
}
}
}
}
TYPED_TEST(ClockCacheTest, ClockEvictionTest) {
for (bool strict_capacity_limit : {false, true}) {
SCOPED_TRACE("strict_capacity_limit = " +
std::to_string(strict_capacity_limit));
this->NewShard(6, strict_capacity_limit);
auto& shard = *this->shard_;
EXPECT_OK(this->Insert('a', Cache::Priority::BOTTOM));
EXPECT_OK(this->Insert('b', Cache::Priority::LOW));
EXPECT_OK(this->Insert('c', Cache::Priority::HIGH));
EXPECT_OK(this->Insert('d', Cache::Priority::BOTTOM));
EXPECT_OK(this->Insert('e', Cache::Priority::LOW));
EXPECT_OK(this->Insert('f', Cache::Priority::HIGH));
EXPECT_TRUE(this->Lookup('a', false));
EXPECT_TRUE(this->Lookup('b', false));
EXPECT_TRUE(this->Lookup('c', false));
EXPECT_TRUE(this->Lookup('d', false));
EXPECT_TRUE(this->Lookup('e', false));
EXPECT_TRUE(this->Lookup('f', false));
EXPECT_OK(this->Insert('g', Cache::Priority::LOW));
EXPECT_OK(this->Insert('h', Cache::Priority::LOW));
EXPECT_FALSE(this->Lookup('a', false));
EXPECT_TRUE(this->Lookup('b', false));
EXPECT_TRUE(this->Lookup('c', false));
EXPECT_FALSE(this->Lookup('d', false));
EXPECT_TRUE(this->Lookup('e', false));
EXPECT_TRUE(this->Lookup('f', false));
EXPECT_TRUE(this->Lookup('g', true));
EXPECT_TRUE(this->Lookup('h', true));
EXPECT_OK(this->Insert('i', Cache::Priority::LOW));
EXPECT_OK(this->Insert('j', Cache::Priority::LOW));
EXPECT_FALSE(this->Lookup('b', false));
EXPECT_TRUE(this->Lookup('c', false));
EXPECT_FALSE(this->Lookup('e', false));
EXPECT_TRUE(this->Lookup('f', false));
EXPECT_TRUE(this->Lookup('g', true));
EXPECT_TRUE(this->Lookup('h', true));
EXPECT_TRUE(this->Lookup('i', false));
EXPECT_TRUE(this->Lookup('j', false));
EXPECT_OK(this->Insert('k', Cache::Priority::LOW));
EXPECT_OK(this->Insert('l', Cache::Priority::LOW));
EXPECT_FALSE(this->Lookup('c', false));
EXPECT_FALSE(this->Lookup('f', false));
EXPECT_TRUE(this->Lookup('g', false));
EXPECT_TRUE(this->Lookup('h', false));
EXPECT_TRUE(this->Lookup('i', false));
EXPECT_TRUE(this->Lookup('j', false));
EXPECT_TRUE(this->Lookup('k', false));
EXPECT_TRUE(this->Lookup('l', false));
EXPECT_OK(this->Insert('m', Cache::Priority::HIGH));
EXPECT_OK(this->Insert('n', Cache::Priority::HIGH));
EXPECT_TRUE(this->Lookup('g', false));
EXPECT_TRUE(this->Lookup('h', false));
EXPECT_FALSE(this->Lookup('i', false));
EXPECT_FALSE(this->Lookup('j', false));
EXPECT_TRUE(this->Lookup('k', false));
EXPECT_TRUE(this->Lookup('l', false));
shard.SetCapacity(4);
EXPECT_OK(this->Insert('o', Cache::Priority::LOW));
EXPECT_OK(this->Insert('p', Cache::Priority::LOW));
EXPECT_FALSE(this->Lookup('g', false));
EXPECT_FALSE(this->Lookup('h', false));
EXPECT_FALSE(this->Lookup('k', false));
EXPECT_FALSE(this->Lookup('l', false));
EXPECT_TRUE(this->Lookup('m', false));
EXPECT_TRUE(this->Lookup('n', false));
EXPECT_TRUE(this->Lookup('o', false));
EXPECT_TRUE(this->Lookup('p', false));
EXPECT_TRUE(this->Lookup('m', true));
EXPECT_TRUE(this->Lookup('n', true));
shard.SetCapacity(6);
EXPECT_OK(this->Insert('q', Cache::Priority::HIGH));
EXPECT_OK(this->Insert('r', Cache::Priority::HIGH));
EXPECT_OK(this->Insert('s', Cache::Priority::HIGH));
EXPECT_OK(this->Insert('t', Cache::Priority::HIGH));
EXPECT_FALSE(this->Lookup('o', false));
EXPECT_FALSE(this->Lookup('p', false));
EXPECT_TRUE(this->Lookup('m', false));
EXPECT_TRUE(this->Lookup('n', false));
EXPECT_TRUE(this->Lookup('q', false));
EXPECT_TRUE(this->Lookup('r', false));
EXPECT_TRUE(this->Lookup('s', false));
EXPECT_TRUE(this->Lookup('t', false));
}
}
TYPED_TEST(ClockCacheTest, ClockEvictionEffortCapTest) {
using HandleImpl = typename ClockCacheTest<TypeParam>::Shard::HandleImpl;
for (bool strict_capacity_limit : {true, false}) {
SCOPED_TRACE("strict_capacity_limit = " +
std::to_string(strict_capacity_limit));
for (int eec : {-42, 0, 1, 10, 100, 1000}) {
SCOPED_TRACE("eviction_effort_cap = " + std::to_string(eec));
constexpr size_t kCapacity = 1000;
this->NewShard(3 * kCapacity, strict_capacity_limit, eec);
auto& shard = *this->shard_;
shard.SetCapacity(kCapacity);
constexpr size_t kCount = kCapacity - 1;
std::unique_ptr<HandleImpl*[]> ha{new HandleImpl* [kCount] {}};
for (size_t i = 0; i < 2 * kCount; ++i) {
UniqueId64x2 hkey = this->CheapHash(i);
ASSERT_OK(shard.Insert(
this->TestKey(hkey), hkey, nullptr , &kNoopCacheItemHelper,
1 , i < kCount ? &ha[i] : nullptr, Cache::Priority::LOW));
}
if (strict_capacity_limit) {
EXPECT_EQ(shard.GetOccupancyCount(), kCapacity);
} else {
int effective_eec = std::max(int{1}, eec) + 1;
EXPECT_NEAR(shard.GetOccupancyCount() * 1.0,
kCount * (1 + 1.4 / effective_eec),
kCount * (0.6 / effective_eec) + 1.0);
}
for (size_t i = 0; i < kCount; ++i) {
shard.Release(ha[i]);
}
}
}
}
namespace {
struct DeleteCounter {
int deleted = 0;
};
const Cache::CacheItemHelper kDeleteCounterHelper{
CacheEntryRole::kMisc,
[](Cache::ObjectPtr value, MemoryAllocator* ) {
static_cast<DeleteCounter*>(value)->deleted += 1;
}};
}
TYPED_TEST(ClockCacheTest, ClockCounterOverflowTest) {
this->NewShard(6, false);
auto& shard = *this->shard_;
using HandleImpl = typename ClockCacheTest<TypeParam>::Shard::HandleImpl;
HandleImpl* h;
DeleteCounter val;
UniqueId64x2 hkey = this->TestHashedKey('x');
ASSERT_OK(shard.Insert(this->TestKey(hkey), hkey, &val, &kDeleteCounterHelper,
1, &h, Cache::Priority::HIGH));
shard.TEST_RefN(h, 123456789);
for (int i = 0; i < 10000; ++i) {
shard.TEST_RefN(h, 1234567);
shard.TEST_ReleaseN(h, 1234567);
}
shard.Erase(this->TestKey(hkey), hkey);
for (int i = 0; i < 10000; ++i) {
shard.TEST_RefN(h, 1234567);
shard.TEST_ReleaseN(h, 1234567);
}
shard.TEST_ReleaseN(h, 123456789);
ASSERT_EQ(val.deleted, 0);
shard.Release(h);
ASSERT_EQ(val.deleted, 1);
}
TYPED_TEST(ClockCacheTest, ClockTableFull) {
this->NewShard(6, false);
auto& shard = *this->shard_;
using HandleImpl = typename ClockCacheTest<TypeParam>::Shard::HandleImpl;
size_t size = shard.GetTableAddressCount();
ASSERT_LE(size + 3, 256); shard.TEST_MutableOccupancyLimit() = size + 100;
shard.SetCapacity(size + 100);
DeleteCounter val;
std::vector<HandleImpl*> handles;
for (size_t i = 0; i < size + 3; ++i) {
UniqueId64x2 hkey = this->TestHashedKey(static_cast<char>(i));
ASSERT_OK(shard.Insert(this->TestKey(hkey), hkey, &val,
&kDeleteCounterHelper, 1, &handles.emplace_back(),
Cache::Priority::HIGH));
}
for (size_t i = 0; i < size + 3; ++i) {
UniqueId64x2 hkey = this->TestHashedKey(static_cast<char>(i));
HandleImpl* h = shard.Lookup(this->TestKey(hkey), hkey);
if (i < size) {
ASSERT_NE(h, nullptr);
shard.Release(h);
} else {
ASSERT_EQ(h, nullptr);
}
}
for (size_t i = 0; i < size + 3; ++i) {
ASSERT_NE(handles[i], nullptr);
shard.Release(handles[i]);
if (i < size) {
ASSERT_EQ(val.deleted, 0);
} else {
ASSERT_EQ(val.deleted, i + 1 - size);
}
}
for (size_t i = size + 3; i > 0; --i) {
UniqueId64x2 hkey = this->TestHashedKey(static_cast<char>(i - 1));
shard.Erase(this->TestKey(hkey), hkey);
if (i - 1 > size) {
ASSERT_EQ(val.deleted, 3);
} else {
ASSERT_EQ(val.deleted, 3 + size - (i - 1));
}
}
}
TYPED_TEST(ClockCacheTest, CollidingInsertEraseTest) {
this->NewShard(6, false);
auto& shard = *this->shard_;
using HandleImpl = typename ClockCacheTest<TypeParam>::Shard::HandleImpl;
DeleteCounter val;
UniqueId64x2 hkey1 = this->TestHashedKey('x');
Slice key1 = this->TestKey(hkey1);
UniqueId64x2 hkey2 = this->TestHashedKey('y');
Slice key2 = this->TestKey(hkey2);
UniqueId64x2 hkey3 = this->TestHashedKey('z');
Slice key3 = this->TestKey(hkey3);
HandleImpl* h1;
ASSERT_OK(shard.Insert(key1, hkey1, &val, &kDeleteCounterHelper, 1, &h1,
Cache::Priority::HIGH));
HandleImpl* h2;
ASSERT_OK(shard.Insert(key2, hkey2, &val, &kDeleteCounterHelper, 1, &h2,
Cache::Priority::HIGH));
HandleImpl* h3;
ASSERT_OK(shard.Insert(key3, hkey3, &val, &kDeleteCounterHelper, 1, &h3,
Cache::Priority::HIGH));
HandleImpl* tmp_h;
for (bool erase_if_last_ref : {true, false}) { tmp_h = shard.Lookup(key1, hkey1);
ASSERT_EQ(h1, tmp_h);
ASSERT_FALSE(shard.Release(tmp_h, erase_if_last_ref));
tmp_h = shard.Lookup(key2, hkey2);
ASSERT_EQ(h2, tmp_h);
ASSERT_FALSE(shard.Release(tmp_h, erase_if_last_ref));
tmp_h = shard.Lookup(key3, hkey3);
ASSERT_EQ(h3, tmp_h);
ASSERT_FALSE(shard.Release(tmp_h, erase_if_last_ref));
}
shard.Erase(key1, hkey1);
shard.Erase(key1, hkey1);
ASSERT_EQ(val.deleted, 0);
tmp_h = shard.Lookup(key1, hkey1);
ASSERT_EQ(nullptr, tmp_h);
for (bool erase_if_last_ref : {true, false}) { tmp_h = shard.Lookup(key2, hkey2);
ASSERT_EQ(h2, tmp_h);
ASSERT_FALSE(shard.Release(tmp_h, erase_if_last_ref));
tmp_h = shard.Lookup(key3, hkey3);
ASSERT_EQ(h3, tmp_h);
ASSERT_FALSE(shard.Release(tmp_h, erase_if_last_ref));
}
ASSERT_OK(shard.Insert(key1, hkey1, &val, &kDeleteCounterHelper, 1, nullptr,
Cache::Priority::HIGH));
tmp_h = shard.Lookup(key1, hkey1);
ASSERT_NE(nullptr, tmp_h);
ASSERT_NE(h1, tmp_h);
ASSERT_TRUE(shard.Release(tmp_h, true));
ASSERT_EQ(val.deleted--, 1);
ASSERT_TRUE(shard.Release(h1, false));
ASSERT_EQ(val.deleted--, 1);
h1 = nullptr;
for (bool erase_if_last_ref : {true, false}) { tmp_h = shard.Lookup(key2, hkey2);
ASSERT_EQ(h2, tmp_h);
ASSERT_FALSE(shard.Release(tmp_h, erase_if_last_ref));
tmp_h = shard.Lookup(key3, hkey3);
ASSERT_EQ(h3, tmp_h);
ASSERT_FALSE(shard.Release(tmp_h, erase_if_last_ref));
}
ASSERT_FALSE(shard.Release(h2, false));
ASSERT_EQ(val.deleted, 0);
tmp_h = shard.Lookup(key2, hkey2);
ASSERT_EQ(h2, tmp_h);
ASSERT_TRUE(shard.Release(h2, true));
ASSERT_EQ(val.deleted--, 1);
tmp_h = shard.Lookup(key2, hkey2);
ASSERT_EQ(nullptr, tmp_h);
for (bool erase_if_last_ref : {true, false}) { tmp_h = shard.Lookup(key3, hkey3);
ASSERT_EQ(h3, tmp_h);
ASSERT_FALSE(shard.Release(tmp_h, erase_if_last_ref));
}
ASSERT_FALSE(shard.Release(h3, false));
ASSERT_EQ(val.deleted, 0);
shard.Erase(key3, hkey3);
ASSERT_EQ(val.deleted--, 1);
tmp_h = shard.Lookup(key3, hkey3);
ASSERT_EQ(nullptr, tmp_h);
}
TYPED_TEST(ClockCacheTest, TableSizesTest) {
for (size_t est_val_size : {1U, 5U, 123U, 2345U, 345678U}) {
SCOPED_TRACE("est_val_size = " + std::to_string(est_val_size));
for (double est_count : {1.1, 2.2, 511.9, 512.1, 2345.0}) {
SCOPED_TRACE("est_count = " + std::to_string(est_count));
size_t capacity = static_cast<size_t>(est_val_size * est_count);
auto cache = HyperClockCacheOptions(
capacity, est_val_size, -1,
false,
nullptr, kDontChargeCacheMetadata)
.MakeSharedCache();
EXPECT_GE(cache->GetTableAddressCount(),
est_count / FixedHyperClockTable::kLoadFactor);
EXPECT_LE(cache->GetTableAddressCount(),
est_count / FixedHyperClockTable::kLoadFactor * 2.0);
EXPECT_EQ(cache->GetUsage(), 0);
if (est_val_size >= 512) {
cache = HyperClockCacheOptions(
capacity, est_val_size, -1,
false,
nullptr, kFullChargeCacheMetadata)
.MakeSharedCache();
double est_count_after_meta =
(capacity - cache->GetUsage()) * 1.0 / est_val_size;
EXPECT_GE(cache->GetTableAddressCount(),
est_count_after_meta / FixedHyperClockTable::kLoadFactor);
EXPECT_LE(
cache->GetTableAddressCount(),
est_count_after_meta / FixedHyperClockTable::kLoadFactor * 2.0);
}
}
}
}
}
class TestSecondaryCache : public SecondaryCache {
public:
enum ResultType {
SUCCESS,
FAIL,
DEFER,
DEFER_AND_FAIL
};
using ResultMap = std::unordered_map<std::string, ResultType>;
explicit TestSecondaryCache(size_t capacity, bool insert_saved = false)
: cache_(NewLRUCache(capacity, 0, false, 0.5 ,
nullptr, kDefaultToAdaptiveMutex,
kDontChargeCacheMetadata)),
num_inserts_(0),
num_lookups_(0),
inject_failure_(false),
insert_saved_(insert_saved) {}
const char* Name() const override { return "TestSecondaryCache"; }
void InjectFailure() { inject_failure_ = true; }
void ResetInjectFailure() { inject_failure_ = false; }
Status Insert(const Slice& key, Cache::ObjectPtr value,
const Cache::CacheItemHelper* helper,
bool ) override {
if (inject_failure_) {
return Status::Corruption("Insertion Data Corrupted");
}
CheckCacheKeyCommonPrefix(key);
size_t size;
char* buf;
Status s;
num_inserts_++;
size = (*helper->size_cb)(value);
buf = new char[size + sizeof(uint64_t)];
EncodeFixed64(buf, size);
s = (*helper->saveto_cb)(value, 0, size, buf + sizeof(uint64_t));
if (!s.ok()) {
delete[] buf;
return s;
}
return cache_.Insert(key, buf, size);
}
Status InsertSaved(const Slice& key, const Slice& saved,
CompressionType = kNoCompression,
CacheTier = CacheTier::kVolatileTier) override {
if (insert_saved_) {
return Insert(key, const_cast<Slice*>(&saved), &kSliceCacheItemHelper,
true);
} else {
return Status::OK();
}
}
std::unique_ptr<SecondaryCacheResultHandle> Lookup(
const Slice& key, const Cache::CacheItemHelper* helper,
Cache::CreateContext* create_context, bool ,
bool , Statistics* ,
bool& kept_in_sec_cache) override {
std::string key_str = key.ToString();
TEST_SYNC_POINT_CALLBACK("TestSecondaryCache::Lookup", &key_str);
std::unique_ptr<SecondaryCacheResultHandle> secondary_handle;
kept_in_sec_cache = false;
ResultType type = ResultType::SUCCESS;
auto iter = result_map_.find(key.ToString());
if (iter != result_map_.end()) {
type = iter->second;
}
if (type == ResultType::FAIL) {
return secondary_handle;
}
TypedHandle* handle = cache_.Lookup(key);
num_lookups_++;
if (handle) {
Cache::ObjectPtr value = nullptr;
size_t charge = 0;
Status s;
if (type != ResultType::DEFER_AND_FAIL) {
char* ptr = cache_.Value(handle);
size_t size = DecodeFixed64(ptr);
ptr += sizeof(uint64_t);
s = helper->create_cb(Slice(ptr, size), kNoCompression,
CacheTier::kVolatileTier, create_context,
nullptr, &value, &charge);
}
if (s.ok()) {
secondary_handle.reset(new TestSecondaryCacheResultHandle(
cache_.get(), handle, value, charge, type));
kept_in_sec_cache = true;
} else {
cache_.Release(handle);
}
}
return secondary_handle;
}
bool SupportForceErase() const override { return false; }
void Erase(const Slice& ) override {}
void WaitAll(std::vector<SecondaryCacheResultHandle*> handles) override {
for (SecondaryCacheResultHandle* handle : handles) {
TestSecondaryCacheResultHandle* sec_handle =
static_cast<TestSecondaryCacheResultHandle*>(handle);
sec_handle->SetReady();
}
}
std::string GetPrintableOptions() const override { return ""; }
void SetResultMap(ResultMap&& map) { result_map_ = std::move(map); }
uint32_t num_inserts() { return num_inserts_; }
uint32_t num_lookups() { return num_lookups_; }
void CheckCacheKeyCommonPrefix(const Slice& key) {
Slice current_prefix(key.data(), OffsetableCacheKey::kCommonPrefixSize);
if (ckey_prefix_.empty()) {
ckey_prefix_ = current_prefix.ToString();
} else {
EXPECT_EQ(ckey_prefix_, current_prefix.ToString());
}
}
private:
class TestSecondaryCacheResultHandle : public SecondaryCacheResultHandle {
public:
TestSecondaryCacheResultHandle(Cache* cache, Cache::Handle* handle,
Cache::ObjectPtr value, size_t size,
ResultType type)
: cache_(cache),
handle_(handle),
value_(value),
size_(size),
is_ready_(true) {
if (type != ResultType::SUCCESS) {
is_ready_ = false;
}
}
~TestSecondaryCacheResultHandle() override { cache_->Release(handle_); }
bool IsReady() override { return is_ready_; }
void Wait() override {}
Cache::ObjectPtr Value() override {
assert(is_ready_);
return value_;
}
size_t Size() override { return Value() ? size_ : 0; }
void SetReady() { is_ready_ = true; }
private:
Cache* cache_;
Cache::Handle* handle_;
Cache::ObjectPtr value_;
size_t size_;
bool is_ready_;
};
using SharedCache =
BasicTypedSharedCacheInterface<char[], CacheEntryRole::kMisc>;
using TypedHandle = SharedCache::TypedHandle;
SharedCache cache_;
uint32_t num_inserts_;
uint32_t num_lookups_;
bool inject_failure_;
bool insert_saved_;
std::string ckey_prefix_;
ResultMap result_map_;
};
using secondary_cache_test_util::GetTestingCacheTypes;
using secondary_cache_test_util::WithCacheTypeParam;
class BasicSecondaryCacheTest : public testing::Test,
public WithCacheTypeParam {};
INSTANTIATE_TEST_CASE_P(BasicSecondaryCacheTest, BasicSecondaryCacheTest,
GetTestingCacheTypes());
class DBSecondaryCacheTest : public DBTestBase, public WithCacheTypeParam {
public:
DBSecondaryCacheTest()
: DBTestBase("db_secondary_cache_test", true) {
fault_fs_.reset(new FaultInjectionTestFS(env_->GetFileSystem()));
fault_env_.reset(new CompositeEnvWrapper(env_, fault_fs_));
}
std::shared_ptr<FaultInjectionTestFS> fault_fs_;
std::unique_ptr<Env> fault_env_;
};
INSTANTIATE_TEST_CASE_P(DBSecondaryCacheTest, DBSecondaryCacheTest,
GetTestingCacheTypes());
TEST_P(BasicSecondaryCacheTest, BasicTest) {
std::shared_ptr<TestSecondaryCache> secondary_cache =
std::make_shared<TestSecondaryCache>(4096, true);
std::shared_ptr<Cache> cache =
NewCache(1024 , 0 ,
false , secondary_cache);
std::shared_ptr<Statistics> stats = CreateDBStatistics();
CacheKey k1 = CacheKey::CreateUniqueForCacheLifetime(cache.get());
CacheKey k2 = CacheKey::CreateUniqueForCacheLifetime(cache.get());
CacheKey k3 = CacheKey::CreateUniqueForCacheLifetime(cache.get());
Random rnd(301);
std::string str3 = rnd.RandomString(1021);
ASSERT_OK(secondary_cache->InsertSaved(k3.AsSlice(), str3));
std::string str1 = rnd.RandomString(1021);
TestItem* item1 = new TestItem(str1.data(), str1.length());
ASSERT_OK(cache->Insert(k1.AsSlice(), item1, GetHelper(), str1.length()));
std::string str2 = rnd.RandomString(1021);
TestItem* item2 = new TestItem(str2.data(), str2.length());
ASSERT_OK(cache->Insert(k2.AsSlice(), item2, GetHelper(), str2.length()));
get_perf_context()->Reset();
Cache::Handle* handle;
handle = cache->Lookup(k2.AsSlice(), GetHelper(),
this, Cache::Priority::LOW, stats.get());
ASSERT_NE(handle, nullptr);
ASSERT_EQ(static_cast<TestItem*>(cache->Value(handle))->Size(), str2.size());
cache->Release(handle);
handle = cache->Lookup(k1.AsSlice(), GetHelper(),
this, Cache::Priority::LOW, stats.get());
ASSERT_NE(handle, nullptr);
ASSERT_EQ(static_cast<TestItem*>(cache->Value(handle))->Size(), str1.size());
cache->Release(handle);
handle = cache->Lookup(k3.AsSlice(), GetHelper(),
this, Cache::Priority::LOW, stats.get());
ASSERT_NE(handle, nullptr);
ASSERT_EQ(static_cast<TestItem*>(cache->Value(handle))->Size(), str3.size());
cache->Release(handle);
ASSERT_EQ(secondary_cache->num_inserts(), 3u);
ASSERT_EQ(secondary_cache->num_lookups(), 2u);
ASSERT_EQ(stats->getTickerCount(SECONDARY_CACHE_HITS),
secondary_cache->num_lookups());
PerfContext perf_ctx = *get_perf_context();
ASSERT_EQ(perf_ctx.secondary_cache_hit_count, secondary_cache->num_lookups());
cache.reset();
secondary_cache.reset();
}
TEST_P(BasicSecondaryCacheTest, StatsTest) {
std::shared_ptr<TestSecondaryCache> secondary_cache =
std::make_shared<TestSecondaryCache>(4096, true);
std::shared_ptr<Cache> cache =
NewCache(1024 , 0 ,
false , secondary_cache);
std::shared_ptr<Statistics> stats = CreateDBStatistics();
CacheKey k1 = CacheKey::CreateUniqueForCacheLifetime(cache.get());
CacheKey k2 = CacheKey::CreateUniqueForCacheLifetime(cache.get());
CacheKey k3 = CacheKey::CreateUniqueForCacheLifetime(cache.get());
Random rnd(301);
std::string str1 = rnd.RandomString(1020);
std::string str2 = rnd.RandomString(1020);
std::string str3 = rnd.RandomString(1020);
ASSERT_OK(secondary_cache->InsertSaved(k1.AsSlice(), str1));
ASSERT_OK(secondary_cache->InsertSaved(k2.AsSlice(), str2));
ASSERT_OK(secondary_cache->InsertSaved(k3.AsSlice(), str3));
get_perf_context()->Reset();
Cache::Handle* handle;
handle = cache->Lookup(k1.AsSlice(), GetHelper(CacheEntryRole::kFilterBlock),
this, Cache::Priority::LOW, stats.get());
ASSERT_NE(handle, nullptr);
ASSERT_EQ(static_cast<TestItem*>(cache->Value(handle))->Size(), str1.size());
cache->Release(handle);
handle = cache->Lookup(k2.AsSlice(), GetHelper(CacheEntryRole::kIndexBlock),
this, Cache::Priority::LOW, stats.get());
ASSERT_NE(handle, nullptr);
ASSERT_EQ(static_cast<TestItem*>(cache->Value(handle))->Size(), str2.size());
cache->Release(handle);
handle = cache->Lookup(k3.AsSlice(), GetHelper(CacheEntryRole::kDataBlock),
this, Cache::Priority::LOW, stats.get());
ASSERT_NE(handle, nullptr);
ASSERT_EQ(static_cast<TestItem*>(cache->Value(handle))->Size(), str3.size());
cache->Release(handle);
ASSERT_EQ(secondary_cache->num_inserts(), 3u);
ASSERT_EQ(secondary_cache->num_lookups(), 3u);
ASSERT_EQ(stats->getTickerCount(SECONDARY_CACHE_HITS),
secondary_cache->num_lookups());
ASSERT_EQ(stats->getTickerCount(SECONDARY_CACHE_FILTER_HITS), 1);
ASSERT_EQ(stats->getTickerCount(SECONDARY_CACHE_INDEX_HITS), 1);
ASSERT_EQ(stats->getTickerCount(SECONDARY_CACHE_DATA_HITS), 1);
PerfContext perf_ctx = *get_perf_context();
ASSERT_EQ(perf_ctx.secondary_cache_hit_count, secondary_cache->num_lookups());
cache.reset();
secondary_cache.reset();
}
TEST_P(BasicSecondaryCacheTest, BasicFailTest) {
std::shared_ptr<TestSecondaryCache> secondary_cache =
std::make_shared<TestSecondaryCache>(2048, true);
std::shared_ptr<Cache> cache =
NewCache(1024 , 0 ,
false , secondary_cache);
CacheKey k1 = CacheKey::CreateUniqueForCacheLifetime(cache.get());
CacheKey k2 = CacheKey::CreateUniqueForCacheLifetime(cache.get());
Random rnd(301);
std::string str1 = rnd.RandomString(1020);
auto item1 = std::make_unique<TestItem>(str1.data(), str1.length());
ASSERT_OK(
cache->Insert(k1.AsSlice(), item1.get(), GetHelper(), str1.length()));
item1.release();
Cache::Handle* handle;
handle = cache->Lookup(k2.AsSlice(), nullptr, this,
Cache::Priority::LOW);
ASSERT_EQ(handle, nullptr);
handle = cache->Lookup(k2.AsSlice(), GetHelper(),
this, Cache::Priority::LOW);
ASSERT_EQ(handle, nullptr);
Cache::AsyncLookupHandle async_handle;
async_handle.key = k2.AsSlice();
async_handle.helper = GetHelper();
async_handle.create_context = this;
async_handle.priority = Cache::Priority::LOW;
cache->StartAsyncLookup(async_handle);
cache->Wait(async_handle);
handle = async_handle.Result();
ASSERT_EQ(handle, nullptr);
cache.reset();
secondary_cache.reset();
}
TEST_P(BasicSecondaryCacheTest, SaveFailTest) {
std::shared_ptr<TestSecondaryCache> secondary_cache =
std::make_shared<TestSecondaryCache>(2048, true);
std::shared_ptr<Cache> cache =
NewCache(1024 , 0 ,
false , secondary_cache);
CacheKey k1 = CacheKey::CreateUniqueForCacheLifetime(cache.get());
CacheKey k2 = CacheKey::CreateUniqueForCacheLifetime(cache.get());
Random rnd(301);
std::string str1 = rnd.RandomString(1020);
TestItem* item1 = new TestItem(str1.data(), str1.length());
ASSERT_OK(cache->Insert(k1.AsSlice(), item1, GetHelperFail(), str1.length()));
std::string str2 = rnd.RandomString(1020);
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
TestItem* item2 = new TestItem(str2.data(), str2.length());
ASSERT_OK(cache->Insert(k2.AsSlice(), item2, GetHelperFail(), str2.length()));
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
Cache::Handle* handle;
handle = cache->Lookup(k2.AsSlice(), GetHelperFail(),
this, Cache::Priority::LOW);
ASSERT_NE(handle, nullptr);
cache->Release(handle);
handle = cache->Lookup(k1.AsSlice(), GetHelperFail(),
this, Cache::Priority::LOW);
ASSERT_EQ(handle, nullptr);
handle = cache->Lookup(k2.AsSlice(), GetHelperFail(),
this, Cache::Priority::LOW);
ASSERT_NE(handle, nullptr);
cache->Release(handle);
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
ASSERT_EQ(secondary_cache->num_lookups(), 1u);
cache.reset();
secondary_cache.reset();
}
TEST_P(BasicSecondaryCacheTest, CreateFailTest) {
std::shared_ptr<TestSecondaryCache> secondary_cache =
std::make_shared<TestSecondaryCache>(2048, true);
std::shared_ptr<Cache> cache =
NewCache(1024 , 0 ,
false , secondary_cache);
CacheKey k1 = CacheKey::CreateUniqueForCacheLifetime(cache.get());
CacheKey k2 = CacheKey::CreateUniqueForCacheLifetime(cache.get());
Random rnd(301);
std::string str1 = rnd.RandomString(1020);
TestItem* item1 = new TestItem(str1.data(), str1.length());
ASSERT_OK(cache->Insert(k1.AsSlice(), item1, GetHelper(), str1.length()));
std::string str2 = rnd.RandomString(1020);
TestItem* item2 = new TestItem(str2.data(), str2.length());
ASSERT_OK(cache->Insert(k2.AsSlice(), item2, GetHelper(), str2.length()));
Cache::Handle* handle;
SetFailCreate(true);
handle = cache->Lookup(k2.AsSlice(), GetHelper(),
this, Cache::Priority::LOW);
ASSERT_NE(handle, nullptr);
cache->Release(handle);
handle = cache->Lookup(k1.AsSlice(), GetHelper(),
this, Cache::Priority::LOW);
ASSERT_EQ(handle, nullptr);
handle = cache->Lookup(k2.AsSlice(), GetHelper(),
this, Cache::Priority::LOW);
ASSERT_NE(handle, nullptr);
cache->Release(handle);
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
ASSERT_EQ(secondary_cache->num_lookups(), 1u);
cache.reset();
secondary_cache.reset();
}
TEST_P(BasicSecondaryCacheTest, FullCapacityTest) {
for (bool strict_capacity_limit : {false, true}) {
std::shared_ptr<TestSecondaryCache> secondary_cache =
std::make_shared<TestSecondaryCache>(2048, true);
std::shared_ptr<Cache> cache =
NewCache(1024 , 0 ,
strict_capacity_limit, secondary_cache);
CacheKey k1 = CacheKey::CreateUniqueForCacheLifetime(cache.get());
CacheKey k2 = CacheKey::CreateUniqueForCacheLifetime(cache.get());
Random rnd(301);
std::string str1 = rnd.RandomString(1020);
TestItem* item1 = new TestItem(str1.data(), str1.length());
ASSERT_OK(cache->Insert(k1.AsSlice(), item1, GetHelper(), str1.length()));
std::string str2 = rnd.RandomString(1020);
TestItem* item2 = new TestItem(str2.data(), str2.length());
ASSERT_OK(cache->Insert(k2.AsSlice(), item2, GetHelper(), str2.length()));
Cache::Handle* handle2;
handle2 = cache->Lookup(k2.AsSlice(), GetHelper(),
this, Cache::Priority::LOW);
ASSERT_NE(handle2, nullptr);
Cache::Handle* handle1;
handle1 = cache->Lookup(
k1.AsSlice(),
GetHelper(CacheEntryRole::kDataBlock, false),
this, Cache::Priority::LOW);
ASSERT_EQ(handle1, nullptr);
handle1 = cache->Lookup(k1.AsSlice(), GetHelper(),
this, Cache::Priority::LOW);
ASSERT_NE(handle1, nullptr);
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
ASSERT_EQ(secondary_cache->num_lookups(), 1u);
cache->Release(handle2);
cache->Release(handle1);
handle2 = cache->Lookup(
k2.AsSlice(),
GetHelper(CacheEntryRole::kDataBlock, false),
this, Cache::Priority::LOW);
if (strict_capacity_limit || IsHyperClock()) {
ASSERT_NE(handle2, nullptr);
cache->Release(handle2);
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
} else {
ASSERT_EQ(handle2, nullptr);
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
}
cache.reset();
secondary_cache.reset();
}
}
TEST_P(DBSecondaryCacheTest, TestSecondaryCacheCorrectness1) {
if (IsHyperClock()) {
ROCKSDB_GTEST_BYPASS("Test depends on LRUCache-specific behaviors");
return;
}
std::shared_ptr<TestSecondaryCache> secondary_cache(
new TestSecondaryCache(2048 * 1024));
std::shared_ptr<Cache> cache =
NewCache(4 * 1024 , 0 ,
false , secondary_cache);
BlockBasedTableOptions table_options;
table_options.block_cache = cache;
table_options.block_size = 4 * 1024;
Options options = GetDefaultOptions();
options.create_if_missing = true;
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
options.env = fault_env_.get();
fault_fs_->SetFailGetUniqueId(true);
options.paranoid_file_checks = true;
DestroyAndReopen(options);
Random rnd(301);
const int N = 6;
for (int i = 0; i < N; i++) {
std::string p_v = rnd.RandomString(1007);
ASSERT_OK(Put(Key(i), p_v));
}
ASSERT_OK(Flush());
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
ASSERT_EQ(secondary_cache->num_lookups(), 2u);
Compact("a", "z");
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
ASSERT_EQ(secondary_cache->num_lookups(), 3u);
std::string v = Get(Key(0));
ASSERT_EQ(1007, v.size());
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
ASSERT_EQ(secondary_cache->num_lookups(), 4u);
v = Get(Key(5));
ASSERT_EQ(1007, v.size());
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
ASSERT_EQ(secondary_cache->num_lookups(), 5u);
v = Get(Key(5));
ASSERT_EQ(1007, v.size());
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
ASSERT_EQ(secondary_cache->num_lookups(), 5u);
v = Get(Key(0));
ASSERT_EQ(1007, v.size());
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
ASSERT_EQ(secondary_cache->num_lookups(), 6u);
v = Get(Key(0));
ASSERT_EQ(1007, v.size());
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
ASSERT_EQ(secondary_cache->num_lookups(), 7u);
Destroy(options);
}
TEST_P(DBSecondaryCacheTest, TestSecondaryCacheCorrectness2) {
if (IsHyperClock()) {
ROCKSDB_GTEST_BYPASS("Test depends on LRUCache-specific behaviors");
return;
}
std::shared_ptr<TestSecondaryCache> secondary_cache(
new TestSecondaryCache(2048 * 1024));
std::shared_ptr<Cache> cache =
NewCache(6100 , 0 ,
false , secondary_cache);
BlockBasedTableOptions table_options;
table_options.block_cache = cache;
table_options.block_size = 4 * 1024;
Options options = GetDefaultOptions();
options.create_if_missing = true;
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
options.paranoid_file_checks = true;
options.env = fault_env_.get();
fault_fs_->SetFailGetUniqueId(true);
DestroyAndReopen(options);
Random rnd(301);
const int N = 6;
for (int i = 0; i < N; i++) {
std::string p_v = rnd.RandomString(1007);
ASSERT_OK(Put(Key(i), p_v));
}
ASSERT_OK(Flush());
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
ASSERT_EQ(secondary_cache->num_lookups(), 2u);
Compact("a", "z");
ASSERT_EQ(secondary_cache->num_inserts(), 2u);
ASSERT_EQ(secondary_cache->num_lookups(), 3u);
std::string v = Get(Key(0));
ASSERT_EQ(1007, v.size());
ASSERT_EQ(secondary_cache->num_inserts(), 2u);
ASSERT_EQ(secondary_cache->num_lookups(), 3u);
v = Get(Key(5));
ASSERT_EQ(1007, v.size());
ASSERT_EQ(secondary_cache->num_inserts(), 2u);
ASSERT_EQ(secondary_cache->num_lookups(), 4u);
v = Get(Key(5));
ASSERT_EQ(1007, v.size());
ASSERT_EQ(secondary_cache->num_inserts(), 2u);
ASSERT_EQ(secondary_cache->num_lookups(), 4u);
v = Get(Key(0));
ASSERT_EQ(1007, v.size());
ASSERT_EQ(secondary_cache->num_inserts(), 2u);
ASSERT_EQ(secondary_cache->num_lookups(), 5u);
v = Get(Key(0));
ASSERT_EQ(1007, v.size());
ASSERT_EQ(secondary_cache->num_inserts(), 2u);
ASSERT_EQ(secondary_cache->num_lookups(), 5u);
Destroy(options);
}
TEST_P(DBSecondaryCacheTest, NoSecondaryCacheInsertion) {
std::shared_ptr<TestSecondaryCache> secondary_cache(
new TestSecondaryCache(2048 * 1024));
std::shared_ptr<Cache> cache =
NewCache(1024 * 1024 , 0 ,
false , secondary_cache);
BlockBasedTableOptions table_options;
table_options.block_cache = cache;
table_options.block_size = 4 * 1024;
Options options = GetDefaultOptions();
options.create_if_missing = true;
options.paranoid_file_checks = true;
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
options.env = fault_env_.get();
fault_fs_->SetFailGetUniqueId(true);
DestroyAndReopen(options);
Random rnd(301);
const int N = 6;
for (int i = 0; i < N; i++) {
std::string p_v = rnd.RandomString(1000);
ASSERT_OK(Put(Key(i), p_v));
}
ASSERT_OK(Flush());
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
ASSERT_EQ(secondary_cache->num_lookups(), 2u);
Compact("a", "z");
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
ASSERT_EQ(secondary_cache->num_lookups(), 2u);
std::string v = Get(Key(0));
ASSERT_EQ(1000, v.size());
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
ASSERT_EQ(secondary_cache->num_lookups(), 2u);
Destroy(options);
}
TEST_P(DBSecondaryCacheTest, SecondaryCacheIntensiveTesting) {
std::shared_ptr<TestSecondaryCache> secondary_cache(
new TestSecondaryCache(2048 * 1024));
std::shared_ptr<Cache> cache =
NewCache(8 * 1024 , 0 ,
false , secondary_cache);
BlockBasedTableOptions table_options;
table_options.block_cache = cache;
table_options.block_size = 4 * 1024;
Options options = GetDefaultOptions();
options.create_if_missing = true;
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
options.env = fault_env_.get();
fault_fs_->SetFailGetUniqueId(true);
DestroyAndReopen(options);
Random rnd(301);
const int N = 256;
for (int i = 0; i < N; i++) {
std::string p_v = rnd.RandomString(1000);
ASSERT_OK(Put(Key(i), p_v));
}
ASSERT_OK(Flush());
Compact("a", "z");
Random r_index(47);
std::string v;
for (int i = 0; i < 1000; i++) {
uint32_t key_i = r_index.Next() % N;
v = Get(Key(key_i));
}
ASSERT_GE(secondary_cache->num_inserts(), 1u);
ASSERT_GE(secondary_cache->num_lookups(), 1u);
Destroy(options);
}
TEST_P(DBSecondaryCacheTest, SecondaryCacheFailureTest) {
if (IsHyperClock()) {
ROCKSDB_GTEST_BYPASS("Test depends on LRUCache-specific behaviors");
return;
}
std::shared_ptr<TestSecondaryCache> secondary_cache(
new TestSecondaryCache(2048 * 1024));
std::shared_ptr<Cache> cache =
NewCache(4 * 1024 , 0 ,
false , secondary_cache);
BlockBasedTableOptions table_options;
table_options.block_cache = cache;
table_options.block_size = 4 * 1024;
Options options = GetDefaultOptions();
options.create_if_missing = true;
options.paranoid_file_checks = true;
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
options.env = fault_env_.get();
fault_fs_->SetFailGetUniqueId(true);
DestroyAndReopen(options);
Random rnd(301);
const int N = 6;
for (int i = 0; i < N; i++) {
std::string p_v = rnd.RandomString(1007);
ASSERT_OK(Put(Key(i), p_v));
}
ASSERT_OK(Flush());
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
ASSERT_EQ(secondary_cache->num_lookups(), 2u);
secondary_cache->InjectFailure();
Compact("a", "z");
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
ASSERT_EQ(secondary_cache->num_lookups(), 3u);
std::string v = Get(Key(0));
ASSERT_EQ(1007, v.size());
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
ASSERT_EQ(secondary_cache->num_lookups(), 4u);
v = Get(Key(5));
ASSERT_EQ(1007, v.size());
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
ASSERT_EQ(secondary_cache->num_lookups(), 5u);
v = Get(Key(5));
ASSERT_EQ(1007, v.size());
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
ASSERT_EQ(secondary_cache->num_lookups(), 5u);
v = Get(Key(0));
ASSERT_EQ(1007, v.size());
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
ASSERT_EQ(secondary_cache->num_lookups(), 6u);
v = Get(Key(0));
ASSERT_EQ(1007, v.size());
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
ASSERT_EQ(secondary_cache->num_lookups(), 7u);
secondary_cache->ResetInjectFailure();
Destroy(options);
}
TEST_P(BasicSecondaryCacheTest, BasicWaitAllTest) {
std::shared_ptr<TestSecondaryCache> secondary_cache =
std::make_shared<TestSecondaryCache>(32 * 1024);
std::shared_ptr<Cache> cache =
NewCache(1024 , 2 ,
false , secondary_cache);
const int num_keys = 32;
OffsetableCacheKey ock{"foo", "bar", 1};
Random rnd(301);
std::vector<std::string> values;
for (int i = 0; i < num_keys; ++i) {
std::string str = rnd.RandomString(1020);
values.emplace_back(str);
TestItem* item = new TestItem(str.data(), str.length());
ASSERT_OK(cache->Insert(ock.WithOffset(i).AsSlice(), item, GetHelper(),
str.length()));
}
if (IsHyperClock()) {
for (int i = 9000; i < 9030; ++i) {
ASSERT_OK(cache->Insert(ock.WithOffset(i).AsSlice(), nullptr,
&kNoopCacheItemHelper, 256));
}
} else {
cache->SetCapacity(0);
}
ASSERT_EQ(secondary_cache->num_inserts(), 32u);
cache->SetCapacity(32 * 1024);
secondary_cache->SetResultMap(
{{ock.WithOffset(3).AsSlice().ToString(),
TestSecondaryCache::ResultType::DEFER},
{ock.WithOffset(4).AsSlice().ToString(),
TestSecondaryCache::ResultType::DEFER_AND_FAIL},
{ock.WithOffset(5).AsSlice().ToString(),
TestSecondaryCache::ResultType::FAIL}});
std::array<Cache::AsyncLookupHandle, 6> async_handles;
std::array<CacheKey, 6> cache_keys;
for (size_t i = 0; i < async_handles.size(); ++i) {
auto& ah = async_handles[i];
cache_keys[i] = ock.WithOffset(i);
ah.key = cache_keys[i].AsSlice();
ah.helper = GetHelper();
ah.create_context = this;
ah.priority = Cache::Priority::LOW;
cache->StartAsyncLookup(ah);
}
cache->WaitAll(async_handles.data(), async_handles.size());
for (size_t i = 0; i < async_handles.size(); ++i) {
SCOPED_TRACE("i = " + std::to_string(i));
Cache::Handle* result = async_handles[i].Result();
if (i == 4 || i == 5) {
ASSERT_EQ(result, nullptr);
continue;
} else {
ASSERT_NE(result, nullptr);
TestItem* item = static_cast<TestItem*>(cache->Value(result));
ASSERT_EQ(item->ToString(), values[i]);
}
cache->Release(result);
}
cache.reset();
secondary_cache.reset();
}
TEST_P(DBSecondaryCacheTest, TestSecondaryCacheMultiGet) {
if (IsHyperClock()) {
ROCKSDB_GTEST_BYPASS("Test depends on LRUCache-specific behaviors");
return;
}
std::shared_ptr<TestSecondaryCache> secondary_cache(
new TestSecondaryCache(2048 * 1024));
std::shared_ptr<Cache> cache =
NewCache(1 << 20 , 0 ,
false , secondary_cache);
BlockBasedTableOptions table_options;
table_options.block_cache = cache;
table_options.block_size = 4 * 1024;
table_options.cache_index_and_filter_blocks = false;
Options options = GetDefaultOptions();
options.create_if_missing = true;
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
options.paranoid_file_checks = true;
DestroyAndReopen(options);
Random rnd(301);
const int N = 8;
std::vector<std::string> keys;
for (int i = 0; i < N; i++) {
std::string p_v = rnd.RandomString(4000);
keys.emplace_back(p_v);
ASSERT_OK(Put(Key(i), p_v));
}
ASSERT_OK(Flush());
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
ASSERT_EQ(secondary_cache->num_lookups(), 8u);
cache->SetCapacity(0);
ASSERT_EQ(secondary_cache->num_inserts(), 8u);
cache->SetCapacity(1 << 20);
std::vector<std::string> cache_keys;
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"TestSecondaryCache::Lookup", [&cache_keys](void* key) -> void {
cache_keys.emplace_back(*(static_cast<std::string*>(key)));
});
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
for (int i = 0; i < N; ++i) {
std::string v = Get(Key(i));
ASSERT_EQ(4000, v.size());
ASSERT_EQ(v, keys[i]);
}
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
ASSERT_EQ(secondary_cache->num_lookups(), 16u);
cache->SetCapacity(0);
cache->SetCapacity(1 << 20);
ASSERT_EQ(Get(Key(2)), keys[2]);
ASSERT_EQ(Get(Key(7)), keys[7]);
secondary_cache->SetResultMap(
{{cache_keys[3], TestSecondaryCache::ResultType::DEFER},
{cache_keys[4], TestSecondaryCache::ResultType::DEFER_AND_FAIL},
{cache_keys[5], TestSecondaryCache::ResultType::FAIL}});
std::vector<std::string> mget_keys(
{Key(0), Key(1), Key(2), Key(3), Key(4), Key(5), Key(6), Key(7)});
std::vector<PinnableSlice> values(mget_keys.size());
std::vector<Status> s(keys.size());
std::vector<Slice> key_slices;
for (const std::string& key : mget_keys) {
key_slices.emplace_back(key);
}
uint32_t num_lookups = secondary_cache->num_lookups();
dbfull()->MultiGet(ReadOptions(), dbfull()->DefaultColumnFamily(),
key_slices.size(), key_slices.data(), values.data(),
s.data(), false);
ASSERT_EQ(secondary_cache->num_lookups(), num_lookups + 5);
for (int i = 0; i < N; ++i) {
ASSERT_OK(s[i]);
ASSERT_EQ(values[i].ToString(), keys[i]);
values[i].Reset();
}
Destroy(options);
}
class CacheWithStats : public CacheWrapper {
public:
using CacheWrapper::CacheWrapper;
static const char* kClassName() { return "CacheWithStats"; }
const char* Name() const override { return kClassName(); }
Status Insert(const Slice& key, Cache::ObjectPtr value,
const CacheItemHelper* helper, size_t charge,
Handle** handle = nullptr, Priority priority = Priority::LOW,
const Slice& = Slice(),
CompressionType = kNoCompression) override {
insert_count_++;
return target_->Insert(key, value, helper, charge, handle, priority);
}
Handle* Lookup(const Slice& key, const CacheItemHelper* helper,
CreateContext* create_context, Priority priority,
Statistics* stats = nullptr) override {
lookup_count_++;
return target_->Lookup(key, helper, create_context, priority, stats);
}
uint32_t GetInsertCount() { return insert_count_; }
uint32_t GetLookupcount() { return lookup_count_; }
void ResetCount() {
insert_count_ = 0;
lookup_count_ = 0;
}
private:
uint32_t insert_count_ = 0;
uint32_t lookup_count_ = 0;
};
TEST_P(DBSecondaryCacheTest, LRUCacheDumpLoadBasic) {
std::shared_ptr<Cache> base_cache =
NewCache(1024 * 1024 , 0 ,
false );
std::shared_ptr<CacheWithStats> cache =
std::make_shared<CacheWithStats>(base_cache);
BlockBasedTableOptions table_options;
table_options.block_cache = cache;
table_options.block_size = 4 * 1024;
Options options = GetDefaultOptions();
options.create_if_missing = true;
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
options.env = fault_env_.get();
DestroyAndReopen(options);
fault_fs_->SetFailGetUniqueId(true);
Random rnd(301);
const int N = 256;
std::vector<std::string> value;
char buf[1000];
memset(buf, 'a', 1000);
value.resize(N);
for (int i = 0; i < N; i++) {
std::string p_v(buf, 1000);
value[i] = p_v;
ASSERT_OK(Put(Key(i), p_v));
}
ASSERT_OK(Flush());
Compact("a", "z");
uint32_t start_insert = cache->GetInsertCount();
uint32_t start_lookup = cache->GetLookupcount();
std::string v;
for (int i = 0; i < N; i++) {
v = Get(Key(i));
ASSERT_EQ(v, value[i]);
}
uint32_t dump_insert = cache->GetInsertCount() - start_insert;
uint32_t dump_lookup = cache->GetLookupcount() - start_lookup;
ASSERT_EQ(63,
static_cast<int>(dump_insert)); ASSERT_EQ(256,
static_cast<int>(dump_lookup));
CacheDumpOptions cd_options;
cd_options.clock = fault_env_->GetSystemClock().get();
std::string dump_path = db_->GetName() + "/cache_dump";
std::unique_ptr<CacheDumpWriter> dump_writer;
Status s = NewToFileCacheDumpWriter(fault_fs_, FileOptions(), dump_path,
&dump_writer);
ASSERT_OK(s);
std::unique_ptr<CacheDumper> cache_dumper;
s = NewDefaultCacheDumper(cd_options, cache, std::move(dump_writer),
&cache_dumper);
ASSERT_OK(s);
std::vector<DB*> db_list;
db_list.push_back(db_.get());
s = cache_dumper->SetDumpFilter(db_list);
ASSERT_OK(s);
s = cache_dumper->DumpCacheEntriesToWriter();
ASSERT_OK(s);
cache_dumper.reset();
std::shared_ptr<TestSecondaryCache> secondary_cache =
std::make_shared<TestSecondaryCache>(2048 * 1024, true);
base_cache = NewCache(1024 * 1024 , 0 ,
false , secondary_cache);
cache = std::make_shared<CacheWithStats>(base_cache);
table_options.block_cache = cache;
table_options.block_size = 4 * 1024;
options.create_if_missing = true;
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
options.env = fault_env_.get();
start_insert = secondary_cache->num_inserts();
start_lookup = secondary_cache->num_lookups();
std::unique_ptr<CacheDumpReader> dump_reader;
s = NewFromFileCacheDumpReader(fault_fs_, FileOptions(), dump_path,
&dump_reader);
ASSERT_OK(s);
std::unique_ptr<CacheDumpedLoader> cache_loader;
s = NewDefaultCacheDumpedLoader(cd_options, table_options, secondary_cache,
std::move(dump_reader), &cache_loader);
ASSERT_OK(s);
s = cache_loader->RestoreCacheEntriesToSecondaryCache();
ASSERT_OK(s);
uint32_t load_insert = secondary_cache->num_inserts() - start_insert;
uint32_t load_lookup = secondary_cache->num_lookups() - start_lookup;
ASSERT_EQ(64, static_cast<int>(load_insert));
ASSERT_EQ(0, static_cast<int>(load_lookup));
ASSERT_OK(s);
Reopen(options);
start_insert = secondary_cache->num_inserts();
start_lookup = secondary_cache->num_lookups();
uint32_t cache_insert = cache->GetInsertCount();
uint32_t cache_lookup = cache->GetLookupcount();
for (int i = 0; i < N; i++) {
v = Get(Key(i));
ASSERT_EQ(v, value[i]);
}
uint32_t final_insert = secondary_cache->num_inserts() - start_insert;
uint32_t final_lookup = secondary_cache->num_lookups() - start_lookup;
ASSERT_EQ(0, static_cast<int>(final_insert));
ASSERT_EQ(64, static_cast<int>(final_lookup));
uint32_t block_insert = cache->GetInsertCount() - cache_insert;
uint32_t block_lookup = cache->GetLookupcount() - cache_lookup;
ASSERT_EQ(0, static_cast<int>(block_insert));
ASSERT_EQ(256, static_cast<int>(block_lookup));
fault_fs_->SetFailGetUniqueId(false);
Destroy(options);
}
TEST_P(DBSecondaryCacheTest, LRUCacheDumpLoadWithFilter) {
std::shared_ptr<Cache> base_cache =
NewCache(1024 * 1024 , 0 ,
false );
std::shared_ptr<CacheWithStats> cache =
std::make_shared<CacheWithStats>(base_cache);
BlockBasedTableOptions table_options;
table_options.block_cache = cache;
table_options.block_size = 4 * 1024;
Options options = GetDefaultOptions();
options.create_if_missing = true;
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
options.env = fault_env_.get();
std::string dbname1 = test::PerThreadDBPath("db_1");
ASSERT_OK(DestroyDB(dbname1, options));
std::unique_ptr<DB> db1;
ASSERT_OK(DB::Open(options, dbname1, &db1));
std::string dbname2 = test::PerThreadDBPath("db_2");
ASSERT_OK(DestroyDB(dbname2, options));
std::unique_ptr<DB> db2;
ASSERT_OK(DB::Open(options, dbname2, &db2));
fault_fs_->SetFailGetUniqueId(true);
Random rnd(301);
const int N = 256;
std::vector<std::string> value1;
WriteOptions wo;
char buf[1000];
memset(buf, 'a', 1000);
value1.resize(N);
for (int i = 0; i < N; i++) {
std::string p_v(buf, 1000);
value1[i] = p_v;
ASSERT_OK(db1->Put(wo, Key(i), p_v));
}
ASSERT_OK(db1->Flush(FlushOptions()));
Slice bg("a");
Slice ed("b");
ASSERT_OK(db1->CompactRange(CompactRangeOptions(), &bg, &ed));
std::vector<std::string> value2;
memset(buf, 'b', 1000);
value2.resize(N);
for (int i = 0; i < N; i++) {
std::string p_v(buf, 1000);
value2[i] = p_v;
ASSERT_OK(db2->Put(wo, Key(i), p_v));
}
ASSERT_OK(db2->Flush(FlushOptions()));
ASSERT_OK(db2->CompactRange(CompactRangeOptions(), &bg, &ed));
uint32_t start_insert = cache->GetInsertCount();
uint32_t start_lookup = cache->GetLookupcount();
ReadOptions ro;
std::string v;
for (int i = 0; i < N; i++) {
ASSERT_OK(db1->Get(ro, Key(i), &v));
ASSERT_EQ(v, value1[i]);
}
for (int i = 0; i < N; i++) {
ASSERT_OK(db2->Get(ro, Key(i), &v));
ASSERT_EQ(v, value2[i]);
}
uint32_t dump_insert = cache->GetInsertCount() - start_insert;
uint32_t dump_lookup = cache->GetLookupcount() - start_lookup;
ASSERT_EQ(128,
static_cast<int>(dump_insert)); ASSERT_EQ(512,
static_cast<int>(dump_lookup));
CacheDumpOptions cd_options;
cd_options.clock = fault_env_->GetSystemClock().get();
std::string dump_path = db1->GetName() + "/cache_dump";
std::unique_ptr<CacheDumpWriter> dump_writer;
Status s = NewToFileCacheDumpWriter(fault_fs_, FileOptions(), dump_path,
&dump_writer);
ASSERT_OK(s);
std::unique_ptr<CacheDumper> cache_dumper;
s = NewDefaultCacheDumper(cd_options, cache, std::move(dump_writer),
&cache_dumper);
ASSERT_OK(s);
std::vector<DB*> db_list;
db_list.push_back(db1.get());
s = cache_dumper->SetDumpFilter(db_list);
ASSERT_OK(s);
s = cache_dumper->DumpCacheEntriesToWriter();
ASSERT_OK(s);
cache_dumper.reset();
std::shared_ptr<TestSecondaryCache> secondary_cache =
std::make_shared<TestSecondaryCache>(2048 * 1024, true);
base_cache = NewCache(1024 * 1024 , 0 ,
false , secondary_cache);
cache = std::make_shared<CacheWithStats>(base_cache);
table_options.block_cache = cache;
table_options.block_size = 4 * 1024;
options.create_if_missing = true;
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
options.env = fault_env_.get();
start_insert = secondary_cache->num_inserts();
start_lookup = secondary_cache->num_lookups();
std::unique_ptr<CacheDumpReader> dump_reader;
s = NewFromFileCacheDumpReader(fault_fs_, FileOptions(), dump_path,
&dump_reader);
ASSERT_OK(s);
std::unique_ptr<CacheDumpedLoader> cache_loader;
s = NewDefaultCacheDumpedLoader(cd_options, table_options, secondary_cache,
std::move(dump_reader), &cache_loader);
ASSERT_OK(s);
s = cache_loader->RestoreCacheEntriesToSecondaryCache();
ASSERT_OK(s);
uint32_t load_insert = secondary_cache->num_inserts() - start_insert;
uint32_t load_lookup = secondary_cache->num_lookups() - start_lookup;
ASSERT_EQ(64, static_cast<int>(load_insert));
ASSERT_EQ(0, static_cast<int>(load_lookup));
ASSERT_OK(s);
ASSERT_OK(db1->Close());
db1.reset();
ASSERT_OK(DB::Open(options, dbname1, &db1));
IOStatus error_msg = IOStatus::IOError("Retryable IO Error");
fault_fs_->SetFilesystemActive(false, error_msg);
start_insert = secondary_cache->num_inserts();
start_lookup = secondary_cache->num_lookups();
uint32_t cache_insert = cache->GetInsertCount();
uint32_t cache_lookup = cache->GetLookupcount();
for (int i = 0; i < N; i++) {
ASSERT_OK(db1->Get(ro, Key(i), &v));
ASSERT_EQ(v, value1[i]);
}
uint32_t final_insert = secondary_cache->num_inserts() - start_insert;
uint32_t final_lookup = secondary_cache->num_lookups() - start_lookup;
ASSERT_EQ(0, static_cast<int>(final_insert));
ASSERT_EQ(64, static_cast<int>(final_lookup));
uint32_t block_insert = cache->GetInsertCount() - cache_insert;
uint32_t block_lookup = cache->GetLookupcount() - cache_lookup;
ASSERT_EQ(0, static_cast<int>(block_insert));
ASSERT_EQ(256, static_cast<int>(block_lookup));
fault_fs_->SetFailGetUniqueId(false);
fault_fs_->SetFilesystemActive(true);
db1.reset();
db2.reset();
ASSERT_OK(DestroyDB(dbname1, options));
ASSERT_OK(DestroyDB(dbname2, options));
}
TEST_P(DBSecondaryCacheTest, TestSecondaryCacheOptionBasic) {
std::shared_ptr<TestSecondaryCache> secondary_cache(
new TestSecondaryCache(2048 * 1024));
std::shared_ptr<Cache> cache =
NewCache(4 * 1024 , 0 ,
false , secondary_cache);
BlockBasedTableOptions table_options;
table_options.block_cache = cache;
table_options.block_size = 4 * 1024;
Options options = GetDefaultOptions();
options.create_if_missing = true;
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
options.env = fault_env_.get();
fault_fs_->SetFailGetUniqueId(true);
options.lowest_used_cache_tier = CacheTier::kVolatileTier;
options.paranoid_file_checks = true;
DestroyAndReopen(options);
Random rnd(301);
const int N = 6;
for (int i = 0; i < N; i++) {
std::string p_v = rnd.RandomString(1007);
ASSERT_OK(Put(Key(i), p_v));
}
ASSERT_OK(Flush());
for (int i = 0; i < N; i++) {
std::string p_v = rnd.RandomString(1007);
ASSERT_OK(Put(Key(i + 70), p_v));
}
ASSERT_OK(Flush());
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
ASSERT_EQ(secondary_cache->num_lookups(), 0u);
Compact("a", "z");
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
ASSERT_EQ(secondary_cache->num_lookups(), 0u);
std::string v = Get(Key(0));
ASSERT_EQ(1007, v.size());
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
ASSERT_EQ(secondary_cache->num_lookups(), 0u);
v = Get(Key(5));
ASSERT_EQ(1007, v.size());
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
ASSERT_EQ(secondary_cache->num_lookups(), 0u);
v = Get(Key(5));
ASSERT_EQ(1007, v.size());
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
ASSERT_EQ(secondary_cache->num_lookups(), 0u);
v = Get(Key(70));
ASSERT_EQ(1007, v.size());
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
ASSERT_EQ(secondary_cache->num_lookups(), 0u);
v = Get(Key(75));
ASSERT_EQ(1007, v.size());
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
ASSERT_EQ(secondary_cache->num_lookups(), 0u);
Destroy(options);
}
TEST_P(DBSecondaryCacheTest, TestSecondaryCacheOptionChange) {
if (IsHyperClock()) {
ROCKSDB_GTEST_BYPASS("Test depends on LRUCache-specific behaviors");
return;
}
std::shared_ptr<TestSecondaryCache> secondary_cache(
new TestSecondaryCache(2048 * 1024));
std::shared_ptr<Cache> cache =
NewCache(4 * 1024 , 0 ,
false , secondary_cache);
BlockBasedTableOptions table_options;
table_options.block_cache = cache;
table_options.block_size = 4 * 1024;
Options options = GetDefaultOptions();
options.create_if_missing = true;
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
options.env = fault_env_.get();
fault_fs_->SetFailGetUniqueId(true);
options.lowest_used_cache_tier = CacheTier::kVolatileTier;
options.paranoid_file_checks = true;
DestroyAndReopen(options);
Random rnd(301);
const int N = 6;
for (int i = 0; i < N; i++) {
std::string p_v = rnd.RandomString(1007);
ASSERT_OK(Put(Key(i), p_v));
}
ASSERT_OK(Flush());
for (int i = 0; i < N; i++) {
std::string p_v = rnd.RandomString(1007);
ASSERT_OK(Put(Key(i + 70), p_v));
}
ASSERT_OK(Flush());
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
ASSERT_EQ(secondary_cache->num_lookups(), 0u);
Compact("a", "z");
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
ASSERT_EQ(secondary_cache->num_lookups(), 0u);
std::string v = Get(Key(0));
ASSERT_EQ(1007, v.size());
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
ASSERT_EQ(secondary_cache->num_lookups(), 0u);
v = Get(Key(5));
ASSERT_EQ(1007, v.size());
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
ASSERT_EQ(secondary_cache->num_lookups(), 0u);
v = Get(Key(5));
ASSERT_EQ(1007, v.size());
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
ASSERT_EQ(secondary_cache->num_lookups(), 0u);
options.lowest_used_cache_tier = CacheTier::kNonVolatileBlockTier;
Reopen(options);
v = Get(Key(70));
ASSERT_EQ(1007, v.size());
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
ASSERT_EQ(secondary_cache->num_lookups(), 1u);
v = Get(Key(75));
ASSERT_EQ(1007, v.size());
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
ASSERT_EQ(secondary_cache->num_lookups(), 2u);
Destroy(options);
}
TEST_P(DBSecondaryCacheTest, TestSecondaryCacheOptionTwoDB) {
if (IsHyperClock()) {
ROCKSDB_GTEST_BYPASS("Test depends on LRUCache-specific behaviors");
return;
}
std::shared_ptr<TestSecondaryCache> secondary_cache(
new TestSecondaryCache(2048 * 1024));
std::shared_ptr<Cache> cache =
NewCache(4 * 1024 , 0 ,
false , secondary_cache);
BlockBasedTableOptions table_options;
table_options.block_cache = cache;
table_options.block_size = 4 * 1024;
Options options = GetDefaultOptions();
options.create_if_missing = true;
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
options.env = fault_env_.get();
options.paranoid_file_checks = true;
std::string dbname1 = test::PerThreadDBPath("db_t_1");
ASSERT_OK(DestroyDB(dbname1, options));
std::unique_ptr<DB> db1;
ASSERT_OK(DB::Open(options, dbname1, &db1));
std::string dbname2 = test::PerThreadDBPath("db_t_2");
ASSERT_OK(DestroyDB(dbname2, options));
std::unique_ptr<DB> db2;
Options options2 = options;
options2.lowest_used_cache_tier = CacheTier::kVolatileTier;
ASSERT_OK(DB::Open(options2, dbname2, &db2));
fault_fs_->SetFailGetUniqueId(true);
WriteOptions wo;
Random rnd(301);
const int N = 6;
for (int i = 0; i < N; i++) {
std::string p_v = rnd.RandomString(1007);
ASSERT_OK(db1->Put(wo, Key(i), p_v));
}
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
ASSERT_EQ(secondary_cache->num_lookups(), 0u);
ASSERT_OK(db1->Flush(FlushOptions()));
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
ASSERT_EQ(secondary_cache->num_lookups(), 2u);
for (int i = 0; i < N; i++) {
std::string p_v = rnd.RandomString(1007);
ASSERT_OK(db2->Put(wo, Key(i), p_v));
}
ASSERT_EQ(secondary_cache->num_inserts(), 0u);
ASSERT_EQ(secondary_cache->num_lookups(), 2u);
ASSERT_OK(db2->Flush(FlushOptions()));
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
ASSERT_EQ(secondary_cache->num_lookups(), 2u);
Slice bg("a");
Slice ed("b");
ASSERT_OK(db1->CompactRange(CompactRangeOptions(), &bg, &ed));
ASSERT_OK(db2->CompactRange(CompactRangeOptions(), &bg, &ed));
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
ASSERT_EQ(secondary_cache->num_lookups(), 2u);
ReadOptions ro;
std::string v;
ASSERT_OK(db1->Get(ro, Key(0), &v));
ASSERT_EQ(1007, v.size());
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
ASSERT_EQ(secondary_cache->num_lookups(), 3u);
ASSERT_OK(db1->Get(ro, Key(5), &v));
ASSERT_EQ(1007, v.size());
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
ASSERT_EQ(secondary_cache->num_lookups(), 4u);
ASSERT_OK(db2->Get(ro, Key(0), &v));
ASSERT_EQ(1007, v.size());
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
ASSERT_EQ(secondary_cache->num_lookups(), 4u);
ASSERT_OK(db2->Get(ro, Key(5), &v));
ASSERT_EQ(1007, v.size());
ASSERT_EQ(secondary_cache->num_inserts(), 1u);
ASSERT_EQ(secondary_cache->num_lookups(), 4u);
fault_fs_->SetFailGetUniqueId(false);
fault_fs_->SetFilesystemActive(true);
db1.reset();
db2.reset();
ASSERT_OK(DestroyDB(dbname1, options));
ASSERT_OK(DestroyDB(dbname2, options));
}
TEST_F(LRUCacheTest, InsertAfterReducingCapacity) {
NewCache(10, 0.2,
0.8);
Insert("x", Cache::Priority::HIGH);
Insert("y", Cache::Priority::HIGH);
cache_->SetCapacity(5);
Insert("aaa", Cache::Priority::LOW, 3);
}
}
int main(int argc, char** argv) {
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}