1 // Copyright (c) 2013, Facebook, Inc. All rights reserved.
2 // This source code is licensed under the BSD-style license found in the
3 // LICENSE file in the root directory of this source tree. An additional grant
4 // of patent rights can be found in the PATENTS file in the same directory.
6 // Copyright (c) 2011 The LevelDB Authors. All rights reserved.
7 // Use of this source code is governed by a BSD-style license that can be
8 // found in the LICENSE file. See the AUTHORS file for names of contributors.
11 #include "utilities/persistent_cache/persistent_cache_test.h"
17 #include "utilities/persistent_cache/block_cache_tier.h"
21 static const double kStressFactor
= .125;
24 static void OnOpenForRead(void* arg
) {
25 int* val
= static_cast<int*>(arg
);
27 rocksdb::SyncPoint::GetInstance()->SetCallBack(
28 "NewRandomAccessFile:O_DIRECT",
29 std::bind(OnOpenForRead
, std::placeholders::_1
));
32 static void OnOpenForWrite(void* arg
) {
33 int* val
= static_cast<int*>(arg
);
35 rocksdb::SyncPoint::GetInstance()->SetCallBack(
36 "NewWritableFile:O_DIRECT",
37 std::bind(OnOpenForWrite
, std::placeholders::_1
));
41 static void RemoveDirectory(const std::string
& folder
) {
42 std::vector
<std::string
> files
;
43 Status status
= Env::Default()->GetChildren(folder
, &files
);
45 // we assume the directory does not exist
49 // cleanup files with the patter :digi:.rc
50 for (auto file
: files
) {
51 if (file
== "." || file
== "..") {
54 status
= Env::Default()->DeleteFile(folder
+ "/" + file
);
58 status
= Env::Default()->DeleteDir(folder
);
62 static void OnDeleteDir(void* arg
) {
63 char* dir
= static_cast<char*>(arg
);
64 RemoveDirectory(std::string(dir
));
68 // Simple logger that prints message on stdout
70 class ConsoleLogger
: public Logger
{
73 ConsoleLogger() : Logger(InfoLogLevel::ERROR_LEVEL
) {}
75 void Logv(const char* format
, va_list ap
) override
{
84 // construct a tiered RAM+Block cache
85 std::unique_ptr
<PersistentTieredCache
> NewTieredCache(
86 const size_t mem_size
, const PersistentCacheConfig
& opt
) {
87 std::unique_ptr
<PersistentTieredCache
> tcache(new PersistentTieredCache());
88 // create primary tier
90 auto pcache
= std::shared_ptr
<PersistentCacheTier
>(new VolatileCacheTier(
91 /*is_compressed*/ true, mem_size
));
92 tcache
->AddTier(pcache
);
93 // create secondary tier
94 auto scache
= std::shared_ptr
<PersistentCacheTier
>(new BlockCacheTier(opt
));
95 tcache
->AddTier(scache
);
97 Status s
= tcache
->Open();
102 // create block cache
103 std::unique_ptr
<PersistentCacheTier
> NewBlockCache(
104 Env
* env
, const std::string
& path
,
105 const uint64_t max_size
= std::numeric_limits
<uint64_t>::max(),
106 const bool enable_direct_writes
= false) {
107 const uint32_t max_file_size
= static_cast<uint32_t>(12 * 1024 * 1024 * kStressFactor
);
108 auto log
= std::make_shared
<ConsoleLogger
>();
109 PersistentCacheConfig
opt(env
, path
, max_size
, log
);
110 opt
.cache_file_size
= max_file_size
;
111 opt
.max_write_pipeline_backlog_size
= std::numeric_limits
<uint64_t>::max();
112 opt
.enable_direct_writes
= enable_direct_writes
;
113 std::unique_ptr
<PersistentCacheTier
> scache(new BlockCacheTier(opt
));
114 Status s
= scache
->Open();
119 // create a new cache tier
120 std::unique_ptr
<PersistentTieredCache
> NewTieredCache(
121 Env
* env
, const std::string
& path
, const uint64_t max_volatile_cache_size
,
122 const uint64_t max_block_cache_size
=
123 std::numeric_limits
<uint64_t>::max()) {
124 const uint32_t max_file_size
= static_cast<uint32_t>(12 * 1024 * 1024 * kStressFactor
);
125 auto log
= std::make_shared
<ConsoleLogger
>();
126 auto opt
= PersistentCacheConfig(env
, path
, max_block_cache_size
, log
);
127 opt
.cache_file_size
= max_file_size
;
128 opt
.max_write_pipeline_backlog_size
= std::numeric_limits
<uint64_t>::max();
129 // create tier out of the two caches
130 auto cache
= NewTieredCache(max_volatile_cache_size
, opt
);
134 PersistentCacheTierTest::PersistentCacheTierTest()
135 : path_(test::TmpDir(Env::Default()) + "/cache_test") {
137 rocksdb::SyncPoint::GetInstance()->EnableProcessing();
138 rocksdb::SyncPoint::GetInstance()->SetCallBack("NewRandomAccessFile:O_DIRECT",
140 rocksdb::SyncPoint::GetInstance()->SetCallBack("NewWritableFile:O_DIRECT",
146 TEST_F(PersistentCacheTierTest
, DISABLED_BlockCacheInsertWithFileCreateError
) {
147 cache_
= NewBlockCache(Env::Default(), path_
,
148 /*size=*/std::numeric_limits
<uint64_t>::max(),
149 /*direct_writes=*/ false);
150 rocksdb::SyncPoint::GetInstance()->SetCallBack(
151 "BlockCacheTier::NewCacheFile:DeleteDir", OnDeleteDir
);
153 RunNegativeInsertTest(/*nthreads=*/ 1,
155 static_cast<size_t>(10 * 1024 * kStressFactor
));
157 rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
161 // Travis is unable to handle the normal version of the tests running out of
162 // fds, out of space and timeouts. This is an easier version of the test
163 // specifically written for Travis
164 TEST_F(PersistentCacheTierTest
, BasicTest
) {
165 cache_
= std::make_shared
<VolatileCacheTier
>();
166 RunInsertTest(/*nthreads=*/1, /*max_keys=*/1024);
168 cache_
= NewBlockCache(Env::Default(), path_
,
169 /*size=*/std::numeric_limits
<uint64_t>::max(),
170 /*direct_writes=*/true);
171 RunInsertTest(/*nthreads=*/1, /*max_keys=*/1024);
173 cache_
= NewTieredCache(Env::Default(), path_
,
174 /*memory_size=*/static_cast<size_t>(1 * 1024 * 1024));
175 RunInsertTest(/*nthreads=*/1, /*max_keys=*/1024);
178 // Volatile cache tests
179 TEST_F(PersistentCacheTierTest
, VolatileCacheInsert
) {
180 for (auto nthreads
: {1, 5}) {
182 {10 * 1024 * kStressFactor
, 1 * 1024 * 1024 * kStressFactor
}) {
183 cache_
= std::make_shared
<VolatileCacheTier
>();
184 RunInsertTest(nthreads
, static_cast<size_t>(max_keys
));
189 TEST_F(PersistentCacheTierTest
, VolatileCacheInsertWithEviction
) {
190 for (auto nthreads
: {1, 5}) {
191 for (auto max_keys
: {1 * 1024 * 1024 * kStressFactor
}) {
192 cache_
= std::make_shared
<VolatileCacheTier
>(
193 /*compressed=*/true, /*size=*/static_cast<size_t>(1 * 1024 * 1024 * kStressFactor
));
194 RunInsertTestWithEviction(nthreads
, static_cast<size_t>(max_keys
));
200 TEST_F(PersistentCacheTierTest
, BlockCacheInsert
) {
201 for (auto direct_writes
: {true, false}) {
202 for (auto nthreads
: {1, 5}) {
204 {10 * 1024 * kStressFactor
, 1 * 1024 * 1024 * kStressFactor
}) {
205 cache_
= NewBlockCache(Env::Default(), path_
,
206 /*size=*/std::numeric_limits
<uint64_t>::max(),
208 RunInsertTest(nthreads
, static_cast<size_t>(max_keys
));
214 TEST_F(PersistentCacheTierTest
, BlockCacheInsertWithEviction
) {
215 for (auto nthreads
: {1, 5}) {
216 for (auto max_keys
: {1 * 1024 * 1024 * kStressFactor
}) {
217 cache_
= NewBlockCache(Env::Default(), path_
,
218 /*max_size=*/static_cast<size_t>(200 * 1024 * 1024 * kStressFactor
));
219 RunInsertTestWithEviction(nthreads
, static_cast<size_t>(max_keys
));
224 // Tiered cache tests
225 TEST_F(PersistentCacheTierTest
, TieredCacheInsert
) {
226 for (auto nthreads
: {1, 5}) {
228 {10 * 1024 * kStressFactor
, 1 * 1024 * 1024 * kStressFactor
}) {
229 cache_
= NewTieredCache(Env::Default(), path_
,
230 /*memory_size=*/static_cast<size_t>(1 * 1024 * 1024 * kStressFactor
));
231 RunInsertTest(nthreads
, static_cast<size_t>(max_keys
));
236 // the tests causes a lot of file deletions which Travis limited testing
237 // environment cannot handle
238 TEST_F(PersistentCacheTierTest
, TieredCacheInsertWithEviction
) {
239 for (auto nthreads
: {1, 5}) {
240 for (auto max_keys
: {1 * 1024 * 1024 * kStressFactor
}) {
241 cache_
= NewTieredCache(
242 Env::Default(), path_
,
243 /*memory_size=*/static_cast<size_t>(1 * 1024 * 1024 * kStressFactor
),
244 /*block_cache_size*/ static_cast<size_t>(200 * 1024 * 1024 * kStressFactor
));
245 RunInsertTestWithEviction(nthreads
, static_cast<size_t>(max_keys
));
251 std::shared_ptr
<PersistentCacheTier
> MakeVolatileCache(
252 const std::string
& /*dbname*/) {
253 return std::make_shared
<VolatileCacheTier
>();
256 std::shared_ptr
<PersistentCacheTier
> MakeBlockCache(const std::string
& dbname
) {
257 return NewBlockCache(Env::Default(), dbname
);
260 std::shared_ptr
<PersistentCacheTier
> MakeTieredCache(
261 const std::string
& dbname
) {
262 const auto memory_size
= 1 * 1024 * 1024 * kStressFactor
;
263 return NewTieredCache(Env::Default(), dbname
, static_cast<size_t>(memory_size
));
267 static void UniqueIdCallback(void* arg
) {
268 int* result
= reinterpret_cast<int*>(arg
);
273 rocksdb::SyncPoint::GetInstance()->ClearTrace();
274 rocksdb::SyncPoint::GetInstance()->SetCallBack(
275 "GetUniqueIdFromFile:FS_IOC_GETVERSION", UniqueIdCallback
);
279 TEST_F(PersistentCacheTierTest
, FactoryTest
) {
280 for (auto nvm_opt
: {true, false}) {
281 ASSERT_FALSE(cache_
);
282 auto log
= std::make_shared
<ConsoleLogger
>();
283 std::shared_ptr
<PersistentCache
> cache
;
284 ASSERT_OK(NewPersistentCache(Env::Default(), path_
,
285 /*size=*/1 * 1024 * 1024 * 1024, log
, nvm_opt
,
288 ASSERT_EQ(cache
->Stats().size(), 1);
289 ASSERT_TRUE(cache
->Stats()[0].size());
294 PersistentCacheDBTest::PersistentCacheDBTest() : DBTestBase("/cache_test") {
296 rocksdb::SyncPoint::GetInstance()->EnableProcessing();
297 rocksdb::SyncPoint::GetInstance()->SetCallBack(
298 "GetUniqueIdFromFile:FS_IOC_GETVERSION", UniqueIdCallback
);
299 rocksdb::SyncPoint::GetInstance()->SetCallBack("NewRandomAccessFile:O_DIRECT",
305 void PersistentCacheDBTest::RunTest(
306 const std::function
<std::shared_ptr
<PersistentCacheTier
>(bool)>& new_pcache
,
307 const size_t max_keys
= 100 * 1024, const size_t max_usecase
= 5) {
308 if (!Snappy_Supported()) {
312 // number of insertion interations
313 int num_iter
= static_cast<int>(max_keys
* kStressFactor
);
315 for (size_t iter
= 0; iter
< max_usecase
; iter
++) {
317 options
.write_buffer_size
=
318 static_cast<size_t>(64 * 1024 * kStressFactor
); // small write buffer
319 options
.statistics
= rocksdb::CreateDBStatistics();
320 options
= CurrentOptions(options
);
323 std::shared_ptr
<PersistentCacheTier
> pcache
;
324 BlockBasedTableOptions table_options
;
325 table_options
.cache_index_and_filter_blocks
= true;
327 const size_t size_max
= std::numeric_limits
<size_t>::max();
331 // page cache, block cache, no-compressed cache
332 pcache
= new_pcache(/*is_compressed=*/true);
333 table_options
.persistent_cache
= pcache
;
334 table_options
.block_cache
= NewLRUCache(size_max
);
335 table_options
.block_cache_compressed
= nullptr;
336 options
.table_factory
.reset(NewBlockBasedTableFactory(table_options
));
339 // page cache, block cache, compressed cache
340 pcache
= new_pcache(/*is_compressed=*/true);
341 table_options
.persistent_cache
= pcache
;
342 table_options
.block_cache
= NewLRUCache(size_max
);
343 table_options
.block_cache_compressed
= NewLRUCache(size_max
);
344 options
.table_factory
.reset(NewBlockBasedTableFactory(table_options
));
347 // page cache, block cache, compressed cache + KNoCompression
348 // both block cache and compressed cache, but DB is not compressed
349 // also, make block cache sizes bigger, to trigger block cache hits
350 pcache
= new_pcache(/*is_compressed=*/true);
351 table_options
.persistent_cache
= pcache
;
352 table_options
.block_cache
= NewLRUCache(size_max
);
353 table_options
.block_cache_compressed
= NewLRUCache(size_max
);
354 options
.table_factory
.reset(NewBlockBasedTableFactory(table_options
));
355 options
.compression
= kNoCompression
;
358 // page cache, no block cache, no compressed cache
359 pcache
= new_pcache(/*is_compressed=*/false);
360 table_options
.persistent_cache
= pcache
;
361 table_options
.block_cache
= nullptr;
362 table_options
.block_cache_compressed
= nullptr;
363 options
.table_factory
.reset(NewBlockBasedTableFactory(table_options
));
366 // page cache, no block cache, no compressed cache
367 // Page cache caches compressed blocks
368 pcache
= new_pcache(/*is_compressed=*/true);
369 table_options
.persistent_cache
= pcache
;
370 table_options
.block_cache
= nullptr;
371 table_options
.block_cache_compressed
= nullptr;
372 options
.table_factory
.reset(NewBlockBasedTableFactory(table_options
));
378 std::vector
<std::string
> values
;
380 Insert(options
, table_options
, num_iter
, &values
);
381 // flush all data in cache to device
382 pcache
->TEST_Flush();
384 Verify(num_iter
, values
);
386 auto block_miss
= TestGetTickerCount(options
, BLOCK_CACHE_MISS
);
387 auto compressed_block_hit
=
388 TestGetTickerCount(options
, BLOCK_CACHE_COMPRESSED_HIT
);
389 auto compressed_block_miss
=
390 TestGetTickerCount(options
, BLOCK_CACHE_COMPRESSED_MISS
);
391 auto page_hit
= TestGetTickerCount(options
, PERSISTENT_CACHE_HIT
);
392 auto page_miss
= TestGetTickerCount(options
, PERSISTENT_CACHE_MISS
);
394 // check that we triggered the appropriate code paths in the cache
397 // page cache, block cache, no-compressed cache
398 ASSERT_GT(page_miss
, 0);
399 ASSERT_GT(page_hit
, 0);
400 ASSERT_GT(block_miss
, 0);
401 ASSERT_EQ(compressed_block_miss
, 0);
402 ASSERT_EQ(compressed_block_hit
, 0);
405 // page cache, block cache, compressed cache
406 ASSERT_GT(page_miss
, 0);
407 ASSERT_GT(block_miss
, 0);
408 ASSERT_GT(compressed_block_miss
, 0);
411 // page cache, block cache, compressed cache + KNoCompression
412 ASSERT_GT(page_miss
, 0);
413 ASSERT_GT(page_hit
, 0);
414 ASSERT_GT(block_miss
, 0);
415 ASSERT_GT(compressed_block_miss
, 0);
416 // remember kNoCompression
417 ASSERT_EQ(compressed_block_hit
, 0);
421 // page cache, no block cache, no compressed cache
422 ASSERT_GT(page_miss
, 0);
423 ASSERT_GT(page_hit
, 0);
424 ASSERT_EQ(compressed_block_hit
, 0);
425 ASSERT_EQ(compressed_block_miss
, 0);
431 options
.create_if_missing
= true;
432 DestroyAndReopen(options
);
439 // Travis is unable to handle the normal version of the tests running out of
440 // fds, out of space and timeouts. This is an easier version of the test
441 // specifically written for Travis
442 TEST_F(PersistentCacheDBTest
, BasicTest
) {
443 RunTest(std::bind(&MakeBlockCache
, dbname_
), /*max_keys=*/1024,
447 // test table with block page cache
448 TEST_F(PersistentCacheDBTest
, BlockCacheTest
) {
449 RunTest(std::bind(&MakeBlockCache
, dbname_
));
452 // test table with volatile page cache
453 TEST_F(PersistentCacheDBTest
, VolatileCacheTest
) {
454 RunTest(std::bind(&MakeVolatileCache
, dbname_
));
457 // test table with tiered page cache
458 TEST_F(PersistentCacheDBTest
, TieredCacheTest
) {
459 RunTest(std::bind(&MakeTieredCache
, dbname_
));
463 } // namespace rocksdb
465 int main(int argc
, char** argv
) {
466 ::testing::InitGoogleTest(&argc
, argv
);
467 return RUN_ALL_TESTS();
470 int main() { return 0; }