mirror of
https://github.com/facebook/rocksdb.git
synced 2024-12-02 20:52:55 +00:00
204fcff751
Summary: Internally refactors SecondaryCache integration out of LRUCache specifically and into a wrapper/adapter class that works with various Cache implementations. Notably, this relies on separating the notion of async lookup handles from other cache handles, so that HyperClockCache doesn't have to deal with the problem of allocating handles from the hash table for lookups that might fail anyway, and might be on the same key without support for coalescing. (LRUCache's hash table can incorporate previously allocated handles thanks to its pointer indirection.) Specifically, I'm worried about the case in which hundreds of threads try to access the same block and probing in the hash table degrades to linear search on the pile of entries with the same key. This change is a big step in the direction of supporting stacked SecondaryCaches, but there are obstacles to completing that. Especially, there is no SecondaryCache hook for evictions to pass from one to the next. It has been proposed that evictions be transmitted simply as the persisted data (as in SaveToCallback), but given the current structure provided by the CacheItemHelpers, that would require an extra copy of the block data, because there's intentionally no way to ask for a contiguous Slice of the data (to allow for flexibility in storage). `AsyncLookupHandle` and the re-worked `WaitAll()` should be essentially prepared for stacked SecondaryCaches, but several "TODO with stacked secondaries" issues remain in various places. It could be argued that the stacking instead be done as a SecondaryCache adapter that wraps two (or more) SecondaryCaches, but at least with the current API that would require an extra heap allocation on SecondaryCache Lookup for a wrapper SecondaryCacheResultHandle that can transfer a Lookup between secondaries. We could also consider trying to unify the Cache and SecondaryCache APIs, though that might be difficult if `AsyncLookupHandle` is kept a fixed struct. ## cache.h (public API) Moves `secondary_cache` option from LRUCacheOptions to ShardedCacheOptions so that it is applicable to HyperClockCache. ## advanced_cache.h (advanced public API) * Add `Cache::CreateStandalone()` so that the SecondaryCache support wrapper can use it. * Add `SetEvictionCallback()` / `eviction_callback_` so that the SecondaryCache support wrapper can use it. Only a single callback is supported for efficiency. If there is ever a need for more than one, hopefully that can be handled with a broadcast callback wrapper. These are essentially the two "extra" pieces of `Cache` for pulling out specific SecondaryCache support from the `Cache` implementation. I think it's a good trade-off as these are reasonable, limited, and reusable "cut points" into the `Cache` implementations. * Remove async capability from standard `Lookup()` (getting rid of awkward restrictions on pending Handles) and add `AsyncLookupHandle` and `StartAsyncLookup()`. As noted in the comments, the full struct of `AsyncLookupHandle` is exposed so that it can be stack allocated, for efficiency, though more data is being copied around than before, which could impact performance. (Lookup info -> AsyncLookupHandle -> Handle vs. Lookup info -> Handle) I could foresee a future in which a Cache internally saves a pointer to the AsyncLookupHandle, which means it's dangerous to allow it to be copyable or even movable. It also means it's not compatible with std::vector (which I don't like requiring as an API parameter anyway), so `WaitAll()` expects any contiguous array of AsyncLookupHandles. I believe this is best for common case efficiency, while behaving well in other cases also. For example, `WaitAll()` has no effect on default-constructed AsyncLookupHandles, which look like a completed cache miss. ## cacheable_entry.h A couple of functions are obsolete because Cache::Handle can no longer be pending. ## cache.cc Provides default implementations for new or revamped Cache functions, especially appropriate for non-blocking caches. ## secondary_cache_adapter.{h,cc} The full details of the Cache wrapper adding SecondaryCache support. Essentially replicates the SecondaryCache handling that was in LRUCache, but obviously refactored. There is a bit of logic duplication, where Lookup() is essentially a manually optimized version of StartAsyncLookup() and Wait(), but it's roughly a dozen lines of code. ## sharded_cache.h, typed_cache.h, charged_cache.{h,cc}, sim_cache.cc Simply updated for Cache API changes. ## lru_cache.{h,cc} Carefully remove SecondaryCache logic, implement `CreateStandalone` and eviction handler functionality. ## clock_cache.{h,cc} Expose existing `CreateStandalone` functionality, add eviction handler functionality. Light refactoring. ## block_based_table_reader* Mostly re-worked the only usage of async Lookup, which is in BlockBasedTable::MultiGet. Used arrays in place of autovector in some places for efficiency. Simplified some logic by not trying to process some cache results before they're all ready. Created new function `BlockBasedTable::GetCachePriority()` to reduce some pre-existing code duplication (and avoid making it worse). Fixed at least one small bug from the prior confusing mixture of async and sync Lookups. In MaybeReadBlockAndLoadToCache(), called by RetrieveBlock(), called by MultiGet() with wait=false, is_cache_hit for the block_cache_tracer entry would not be set to true if the handle was pending after Lookup and before Wait. ## Intended follow-up work * Figure out if there are any missing stats or block_cache_tracer work in refactored BlockBasedTable::MultiGet * Stacked secondary caches (see above discussion) * See if we can make up for the small MultiGet performance regression. * Study more performance with SecondaryCache * Items evicted from over-full LRUCache in Release were not being demoted to SecondaryCache, and still aren't to minimize unit test churn. Ideally they would be demoted, but it's an exceptional case so not a big deal. * Use CreateStandalone for cache reservations (save unnecessary hash table operations). Not a big deal, but worthy cleanup. * Somehow I got the contract for SecondaryCache::Insert wrong in #10945. (Doesn't take ownership!) That API comment needs to be fixed, but didn't want to mingle that in here. Pull Request resolved: https://github.com/facebook/rocksdb/pull/11301 Test Plan: ## Unit tests Generally updated to include HCC in SecondaryCache tests, though HyperClockCache has some different, less strict behaviors that leads to some tests not really being set up to work with it. Some of the tests remain disabled with it, but I think we have good coverage without them. ## Crash/stress test Updated to use the new combination. ## Performance First, let's check for regression on caches without secondary cache configured. Adding support for the eviction callback is likely to have a tiny effect, but it shouldn't be worrisome. LRUCache could benefit slightly from less logic around SecondaryCache handling. We can test with cache_bench default settings, built with DEBUG_LEVEL=0 and PORTABLE=0. ``` (while :; do base/cache_bench --cache_type=hyper_clock_cache | grep Rough; done) | awk '{ sum += $9; count++; print $0; print "Average: " int(sum / count) }' ``` **Before** this and #11299 (which could also have a small effect), running for about an hour, before & after running concurrently for each cache type: HyperClockCache: 3168662 (average parallel ops/sec) LRUCache: 2940127 **After** this and #11299, running for about an hour: HyperClockCache: 3164862 (average parallel ops/sec) (0.12% slower) LRUCache: 2940928 (0.03% faster) This is an acceptable difference IMHO. Next, let's consider essentially the worst case of new CPU overhead affecting overall performance. MultiGet uses the async lookup interface regardless of whether SecondaryCache or folly are used. We can configure a benchmark where all block cache queries are for data blocks, and all are hits. Create DB and test (before and after tests running simultaneously): ``` TEST_TMPDIR=/dev/shm ./db_bench -benchmarks=fillrandom -num=30000000 -disable_wal=1 -bloom_bits=16 TEST_TMPDIR=/dev/shm base/db_bench -benchmarks=multireadrandom[-X30] -readonly -multiread_batched -batch_size=32 -num=30000000 -bloom_bits=16 -cache_size=6789000000 -duration 20 -threads=16 ``` **Before**: multireadrandom [AVG 30 runs] : 3444202 (± 57049) ops/sec; 240.9 (± 4.0) MB/sec multireadrandom [MEDIAN 30 runs] : 3514443 ops/sec; 245.8 MB/sec **After**: multireadrandom [AVG 30 runs] : 3291022 (± 58851) ops/sec; 230.2 (± 4.1) MB/sec multireadrandom [MEDIAN 30 runs] : 3366179 ops/sec; 235.4 MB/sec So that's roughly a 3% regression, on kind of a *worst case* test of MultiGet CPU. Similar story with HyperClockCache: **Before**: multireadrandom [AVG 30 runs] : 3933777 (± 41840) ops/sec; 275.1 (± 2.9) MB/sec multireadrandom [MEDIAN 30 runs] : 3970667 ops/sec; 277.7 MB/sec **After**: multireadrandom [AVG 30 runs] : 3755338 (± 30391) ops/sec; 262.6 (± 2.1) MB/sec multireadrandom [MEDIAN 30 runs] : 3785696 ops/sec; 264.8 MB/sec Roughly a 4-5% regression. Not ideal, but not the whole story, fortunately. Let's also look at Get() in db_bench: ``` TEST_TMPDIR=/dev/shm ./db_bench -benchmarks=readrandom[-X30] -readonly -num=30000000 -bloom_bits=16 -cache_size=6789000000 -duration 20 -threads=16 ``` **Before**: readrandom [AVG 30 runs] : 2198685 (± 13412) ops/sec; 153.8 (± 0.9) MB/sec readrandom [MEDIAN 30 runs] : 2209498 ops/sec; 154.5 MB/sec **After**: readrandom [AVG 30 runs] : 2292814 (± 43508) ops/sec; 160.3 (± 3.0) MB/sec readrandom [MEDIAN 30 runs] : 2365181 ops/sec; 165.4 MB/sec That's showing roughly a 4% improvement, perhaps because of the secondary cache code that is no longer part of LRUCache. But weirdly, HyperClockCache is also showing 2-3% improvement: **Before**: readrandom [AVG 30 runs] : 2272333 (± 9992) ops/sec; 158.9 (± 0.7) MB/sec readrandom [MEDIAN 30 runs] : 2273239 ops/sec; 159.0 MB/sec **After**: readrandom [AVG 30 runs] : 2332407 (± 11252) ops/sec; 163.1 (± 0.8) MB/sec readrandom [MEDIAN 30 runs] : 2335329 ops/sec; 163.3 MB/sec Reviewed By: ltamasi Differential Revision: D44177044 Pulled By: pdillinger fbshipit-source-id: e808e48ff3fe2f792a79841ba617be98e48689f5
981 lines
38 KiB
C++
981 lines
38 KiB
C++
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
|
// This source code is licensed under both the GPLv2 (found in the
|
|
// COPYING file in the root directory) and Apache 2.0 License
|
|
// (found in the LICENSE.Apache file in the root directory).
|
|
|
|
#include "cache/compressed_secondary_cache.h"
|
|
|
|
#include <array>
|
|
#include <iterator>
|
|
#include <memory>
|
|
#include <tuple>
|
|
|
|
#include "memory/jemalloc_nodump_allocator.h"
|
|
#include "rocksdb/convenience.h"
|
|
#include "test_util/secondary_cache_test_util.h"
|
|
#include "test_util/testharness.h"
|
|
#include "test_util/testutil.h"
|
|
|
|
namespace ROCKSDB_NAMESPACE {
|
|
|
|
using secondary_cache_test_util::GetTestingCacheTypes;
|
|
using secondary_cache_test_util::WithCacheType;
|
|
|
|
// 16 bytes for HCC compatibility
|
|
const std::string key0 = "____ ____key0";
|
|
const std::string key1 = "____ ____key1";
|
|
const std::string key2 = "____ ____key2";
|
|
const std::string key3 = "____ ____key3";
|
|
|
|
class CompressedSecondaryCacheTestBase : public testing::Test,
|
|
public WithCacheType {
|
|
public:
|
|
CompressedSecondaryCacheTestBase() {}
|
|
~CompressedSecondaryCacheTestBase() override = default;
|
|
|
|
protected:
|
|
void BasicTestHelper(std::shared_ptr<SecondaryCache> sec_cache,
|
|
bool sec_cache_is_compressed) {
|
|
get_perf_context()->Reset();
|
|
bool kept_in_sec_cache{true};
|
|
// Lookup an non-existent key.
|
|
std::unique_ptr<SecondaryCacheResultHandle> handle0 =
|
|
sec_cache->Lookup(key0, GetHelper(), this, true, /*advise_erase=*/true,
|
|
kept_in_sec_cache);
|
|
ASSERT_EQ(handle0, nullptr);
|
|
|
|
Random rnd(301);
|
|
// Insert and Lookup the item k1 for the first time.
|
|
std::string str1(rnd.RandomString(1000));
|
|
TestItem item1(str1.data(), str1.length());
|
|
// A dummy handle is inserted if the item is inserted for the first time.
|
|
ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper()));
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 1);
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0);
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0);
|
|
|
|
std::unique_ptr<SecondaryCacheResultHandle> handle1_1 =
|
|
sec_cache->Lookup(key1, GetHelper(), this, true, /*advise_erase=*/false,
|
|
kept_in_sec_cache);
|
|
ASSERT_EQ(handle1_1, nullptr);
|
|
|
|
// Insert and Lookup the item k1 for the second time and advise erasing it.
|
|
ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper()));
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 1);
|
|
|
|
std::unique_ptr<SecondaryCacheResultHandle> handle1_2 =
|
|
sec_cache->Lookup(key1, GetHelper(), this, true, /*advise_erase=*/true,
|
|
kept_in_sec_cache);
|
|
ASSERT_NE(handle1_2, nullptr);
|
|
ASSERT_FALSE(kept_in_sec_cache);
|
|
if (sec_cache_is_compressed) {
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes,
|
|
1000);
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes,
|
|
1007);
|
|
} else {
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0);
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0);
|
|
}
|
|
|
|
std::unique_ptr<TestItem> val1 =
|
|
std::unique_ptr<TestItem>(static_cast<TestItem*>(handle1_2->Value()));
|
|
ASSERT_NE(val1, nullptr);
|
|
ASSERT_EQ(memcmp(val1->Buf(), item1.Buf(), item1.Size()), 0);
|
|
|
|
// Lookup the item k1 again.
|
|
std::unique_ptr<SecondaryCacheResultHandle> handle1_3 =
|
|
sec_cache->Lookup(key1, GetHelper(), this, true, /*advise_erase=*/true,
|
|
kept_in_sec_cache);
|
|
ASSERT_EQ(handle1_3, nullptr);
|
|
|
|
// Insert and Lookup the item k2.
|
|
std::string str2(rnd.RandomString(1000));
|
|
TestItem item2(str2.data(), str2.length());
|
|
ASSERT_OK(sec_cache->Insert(key2, &item2, GetHelper()));
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 2);
|
|
std::unique_ptr<SecondaryCacheResultHandle> handle2_1 =
|
|
sec_cache->Lookup(key2, GetHelper(), this, true, /*advise_erase=*/false,
|
|
kept_in_sec_cache);
|
|
ASSERT_EQ(handle2_1, nullptr);
|
|
|
|
ASSERT_OK(sec_cache->Insert(key2, &item2, GetHelper()));
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 2);
|
|
if (sec_cache_is_compressed) {
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes,
|
|
2000);
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes,
|
|
2014);
|
|
} else {
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0);
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0);
|
|
}
|
|
std::unique_ptr<SecondaryCacheResultHandle> handle2_2 =
|
|
sec_cache->Lookup(key2, GetHelper(), this, true, /*advise_erase=*/false,
|
|
kept_in_sec_cache);
|
|
ASSERT_NE(handle2_2, nullptr);
|
|
std::unique_ptr<TestItem> val2 =
|
|
std::unique_ptr<TestItem>(static_cast<TestItem*>(handle2_2->Value()));
|
|
ASSERT_NE(val2, nullptr);
|
|
ASSERT_EQ(memcmp(val2->Buf(), item2.Buf(), item2.Size()), 0);
|
|
|
|
std::vector<SecondaryCacheResultHandle*> handles = {handle1_2.get(),
|
|
handle2_2.get()};
|
|
sec_cache->WaitAll(handles);
|
|
|
|
sec_cache.reset();
|
|
}
|
|
|
|
void BasicTest(bool sec_cache_is_compressed, bool use_jemalloc) {
|
|
CompressedSecondaryCacheOptions opts;
|
|
opts.capacity = 2048;
|
|
opts.num_shard_bits = 0;
|
|
|
|
if (sec_cache_is_compressed) {
|
|
if (!LZ4_Supported()) {
|
|
ROCKSDB_GTEST_SKIP("This test requires LZ4 support.");
|
|
opts.compression_type = CompressionType::kNoCompression;
|
|
sec_cache_is_compressed = false;
|
|
}
|
|
} else {
|
|
opts.compression_type = CompressionType::kNoCompression;
|
|
}
|
|
|
|
if (use_jemalloc) {
|
|
JemallocAllocatorOptions jopts;
|
|
std::shared_ptr<MemoryAllocator> allocator;
|
|
std::string msg;
|
|
if (JemallocNodumpAllocator::IsSupported(&msg)) {
|
|
Status s = NewJemallocNodumpAllocator(jopts, &allocator);
|
|
if (s.ok()) {
|
|
opts.memory_allocator = allocator;
|
|
}
|
|
} else {
|
|
ROCKSDB_GTEST_BYPASS("JEMALLOC not supported");
|
|
}
|
|
}
|
|
std::shared_ptr<SecondaryCache> sec_cache =
|
|
NewCompressedSecondaryCache(opts);
|
|
|
|
BasicTestHelper(sec_cache, sec_cache_is_compressed);
|
|
}
|
|
|
|
void FailsTest(bool sec_cache_is_compressed) {
|
|
CompressedSecondaryCacheOptions secondary_cache_opts;
|
|
if (sec_cache_is_compressed) {
|
|
if (!LZ4_Supported()) {
|
|
ROCKSDB_GTEST_SKIP("This test requires LZ4 support.");
|
|
secondary_cache_opts.compression_type = CompressionType::kNoCompression;
|
|
}
|
|
} else {
|
|
secondary_cache_opts.compression_type = CompressionType::kNoCompression;
|
|
}
|
|
|
|
secondary_cache_opts.capacity = 1100;
|
|
secondary_cache_opts.num_shard_bits = 0;
|
|
std::shared_ptr<SecondaryCache> sec_cache =
|
|
NewCompressedSecondaryCache(secondary_cache_opts);
|
|
|
|
// Insert and Lookup the first item.
|
|
Random rnd(301);
|
|
std::string str1(rnd.RandomString(1000));
|
|
TestItem item1(str1.data(), str1.length());
|
|
// Insert a dummy handle.
|
|
ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper()));
|
|
// Insert k1.
|
|
ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper()));
|
|
|
|
// Insert and Lookup the second item.
|
|
std::string str2(rnd.RandomString(200));
|
|
TestItem item2(str2.data(), str2.length());
|
|
// Insert a dummy handle, k1 is not evicted.
|
|
ASSERT_OK(sec_cache->Insert(key2, &item2, GetHelper()));
|
|
bool kept_in_sec_cache{false};
|
|
std::unique_ptr<SecondaryCacheResultHandle> handle1 =
|
|
sec_cache->Lookup(key1, GetHelper(), this, true, /*advise_erase=*/false,
|
|
kept_in_sec_cache);
|
|
ASSERT_EQ(handle1, nullptr);
|
|
|
|
// Insert k2 and k1 is evicted.
|
|
ASSERT_OK(sec_cache->Insert(key2, &item2, GetHelper()));
|
|
std::unique_ptr<SecondaryCacheResultHandle> handle2 =
|
|
sec_cache->Lookup(key2, GetHelper(), this, true, /*advise_erase=*/false,
|
|
kept_in_sec_cache);
|
|
ASSERT_NE(handle2, nullptr);
|
|
std::unique_ptr<TestItem> val2 =
|
|
std::unique_ptr<TestItem>(static_cast<TestItem*>(handle2->Value()));
|
|
ASSERT_NE(val2, nullptr);
|
|
ASSERT_EQ(memcmp(val2->Buf(), item2.Buf(), item2.Size()), 0);
|
|
|
|
// Insert k1 again and a dummy handle is inserted.
|
|
ASSERT_OK(sec_cache->Insert(key1, &item1, GetHelper()));
|
|
|
|
std::unique_ptr<SecondaryCacheResultHandle> handle1_1 =
|
|
sec_cache->Lookup(key1, GetHelper(), this, true, /*advise_erase=*/false,
|
|
kept_in_sec_cache);
|
|
ASSERT_EQ(handle1_1, nullptr);
|
|
|
|
// Create Fails.
|
|
SetFailCreate(true);
|
|
std::unique_ptr<SecondaryCacheResultHandle> handle2_1 =
|
|
sec_cache->Lookup(key2, GetHelper(), this, true, /*advise_erase=*/true,
|
|
kept_in_sec_cache);
|
|
ASSERT_EQ(handle2_1, nullptr);
|
|
|
|
// Save Fails.
|
|
std::string str3 = rnd.RandomString(10);
|
|
TestItem item3(str3.data(), str3.length());
|
|
// The Status is OK because a dummy handle is inserted.
|
|
ASSERT_OK(sec_cache->Insert(key3, &item3, GetHelperFail()));
|
|
ASSERT_NOK(sec_cache->Insert(key3, &item3, GetHelperFail()));
|
|
|
|
sec_cache.reset();
|
|
}
|
|
|
|
void BasicIntegrationTest(bool sec_cache_is_compressed,
|
|
bool enable_custom_split_merge) {
|
|
CompressedSecondaryCacheOptions secondary_cache_opts;
|
|
|
|
if (sec_cache_is_compressed) {
|
|
if (!LZ4_Supported()) {
|
|
ROCKSDB_GTEST_SKIP("This test requires LZ4 support.");
|
|
secondary_cache_opts.compression_type = CompressionType::kNoCompression;
|
|
sec_cache_is_compressed = false;
|
|
}
|
|
} else {
|
|
secondary_cache_opts.compression_type = CompressionType::kNoCompression;
|
|
}
|
|
|
|
secondary_cache_opts.capacity = 6000;
|
|
secondary_cache_opts.num_shard_bits = 0;
|
|
secondary_cache_opts.enable_custom_split_merge = enable_custom_split_merge;
|
|
std::shared_ptr<SecondaryCache> secondary_cache =
|
|
NewCompressedSecondaryCache(secondary_cache_opts);
|
|
std::shared_ptr<Cache> cache = NewCache(
|
|
/*_capacity =*/1300, /*_num_shard_bits =*/0,
|
|
/*_strict_capacity_limit =*/true, secondary_cache);
|
|
std::shared_ptr<Statistics> stats = CreateDBStatistics();
|
|
|
|
get_perf_context()->Reset();
|
|
Random rnd(301);
|
|
std::string str1 = rnd.RandomString(1001);
|
|
auto item1_1 = new TestItem(str1.data(), str1.length());
|
|
ASSERT_OK(cache->Insert(key1, item1_1, GetHelper(), str1.length()));
|
|
|
|
std::string str2 = rnd.RandomString(1012);
|
|
auto item2_1 = new TestItem(str2.data(), str2.length());
|
|
// After this Insert, primary cache contains k2 and secondary cache contains
|
|
// k1's dummy item.
|
|
ASSERT_OK(cache->Insert(key2, item2_1, GetHelper(), str2.length()));
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 1);
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0);
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0);
|
|
|
|
std::string str3 = rnd.RandomString(1024);
|
|
auto item3_1 = new TestItem(str3.data(), str3.length());
|
|
// After this Insert, primary cache contains k3 and secondary cache contains
|
|
// k1's dummy item and k2's dummy item.
|
|
ASSERT_OK(cache->Insert(key3, item3_1, GetHelper(), str3.length()));
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 2);
|
|
|
|
// After this Insert, primary cache contains k1 and secondary cache contains
|
|
// k1's dummy item, k2's dummy item, and k3's dummy item.
|
|
auto item1_2 = new TestItem(str1.data(), str1.length());
|
|
ASSERT_OK(cache->Insert(key1, item1_2, GetHelper(), str1.length()));
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 3);
|
|
|
|
// After this Insert, primary cache contains k2 and secondary cache contains
|
|
// k1's item, k2's dummy item, and k3's dummy item.
|
|
auto item2_2 = new TestItem(str2.data(), str2.length());
|
|
ASSERT_OK(cache->Insert(key2, item2_2, GetHelper(), str2.length()));
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 1);
|
|
if (sec_cache_is_compressed) {
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes,
|
|
str1.length());
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes,
|
|
1008);
|
|
} else {
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0);
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0);
|
|
}
|
|
|
|
// After this Insert, primary cache contains k3 and secondary cache contains
|
|
// k1's item and k2's item.
|
|
auto item3_2 = new TestItem(str3.data(), str3.length());
|
|
ASSERT_OK(cache->Insert(key3, item3_2, GetHelper(), str3.length()));
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 2);
|
|
if (sec_cache_is_compressed) {
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes,
|
|
str1.length() + str2.length());
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes,
|
|
2027);
|
|
} else {
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0);
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0);
|
|
}
|
|
|
|
Cache::Handle* handle;
|
|
handle = cache->Lookup(key3, GetHelper(), this, Cache::Priority::LOW,
|
|
stats.get());
|
|
ASSERT_NE(handle, nullptr);
|
|
auto val3 = static_cast<TestItem*>(cache->Value(handle));
|
|
ASSERT_NE(val3, nullptr);
|
|
ASSERT_EQ(memcmp(val3->Buf(), item3_2->Buf(), item3_2->Size()), 0);
|
|
cache->Release(handle);
|
|
|
|
// Lookup an non-existent key.
|
|
handle = cache->Lookup(key0, GetHelper(), this, Cache::Priority::LOW,
|
|
stats.get());
|
|
ASSERT_EQ(handle, nullptr);
|
|
|
|
// This Lookup should just insert a dummy handle in the primary cache
|
|
// and the k1 is still in the secondary cache.
|
|
handle = cache->Lookup(key1, GetHelper(), this, Cache::Priority::LOW,
|
|
stats.get());
|
|
ASSERT_NE(handle, nullptr);
|
|
ASSERT_EQ(get_perf_context()->block_cache_standalone_handle_count, 1);
|
|
auto val1_1 = static_cast<TestItem*>(cache->Value(handle));
|
|
ASSERT_NE(val1_1, nullptr);
|
|
ASSERT_EQ(memcmp(val1_1->Buf(), str1.data(), str1.size()), 0);
|
|
cache->Release(handle);
|
|
|
|
// This Lookup should erase k1 from the secondary cache and insert
|
|
// it into primary cache; then k3 is demoted.
|
|
// k2 and k3 are in secondary cache.
|
|
handle = cache->Lookup(key1, GetHelper(), this, Cache::Priority::LOW,
|
|
stats.get());
|
|
ASSERT_NE(handle, nullptr);
|
|
ASSERT_EQ(get_perf_context()->block_cache_standalone_handle_count, 1);
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 3);
|
|
cache->Release(handle);
|
|
|
|
// k2 is still in secondary cache.
|
|
handle = cache->Lookup(key2, GetHelper(), this, Cache::Priority::LOW,
|
|
stats.get());
|
|
ASSERT_NE(handle, nullptr);
|
|
ASSERT_EQ(get_perf_context()->block_cache_standalone_handle_count, 2);
|
|
cache->Release(handle);
|
|
|
|
// Testing SetCapacity().
|
|
ASSERT_OK(secondary_cache->SetCapacity(0));
|
|
handle = cache->Lookup(key3, GetHelper(), this, Cache::Priority::LOW,
|
|
stats.get());
|
|
ASSERT_EQ(handle, nullptr);
|
|
|
|
ASSERT_OK(secondary_cache->SetCapacity(7000));
|
|
size_t capacity;
|
|
ASSERT_OK(secondary_cache->GetCapacity(capacity));
|
|
ASSERT_EQ(capacity, 7000);
|
|
auto item1_3 = new TestItem(str1.data(), str1.length());
|
|
// After this Insert, primary cache contains k1.
|
|
ASSERT_OK(cache->Insert(key1, item1_3, GetHelper(), str2.length()));
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 3);
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 4);
|
|
|
|
auto item2_3 = new TestItem(str2.data(), str2.length());
|
|
// After this Insert, primary cache contains k2 and secondary cache contains
|
|
// k1's dummy item.
|
|
ASSERT_OK(cache->Insert(key2, item2_3, GetHelper(), str1.length()));
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 4);
|
|
|
|
auto item1_4 = new TestItem(str1.data(), str1.length());
|
|
// After this Insert, primary cache contains k1 and secondary cache contains
|
|
// k1's dummy item and k2's dummy item.
|
|
ASSERT_OK(cache->Insert(key1, item1_4, GetHelper(), str2.length()));
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 5);
|
|
|
|
auto item2_4 = new TestItem(str2.data(), str2.length());
|
|
// After this Insert, primary cache contains k2 and secondary cache contains
|
|
// k1's real item and k2's dummy item.
|
|
ASSERT_OK(cache->Insert(key2, item2_4, GetHelper(), str2.length()));
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 5);
|
|
// This Lookup should just insert a dummy handle in the primary cache
|
|
// and the k1 is still in the secondary cache.
|
|
handle = cache->Lookup(key1, GetHelper(), this, Cache::Priority::LOW,
|
|
stats.get());
|
|
|
|
ASSERT_NE(handle, nullptr);
|
|
cache->Release(handle);
|
|
ASSERT_EQ(get_perf_context()->block_cache_standalone_handle_count, 3);
|
|
|
|
cache.reset();
|
|
secondary_cache.reset();
|
|
}
|
|
|
|
void BasicIntegrationFailTest(bool sec_cache_is_compressed) {
|
|
CompressedSecondaryCacheOptions secondary_cache_opts;
|
|
|
|
if (sec_cache_is_compressed) {
|
|
if (!LZ4_Supported()) {
|
|
ROCKSDB_GTEST_SKIP("This test requires LZ4 support.");
|
|
secondary_cache_opts.compression_type = CompressionType::kNoCompression;
|
|
}
|
|
} else {
|
|
secondary_cache_opts.compression_type = CompressionType::kNoCompression;
|
|
}
|
|
|
|
secondary_cache_opts.capacity = 6000;
|
|
secondary_cache_opts.num_shard_bits = 0;
|
|
std::shared_ptr<SecondaryCache> secondary_cache =
|
|
NewCompressedSecondaryCache(secondary_cache_opts);
|
|
|
|
std::shared_ptr<Cache> cache = NewCache(
|
|
/*_capacity=*/1300, /*_num_shard_bits=*/0,
|
|
/*_strict_capacity_limit=*/false, secondary_cache);
|
|
|
|
Random rnd(301);
|
|
std::string str1 = rnd.RandomString(1001);
|
|
auto item1 = std::make_unique<TestItem>(str1.data(), str1.length());
|
|
ASSERT_OK(cache->Insert(key1, item1.get(), GetHelper(), str1.length()));
|
|
item1.release(); // Appease clang-analyze "potential memory leak"
|
|
|
|
Cache::Handle* handle;
|
|
handle = cache->Lookup(key2, nullptr, this, Cache::Priority::LOW);
|
|
ASSERT_EQ(handle, nullptr);
|
|
handle = cache->Lookup(key2, GetHelper(), this, Cache::Priority::LOW);
|
|
ASSERT_EQ(handle, nullptr);
|
|
|
|
Cache::AsyncLookupHandle ah;
|
|
ah.key = key2;
|
|
ah.helper = GetHelper();
|
|
ah.create_context = this;
|
|
ah.priority = Cache::Priority::LOW;
|
|
cache->StartAsyncLookup(ah);
|
|
cache->Wait(ah);
|
|
ASSERT_EQ(ah.Result(), nullptr);
|
|
|
|
cache.reset();
|
|
secondary_cache.reset();
|
|
}
|
|
|
|
void IntegrationSaveFailTest(bool sec_cache_is_compressed) {
|
|
CompressedSecondaryCacheOptions secondary_cache_opts;
|
|
|
|
if (sec_cache_is_compressed) {
|
|
if (!LZ4_Supported()) {
|
|
ROCKSDB_GTEST_SKIP("This test requires LZ4 support.");
|
|
secondary_cache_opts.compression_type = CompressionType::kNoCompression;
|
|
}
|
|
} else {
|
|
secondary_cache_opts.compression_type = CompressionType::kNoCompression;
|
|
}
|
|
|
|
secondary_cache_opts.capacity = 6000;
|
|
secondary_cache_opts.num_shard_bits = 0;
|
|
|
|
std::shared_ptr<SecondaryCache> secondary_cache =
|
|
NewCompressedSecondaryCache(secondary_cache_opts);
|
|
|
|
std::shared_ptr<Cache> cache = NewCache(
|
|
/*_capacity=*/1300, /*_num_shard_bits=*/0,
|
|
/*_strict_capacity_limit=*/true, secondary_cache);
|
|
|
|
Random rnd(301);
|
|
std::string str1 = rnd.RandomString(1001);
|
|
auto item1 = new TestItem(str1.data(), str1.length());
|
|
ASSERT_OK(cache->Insert(key1, item1, GetHelperFail(), str1.length()));
|
|
|
|
std::string str2 = rnd.RandomString(1002);
|
|
auto item2 = new TestItem(str2.data(), str2.length());
|
|
// k1 should be demoted to the secondary cache.
|
|
ASSERT_OK(cache->Insert(key2, item2, GetHelperFail(), str2.length()));
|
|
|
|
Cache::Handle* handle;
|
|
handle = cache->Lookup(key2, GetHelperFail(), this, Cache::Priority::LOW);
|
|
ASSERT_NE(handle, nullptr);
|
|
cache->Release(handle);
|
|
// This lookup should fail, since k1 demotion would have failed.
|
|
handle = cache->Lookup(key1, GetHelperFail(), this, Cache::Priority::LOW);
|
|
ASSERT_EQ(handle, nullptr);
|
|
// Since k1 was not promoted, k2 should still be in cache.
|
|
handle = cache->Lookup(key2, GetHelperFail(), this, Cache::Priority::LOW);
|
|
ASSERT_NE(handle, nullptr);
|
|
cache->Release(handle);
|
|
|
|
cache.reset();
|
|
secondary_cache.reset();
|
|
}
|
|
|
|
void IntegrationCreateFailTest(bool sec_cache_is_compressed) {
|
|
CompressedSecondaryCacheOptions secondary_cache_opts;
|
|
|
|
if (sec_cache_is_compressed) {
|
|
if (!LZ4_Supported()) {
|
|
ROCKSDB_GTEST_SKIP("This test requires LZ4 support.");
|
|
secondary_cache_opts.compression_type = CompressionType::kNoCompression;
|
|
}
|
|
} else {
|
|
secondary_cache_opts.compression_type = CompressionType::kNoCompression;
|
|
}
|
|
|
|
secondary_cache_opts.capacity = 6000;
|
|
secondary_cache_opts.num_shard_bits = 0;
|
|
|
|
std::shared_ptr<SecondaryCache> secondary_cache =
|
|
NewCompressedSecondaryCache(secondary_cache_opts);
|
|
|
|
std::shared_ptr<Cache> cache = NewCache(
|
|
/*_capacity=*/1300, /*_num_shard_bits=*/0,
|
|
/*_strict_capacity_limit=*/true, secondary_cache);
|
|
|
|
Random rnd(301);
|
|
std::string str1 = rnd.RandomString(1001);
|
|
auto item1 = new TestItem(str1.data(), str1.length());
|
|
ASSERT_OK(cache->Insert(key1, item1, GetHelper(), str1.length()));
|
|
|
|
std::string str2 = rnd.RandomString(1002);
|
|
auto item2 = new TestItem(str2.data(), str2.length());
|
|
// k1 should be demoted to the secondary cache.
|
|
ASSERT_OK(cache->Insert(key2, item2, GetHelper(), str2.length()));
|
|
|
|
Cache::Handle* handle;
|
|
SetFailCreate(true);
|
|
handle = cache->Lookup(key2, GetHelper(), this, Cache::Priority::LOW);
|
|
ASSERT_NE(handle, nullptr);
|
|
cache->Release(handle);
|
|
// This lookup should fail, since k1 creation would have failed
|
|
handle = cache->Lookup(key1, GetHelper(), this, Cache::Priority::LOW);
|
|
ASSERT_EQ(handle, nullptr);
|
|
// Since k1 didn't get promoted, k2 should still be in cache
|
|
handle = cache->Lookup(key2, GetHelper(), this, Cache::Priority::LOW);
|
|
ASSERT_NE(handle, nullptr);
|
|
cache->Release(handle);
|
|
|
|
cache.reset();
|
|
secondary_cache.reset();
|
|
}
|
|
|
|
void IntegrationFullCapacityTest(bool sec_cache_is_compressed) {
|
|
CompressedSecondaryCacheOptions secondary_cache_opts;
|
|
|
|
if (sec_cache_is_compressed) {
|
|
if (!LZ4_Supported()) {
|
|
ROCKSDB_GTEST_SKIP("This test requires LZ4 support.");
|
|
secondary_cache_opts.compression_type = CompressionType::kNoCompression;
|
|
}
|
|
} else {
|
|
secondary_cache_opts.compression_type = CompressionType::kNoCompression;
|
|
}
|
|
|
|
secondary_cache_opts.capacity = 6000;
|
|
secondary_cache_opts.num_shard_bits = 0;
|
|
|
|
std::shared_ptr<SecondaryCache> secondary_cache =
|
|
NewCompressedSecondaryCache(secondary_cache_opts);
|
|
|
|
std::shared_ptr<Cache> cache = NewCache(
|
|
/*_capacity=*/1300, /*_num_shard_bits=*/0,
|
|
/*_strict_capacity_limit=*/false, secondary_cache);
|
|
|
|
Random rnd(301);
|
|
std::string str1 = rnd.RandomString(1001);
|
|
auto item1_1 = new TestItem(str1.data(), str1.length());
|
|
ASSERT_OK(cache->Insert(key1, item1_1, GetHelper(), str1.length()));
|
|
|
|
std::string str2 = rnd.RandomString(1002);
|
|
std::string str2_clone{str2};
|
|
auto item2 = new TestItem(str2.data(), str2.length());
|
|
// After this Insert, primary cache contains k2 and secondary cache contains
|
|
// k1's dummy item.
|
|
ASSERT_OK(cache->Insert(key2, item2, GetHelper(), str2.length()));
|
|
|
|
// After this Insert, primary cache contains k1 and secondary cache contains
|
|
// k1's dummy item and k2's dummy item.
|
|
auto item1_2 = new TestItem(str1.data(), str1.length());
|
|
ASSERT_OK(cache->Insert(key1, item1_2, GetHelper(), str1.length()));
|
|
|
|
auto item2_2 = new TestItem(str2.data(), str2.length());
|
|
// After this Insert, primary cache contains k2 and secondary cache contains
|
|
// k1's item and k2's dummy item.
|
|
ASSERT_OK(cache->Insert(key2, item2_2, GetHelper(), str2.length()));
|
|
|
|
Cache::Handle* handle2;
|
|
handle2 = cache->Lookup(key2, GetHelper(), this, Cache::Priority::LOW);
|
|
ASSERT_NE(handle2, nullptr);
|
|
cache->Release(handle2);
|
|
|
|
// k1 promotion should fail because cache is at capacity and
|
|
// strict_capacity_limit is true, but the lookup should still succeed.
|
|
// A k1's dummy item is inserted into primary cache.
|
|
Cache::Handle* handle1;
|
|
handle1 = cache->Lookup(key1, GetHelper(), this, Cache::Priority::LOW);
|
|
ASSERT_NE(handle1, nullptr);
|
|
cache->Release(handle1);
|
|
|
|
// Since k1 didn't get inserted, k2 should still be in cache
|
|
handle2 = cache->Lookup(key2, GetHelper(), this, Cache::Priority::LOW);
|
|
ASSERT_NE(handle2, nullptr);
|
|
cache->Release(handle2);
|
|
|
|
cache.reset();
|
|
secondary_cache.reset();
|
|
}
|
|
|
|
void SplitValueIntoChunksTest() {
|
|
JemallocAllocatorOptions jopts;
|
|
std::shared_ptr<MemoryAllocator> allocator;
|
|
std::string msg;
|
|
if (JemallocNodumpAllocator::IsSupported(&msg)) {
|
|
Status s = NewJemallocNodumpAllocator(jopts, &allocator);
|
|
if (!s.ok()) {
|
|
ROCKSDB_GTEST_BYPASS("JEMALLOC not supported");
|
|
}
|
|
} else {
|
|
ROCKSDB_GTEST_BYPASS("JEMALLOC not supported");
|
|
}
|
|
|
|
using CacheValueChunk = CompressedSecondaryCache::CacheValueChunk;
|
|
std::unique_ptr<CompressedSecondaryCache> sec_cache =
|
|
std::make_unique<CompressedSecondaryCache>(1000, 0, true, 0.5, 0.0,
|
|
allocator);
|
|
Random rnd(301);
|
|
// 8500 = 8169 + 233 + 98, so there should be 3 chunks after split.
|
|
size_t str_size{8500};
|
|
std::string str = rnd.RandomString(static_cast<int>(str_size));
|
|
size_t charge{0};
|
|
CacheValueChunk* chunks_head =
|
|
sec_cache->SplitValueIntoChunks(str, kLZ4Compression, charge);
|
|
ASSERT_EQ(charge, str_size + 3 * (sizeof(CacheValueChunk) - 1));
|
|
|
|
CacheValueChunk* current_chunk = chunks_head;
|
|
ASSERT_EQ(current_chunk->size, 8192 - sizeof(CacheValueChunk) + 1);
|
|
current_chunk = current_chunk->next;
|
|
ASSERT_EQ(current_chunk->size, 256 - sizeof(CacheValueChunk) + 1);
|
|
current_chunk = current_chunk->next;
|
|
ASSERT_EQ(current_chunk->size, 98);
|
|
|
|
sec_cache->GetHelper(true)->del_cb(chunks_head, /*alloc*/ nullptr);
|
|
}
|
|
|
|
void MergeChunksIntoValueTest() {
|
|
using CacheValueChunk = CompressedSecondaryCache::CacheValueChunk;
|
|
Random rnd(301);
|
|
size_t size1{2048};
|
|
std::string str1 = rnd.RandomString(static_cast<int>(size1));
|
|
CacheValueChunk* current_chunk = reinterpret_cast<CacheValueChunk*>(
|
|
new char[sizeof(CacheValueChunk) - 1 + size1]);
|
|
CacheValueChunk* chunks_head = current_chunk;
|
|
memcpy(current_chunk->data, str1.data(), size1);
|
|
current_chunk->size = size1;
|
|
|
|
size_t size2{256};
|
|
std::string str2 = rnd.RandomString(static_cast<int>(size2));
|
|
current_chunk->next = reinterpret_cast<CacheValueChunk*>(
|
|
new char[sizeof(CacheValueChunk) - 1 + size2]);
|
|
current_chunk = current_chunk->next;
|
|
memcpy(current_chunk->data, str2.data(), size2);
|
|
current_chunk->size = size2;
|
|
|
|
size_t size3{31};
|
|
std::string str3 = rnd.RandomString(static_cast<int>(size3));
|
|
current_chunk->next = reinterpret_cast<CacheValueChunk*>(
|
|
new char[sizeof(CacheValueChunk) - 1 + size3]);
|
|
current_chunk = current_chunk->next;
|
|
memcpy(current_chunk->data, str3.data(), size3);
|
|
current_chunk->size = size3;
|
|
current_chunk->next = nullptr;
|
|
|
|
std::string str = str1 + str2 + str3;
|
|
|
|
std::unique_ptr<CompressedSecondaryCache> sec_cache =
|
|
std::make_unique<CompressedSecondaryCache>(1000, 0, true, 0.5, 0.0);
|
|
size_t charge{0};
|
|
CacheAllocationPtr value =
|
|
sec_cache->MergeChunksIntoValue(chunks_head, charge);
|
|
ASSERT_EQ(charge, size1 + size2 + size3);
|
|
std::string value_str{value.get(), charge};
|
|
ASSERT_EQ(strcmp(value_str.data(), str.data()), 0);
|
|
|
|
while (chunks_head != nullptr) {
|
|
CacheValueChunk* tmp_chunk = chunks_head;
|
|
chunks_head = chunks_head->next;
|
|
tmp_chunk->Free();
|
|
}
|
|
}
|
|
|
|
void SplictValueAndMergeChunksTest() {
|
|
JemallocAllocatorOptions jopts;
|
|
std::shared_ptr<MemoryAllocator> allocator;
|
|
std::string msg;
|
|
if (JemallocNodumpAllocator::IsSupported(&msg)) {
|
|
Status s = NewJemallocNodumpAllocator(jopts, &allocator);
|
|
if (!s.ok()) {
|
|
ROCKSDB_GTEST_BYPASS("JEMALLOC not supported");
|
|
}
|
|
} else {
|
|
ROCKSDB_GTEST_BYPASS("JEMALLOC not supported");
|
|
}
|
|
|
|
using CacheValueChunk = CompressedSecondaryCache::CacheValueChunk;
|
|
std::unique_ptr<CompressedSecondaryCache> sec_cache =
|
|
std::make_unique<CompressedSecondaryCache>(1000, 0, true, 0.5, 0.0,
|
|
allocator);
|
|
Random rnd(301);
|
|
// 8500 = 8169 + 233 + 98, so there should be 3 chunks after split.
|
|
size_t str_size{8500};
|
|
std::string str = rnd.RandomString(static_cast<int>(str_size));
|
|
size_t charge{0};
|
|
CacheValueChunk* chunks_head =
|
|
sec_cache->SplitValueIntoChunks(str, kLZ4Compression, charge);
|
|
ASSERT_EQ(charge, str_size + 3 * (sizeof(CacheValueChunk) - 1));
|
|
|
|
CacheAllocationPtr value =
|
|
sec_cache->MergeChunksIntoValue(chunks_head, charge);
|
|
ASSERT_EQ(charge, str_size);
|
|
std::string value_str{value.get(), charge};
|
|
ASSERT_EQ(strcmp(value_str.data(), str.data()), 0);
|
|
|
|
sec_cache->GetHelper(true)->del_cb(chunks_head, /*alloc*/ nullptr);
|
|
}
|
|
};
|
|
|
|
class CompressedSecondaryCacheTest
|
|
: public CompressedSecondaryCacheTestBase,
|
|
public testing::WithParamInterface<std::string> {
|
|
const std::string& Type() override { return GetParam(); }
|
|
};
|
|
|
|
INSTANTIATE_TEST_CASE_P(CompressedSecondaryCacheTest,
|
|
CompressedSecondaryCacheTest, GetTestingCacheTypes());
|
|
|
|
class CompressedSecCacheTestWithCompressAndAllocatorParam
|
|
: public CompressedSecondaryCacheTestBase,
|
|
public ::testing::WithParamInterface<
|
|
std::tuple<bool, bool, std::string>> {
|
|
public:
|
|
CompressedSecCacheTestWithCompressAndAllocatorParam() {
|
|
sec_cache_is_compressed_ = std::get<0>(GetParam());
|
|
use_jemalloc_ = std::get<1>(GetParam());
|
|
}
|
|
const std::string& Type() override { return std::get<2>(GetParam()); }
|
|
bool sec_cache_is_compressed_;
|
|
bool use_jemalloc_;
|
|
};
|
|
|
|
TEST_P(CompressedSecCacheTestWithCompressAndAllocatorParam, BasicTes) {
|
|
BasicTest(sec_cache_is_compressed_, use_jemalloc_);
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(CompressedSecCacheTests,
|
|
CompressedSecCacheTestWithCompressAndAllocatorParam,
|
|
::testing::Combine(testing::Bool(), testing::Bool(),
|
|
GetTestingCacheTypes()));
|
|
|
|
class CompressedSecondaryCacheTestWithCompressionParam
|
|
: public CompressedSecondaryCacheTestBase,
|
|
public ::testing::WithParamInterface<std::tuple<bool, std::string>> {
|
|
public:
|
|
CompressedSecondaryCacheTestWithCompressionParam() {
|
|
sec_cache_is_compressed_ = std::get<0>(GetParam());
|
|
}
|
|
const std::string& Type() override { return std::get<1>(GetParam()); }
|
|
bool sec_cache_is_compressed_;
|
|
};
|
|
|
|
TEST_P(CompressedSecondaryCacheTestWithCompressionParam, BasicTestFromString) {
|
|
std::shared_ptr<SecondaryCache> sec_cache{nullptr};
|
|
std::string sec_cache_uri;
|
|
if (sec_cache_is_compressed_) {
|
|
if (LZ4_Supported()) {
|
|
sec_cache_uri =
|
|
"compressed_secondary_cache://"
|
|
"capacity=2048;num_shard_bits=0;compression_type=kLZ4Compression;"
|
|
"compress_format_version=2";
|
|
} else {
|
|
ROCKSDB_GTEST_SKIP("This test requires LZ4 support.");
|
|
sec_cache_uri =
|
|
"compressed_secondary_cache://"
|
|
"capacity=2048;num_shard_bits=0;compression_type=kNoCompression";
|
|
sec_cache_is_compressed_ = false;
|
|
}
|
|
Status s = SecondaryCache::CreateFromString(ConfigOptions(), sec_cache_uri,
|
|
&sec_cache);
|
|
EXPECT_OK(s);
|
|
} else {
|
|
sec_cache_uri =
|
|
"compressed_secondary_cache://"
|
|
"capacity=2048;num_shard_bits=0;compression_type=kNoCompression";
|
|
Status s = SecondaryCache::CreateFromString(ConfigOptions(), sec_cache_uri,
|
|
&sec_cache);
|
|
EXPECT_OK(s);
|
|
}
|
|
BasicTestHelper(sec_cache, sec_cache_is_compressed_);
|
|
}
|
|
|
|
TEST_P(CompressedSecondaryCacheTestWithCompressionParam,
|
|
BasicTestFromStringWithSplit) {
|
|
std::shared_ptr<SecondaryCache> sec_cache{nullptr};
|
|
std::string sec_cache_uri;
|
|
if (sec_cache_is_compressed_) {
|
|
if (LZ4_Supported()) {
|
|
sec_cache_uri =
|
|
"compressed_secondary_cache://"
|
|
"capacity=2048;num_shard_bits=0;compression_type=kLZ4Compression;"
|
|
"compress_format_version=2;enable_custom_split_merge=true";
|
|
} else {
|
|
ROCKSDB_GTEST_SKIP("This test requires LZ4 support.");
|
|
sec_cache_uri =
|
|
"compressed_secondary_cache://"
|
|
"capacity=2048;num_shard_bits=0;compression_type=kNoCompression;"
|
|
"enable_custom_split_merge=true";
|
|
sec_cache_is_compressed_ = false;
|
|
}
|
|
Status s = SecondaryCache::CreateFromString(ConfigOptions(), sec_cache_uri,
|
|
&sec_cache);
|
|
EXPECT_OK(s);
|
|
} else {
|
|
sec_cache_uri =
|
|
"compressed_secondary_cache://"
|
|
"capacity=2048;num_shard_bits=0;compression_type=kNoCompression;"
|
|
"enable_custom_split_merge=true";
|
|
Status s = SecondaryCache::CreateFromString(ConfigOptions(), sec_cache_uri,
|
|
&sec_cache);
|
|
EXPECT_OK(s);
|
|
}
|
|
BasicTestHelper(sec_cache, sec_cache_is_compressed_);
|
|
}
|
|
|
|
|
|
TEST_P(CompressedSecondaryCacheTestWithCompressionParam, FailsTest) {
|
|
FailsTest(sec_cache_is_compressed_);
|
|
}
|
|
|
|
TEST_P(CompressedSecondaryCacheTestWithCompressionParam,
|
|
BasicIntegrationFailTest) {
|
|
BasicIntegrationFailTest(sec_cache_is_compressed_);
|
|
}
|
|
|
|
TEST_P(CompressedSecondaryCacheTestWithCompressionParam,
|
|
IntegrationSaveFailTest) {
|
|
IntegrationSaveFailTest(sec_cache_is_compressed_);
|
|
}
|
|
|
|
TEST_P(CompressedSecondaryCacheTestWithCompressionParam,
|
|
IntegrationCreateFailTest) {
|
|
IntegrationCreateFailTest(sec_cache_is_compressed_);
|
|
}
|
|
|
|
TEST_P(CompressedSecondaryCacheTestWithCompressionParam,
|
|
IntegrationFullCapacityTest) {
|
|
IntegrationFullCapacityTest(sec_cache_is_compressed_);
|
|
}
|
|
|
|
TEST_P(CompressedSecondaryCacheTestWithCompressionParam, EntryRoles) {
|
|
CompressedSecondaryCacheOptions opts;
|
|
opts.capacity = 2048;
|
|
opts.num_shard_bits = 0;
|
|
|
|
if (sec_cache_is_compressed_) {
|
|
if (!LZ4_Supported()) {
|
|
ROCKSDB_GTEST_SKIP("This test requires LZ4 support.");
|
|
return;
|
|
}
|
|
} else {
|
|
opts.compression_type = CompressionType::kNoCompression;
|
|
}
|
|
|
|
// Select a random subset to include, for fast test
|
|
Random& r = *Random::GetTLSInstance();
|
|
CacheEntryRoleSet do_not_compress;
|
|
for (uint32_t i = 0; i < kNumCacheEntryRoles; ++i) {
|
|
// A few included on average, but decent chance of zero
|
|
if (r.OneIn(5)) {
|
|
do_not_compress.Add(static_cast<CacheEntryRole>(i));
|
|
}
|
|
}
|
|
opts.do_not_compress_roles = do_not_compress;
|
|
|
|
std::shared_ptr<SecondaryCache> sec_cache = NewCompressedSecondaryCache(opts);
|
|
|
|
// Fixed seed to ensure consistent compressibility (doesn't compress)
|
|
std::string junk(Random(301).RandomString(1000));
|
|
|
|
for (uint32_t i = 0; i < kNumCacheEntryRoles; ++i) {
|
|
CacheEntryRole role = static_cast<CacheEntryRole>(i);
|
|
|
|
// Uniquify `junk`
|
|
junk[0] = static_cast<char>(i);
|
|
TestItem item{junk.data(), junk.length()};
|
|
Slice ith_key = Slice(junk.data(), 16);
|
|
|
|
get_perf_context()->Reset();
|
|
ASSERT_OK(sec_cache->Insert(ith_key, &item, GetHelper(role)));
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_dummy_count, 1U);
|
|
|
|
ASSERT_OK(sec_cache->Insert(ith_key, &item, GetHelper(role)));
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_insert_real_count, 1U);
|
|
|
|
bool kept_in_sec_cache{true};
|
|
std::unique_ptr<SecondaryCacheResultHandle> handle =
|
|
sec_cache->Lookup(ith_key, GetHelper(role), this, true,
|
|
/*advise_erase=*/true, kept_in_sec_cache);
|
|
ASSERT_NE(handle, nullptr);
|
|
|
|
// Lookup returns the right data
|
|
std::unique_ptr<TestItem> val =
|
|
std::unique_ptr<TestItem>(static_cast<TestItem*>(handle->Value()));
|
|
ASSERT_NE(val, nullptr);
|
|
ASSERT_EQ(memcmp(val->Buf(), item.Buf(), item.Size()), 0);
|
|
|
|
bool compressed =
|
|
sec_cache_is_compressed_ && !do_not_compress.Contains(role);
|
|
if (compressed) {
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes,
|
|
1000);
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes,
|
|
1007);
|
|
} else {
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_uncompressed_bytes, 0);
|
|
ASSERT_EQ(get_perf_context()->compressed_sec_cache_compressed_bytes, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(CompressedSecCacheTests,
|
|
CompressedSecondaryCacheTestWithCompressionParam,
|
|
testing::Combine(testing::Bool(),
|
|
GetTestingCacheTypes()));
|
|
|
|
class CompressedSecCacheTestWithCompressAndSplitParam
|
|
: public CompressedSecondaryCacheTestBase,
|
|
public ::testing::WithParamInterface<
|
|
std::tuple<bool, bool, std::string>> {
|
|
public:
|
|
CompressedSecCacheTestWithCompressAndSplitParam() {
|
|
sec_cache_is_compressed_ = std::get<0>(GetParam());
|
|
enable_custom_split_merge_ = std::get<1>(GetParam());
|
|
}
|
|
const std::string& Type() override { return std::get<2>(GetParam()); }
|
|
bool sec_cache_is_compressed_;
|
|
bool enable_custom_split_merge_;
|
|
};
|
|
|
|
TEST_P(CompressedSecCacheTestWithCompressAndSplitParam, BasicIntegrationTest) {
|
|
BasicIntegrationTest(sec_cache_is_compressed_, enable_custom_split_merge_);
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(CompressedSecCacheTests,
|
|
CompressedSecCacheTestWithCompressAndSplitParam,
|
|
::testing::Combine(testing::Bool(), testing::Bool(),
|
|
GetTestingCacheTypes()));
|
|
|
|
TEST_P(CompressedSecondaryCacheTest, SplitValueIntoChunksTest) {
|
|
SplitValueIntoChunksTest();
|
|
}
|
|
|
|
TEST_P(CompressedSecondaryCacheTest, MergeChunksIntoValueTest) {
|
|
MergeChunksIntoValueTest();
|
|
}
|
|
|
|
TEST_P(CompressedSecondaryCacheTest, SplictValueAndMergeChunksTest) {
|
|
SplictValueAndMergeChunksTest();
|
|
}
|
|
|
|
} // namespace ROCKSDB_NAMESPACE
|
|
|
|
int main(int argc, char** argv) {
|
|
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
|
|
::testing::InitGoogleTest(&argc, argv);
|
|
return RUN_ALL_TESTS();
|
|
}
|