1 // Copyright (c) 2013, Facebook, Inc. All rights reserved.
2 // This source code is licensed under both the GPLv2 (found in the
3 // COPYING file in the root directory) and Apache 2.0 License
4 // (found in the LICENSE.Apache file in the root directory).
5 //
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.
9
10 // GetUniqueIdFromFile is not implemented on Windows. Persistent cache
11 // breaks when that function is not implemented
12 #if !defined(ROCKSDB_LITE) && !defined(OS_WIN)
13
14 #include "utilities/persistent_cache/persistent_cache_test.h"
15
16 #include <functional>
17 #include <memory>
18 #include <thread>
19
20 #include "utilities/persistent_cache/block_cache_tier.h"
21
22 namespace ROCKSDB_NAMESPACE {
23
24 static const double kStressFactor = .125;
25
26 #ifdef OS_LINUX
OnOpenForRead(void * arg)27 static void OnOpenForRead(void* arg) {
28 int* val = static_cast<int*>(arg);
29 *val &= ~O_DIRECT;
30 ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
31 "NewRandomAccessFile:O_DIRECT",
32 std::bind(OnOpenForRead, std::placeholders::_1));
33 }
34
OnOpenForWrite(void * arg)35 static void OnOpenForWrite(void* arg) {
36 int* val = static_cast<int*>(arg);
37 *val &= ~O_DIRECT;
38 ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
39 "NewWritableFile:O_DIRECT",
40 std::bind(OnOpenForWrite, std::placeholders::_1));
41 }
42 #endif
43
RemoveDirectory(const std::string & folder)44 static void RemoveDirectory(const std::string& folder) {
45 std::vector<std::string> files;
46 Status status = Env::Default()->GetChildren(folder, &files);
47 if (!status.ok()) {
48 // we assume the directory does not exist
49 return;
50 }
51
52 // cleanup files with the patter :digi:.rc
53 for (auto file : files) {
54 if (file == "." || file == "..") {
55 continue;
56 }
57 status = Env::Default()->DeleteFile(folder + "/" + file);
58 assert(status.ok());
59 }
60
61 status = Env::Default()->DeleteDir(folder);
62 assert(status.ok());
63 }
64
OnDeleteDir(void * arg)65 static void OnDeleteDir(void* arg) {
66 char* dir = static_cast<char*>(arg);
67 RemoveDirectory(std::string(dir));
68 }
69
70 //
71 // Simple logger that prints message on stdout
72 //
73 class ConsoleLogger : public Logger {
74 public:
75 using Logger::Logv;
ConsoleLogger()76 ConsoleLogger() : Logger(InfoLogLevel::ERROR_LEVEL) {}
77
Logv(const char * format,va_list ap)78 void Logv(const char* format, va_list ap) override {
79 MutexLock _(&lock_);
80 vprintf(format, ap);
81 printf("\n");
82 }
83
84 port::Mutex lock_;
85 };
86
87 // construct a tiered RAM+Block cache
NewTieredCache(const size_t mem_size,const PersistentCacheConfig & opt)88 std::unique_ptr<PersistentTieredCache> NewTieredCache(
89 const size_t mem_size, const PersistentCacheConfig& opt) {
90 std::unique_ptr<PersistentTieredCache> tcache(new PersistentTieredCache());
91 // create primary tier
92 assert(mem_size);
93 auto pcache = std::shared_ptr<PersistentCacheTier>(new VolatileCacheTier(
94 /*is_compressed*/ true, mem_size));
95 tcache->AddTier(pcache);
96 // create secondary tier
97 auto scache = std::shared_ptr<PersistentCacheTier>(new BlockCacheTier(opt));
98 tcache->AddTier(scache);
99
100 Status s = tcache->Open();
101 assert(s.ok());
102 return tcache;
103 }
104
105 // create block cache
NewBlockCache(Env * env,const std::string & path,const uint64_t max_size=std::numeric_limits<uint64_t>::max (),const bool enable_direct_writes=false)106 std::unique_ptr<PersistentCacheTier> NewBlockCache(
107 Env* env, const std::string& path,
108 const uint64_t max_size = std::numeric_limits<uint64_t>::max(),
109 const bool enable_direct_writes = false) {
110 const uint32_t max_file_size = static_cast<uint32_t>(12 * 1024 * 1024 * kStressFactor);
111 auto log = std::make_shared<ConsoleLogger>();
112 PersistentCacheConfig opt(env, path, max_size, log);
113 opt.cache_file_size = max_file_size;
114 opt.max_write_pipeline_backlog_size = std::numeric_limits<uint64_t>::max();
115 opt.enable_direct_writes = enable_direct_writes;
116 std::unique_ptr<PersistentCacheTier> scache(new BlockCacheTier(opt));
117 Status s = scache->Open();
118 assert(s.ok());
119 return scache;
120 }
121
122 // create a new cache tier
NewTieredCache(Env * env,const std::string & path,const uint64_t max_volatile_cache_size,const uint64_t max_block_cache_size=std::numeric_limits<uint64_t>::max ())123 std::unique_ptr<PersistentTieredCache> NewTieredCache(
124 Env* env, const std::string& path, const uint64_t max_volatile_cache_size,
125 const uint64_t max_block_cache_size =
126 std::numeric_limits<uint64_t>::max()) {
127 const uint32_t max_file_size = static_cast<uint32_t>(12 * 1024 * 1024 * kStressFactor);
128 auto log = std::make_shared<ConsoleLogger>();
129 auto opt = PersistentCacheConfig(env, path, max_block_cache_size, log);
130 opt.cache_file_size = max_file_size;
131 opt.max_write_pipeline_backlog_size = std::numeric_limits<uint64_t>::max();
132 // create tier out of the two caches
133 auto cache = NewTieredCache(max_volatile_cache_size, opt);
134 return cache;
135 }
136
PersistentCacheTierTest()137 PersistentCacheTierTest::PersistentCacheTierTest()
138 : path_(test::PerThreadDBPath("cache_test")) {
139 #ifdef OS_LINUX
140 ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
141 ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
142 "NewRandomAccessFile:O_DIRECT", OnOpenForRead);
143 ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
144 "NewWritableFile:O_DIRECT", OnOpenForWrite);
145 #endif
146 }
147
148 // Block cache tests
TEST_F(PersistentCacheTierTest,DISABLED_BlockCacheInsertWithFileCreateError)149 TEST_F(PersistentCacheTierTest, DISABLED_BlockCacheInsertWithFileCreateError) {
150 cache_ = NewBlockCache(Env::Default(), path_,
151 /*size=*/std::numeric_limits<uint64_t>::max(),
152 /*direct_writes=*/ false);
153 ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
154 "BlockCacheTier::NewCacheFile:DeleteDir", OnDeleteDir);
155
156 RunNegativeInsertTest(/*nthreads=*/ 1,
157 /*max_keys*/
158 static_cast<size_t>(10 * 1024 * kStressFactor));
159
160 ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
161 }
162
163 // Travis is unable to handle the normal version of the tests running out of
164 // fds, out of space and timeouts. This is an easier version of the test
165 // specifically written for Travis
TEST_F(PersistentCacheTierTest,BasicTest)166 TEST_F(PersistentCacheTierTest, BasicTest) {
167 cache_ = std::make_shared<VolatileCacheTier>();
168 RunInsertTest(/*nthreads=*/1, /*max_keys=*/1024);
169
170 cache_ = NewBlockCache(Env::Default(), path_,
171 /*size=*/std::numeric_limits<uint64_t>::max(),
172 /*direct_writes=*/true);
173 RunInsertTest(/*nthreads=*/1, /*max_keys=*/1024);
174
175 cache_ = NewTieredCache(Env::Default(), path_,
176 /*memory_size=*/static_cast<size_t>(1 * 1024 * 1024));
177 RunInsertTest(/*nthreads=*/1, /*max_keys=*/1024);
178 }
179
180 // Volatile cache tests
181 // DISABLED for now (somewhat expensive)
TEST_F(PersistentCacheTierTest,DISABLED_VolatileCacheInsert)182 TEST_F(PersistentCacheTierTest, DISABLED_VolatileCacheInsert) {
183 for (auto nthreads : {1, 5}) {
184 for (auto max_keys :
185 {10 * 1024 * kStressFactor, 1 * 1024 * 1024 * kStressFactor}) {
186 cache_ = std::make_shared<VolatileCacheTier>();
187 RunInsertTest(nthreads, static_cast<size_t>(max_keys));
188 }
189 }
190 }
191
192 // DISABLED for now (somewhat expensive)
TEST_F(PersistentCacheTierTest,DISABLED_VolatileCacheInsertWithEviction)193 TEST_F(PersistentCacheTierTest, DISABLED_VolatileCacheInsertWithEviction) {
194 for (auto nthreads : {1, 5}) {
195 for (auto max_keys : {1 * 1024 * 1024 * kStressFactor}) {
196 cache_ = std::make_shared<VolatileCacheTier>(
197 /*compressed=*/true, /*size=*/static_cast<size_t>(1 * 1024 * 1024 * kStressFactor));
198 RunInsertTestWithEviction(nthreads, static_cast<size_t>(max_keys));
199 }
200 }
201 }
202
203 // Block cache tests
204 // DISABLED for now (expensive)
TEST_F(PersistentCacheTierTest,DISABLED_BlockCacheInsert)205 TEST_F(PersistentCacheTierTest, DISABLED_BlockCacheInsert) {
206 for (auto direct_writes : {true, false}) {
207 for (auto nthreads : {1, 5}) {
208 for (auto max_keys :
209 {10 * 1024 * kStressFactor, 1 * 1024 * 1024 * kStressFactor}) {
210 cache_ = NewBlockCache(Env::Default(), path_,
211 /*size=*/std::numeric_limits<uint64_t>::max(),
212 direct_writes);
213 RunInsertTest(nthreads, static_cast<size_t>(max_keys));
214 }
215 }
216 }
217 }
218
219 // DISABLED for now (somewhat expensive)
TEST_F(PersistentCacheTierTest,DISABLED_BlockCacheInsertWithEviction)220 TEST_F(PersistentCacheTierTest, DISABLED_BlockCacheInsertWithEviction) {
221 for (auto nthreads : {1, 5}) {
222 for (auto max_keys : {1 * 1024 * 1024 * kStressFactor}) {
223 cache_ = NewBlockCache(Env::Default(), path_,
224 /*max_size=*/static_cast<size_t>(200 * 1024 * 1024 * kStressFactor));
225 RunInsertTestWithEviction(nthreads, static_cast<size_t>(max_keys));
226 }
227 }
228 }
229
230 // Tiered cache tests
231 // DISABLED for now (expensive)
TEST_F(PersistentCacheTierTest,DISABLED_TieredCacheInsert)232 TEST_F(PersistentCacheTierTest, DISABLED_TieredCacheInsert) {
233 for (auto nthreads : {1, 5}) {
234 for (auto max_keys :
235 {10 * 1024 * kStressFactor, 1 * 1024 * 1024 * kStressFactor}) {
236 cache_ = NewTieredCache(Env::Default(), path_,
237 /*memory_size=*/static_cast<size_t>(1 * 1024 * 1024 * kStressFactor));
238 RunInsertTest(nthreads, static_cast<size_t>(max_keys));
239 }
240 }
241 }
242
243 // the tests causes a lot of file deletions which Travis limited testing
244 // environment cannot handle
245 // DISABLED for now (somewhat expensive)
TEST_F(PersistentCacheTierTest,DISABLED_TieredCacheInsertWithEviction)246 TEST_F(PersistentCacheTierTest, DISABLED_TieredCacheInsertWithEviction) {
247 for (auto nthreads : {1, 5}) {
248 for (auto max_keys : {1 * 1024 * 1024 * kStressFactor}) {
249 cache_ = NewTieredCache(
250 Env::Default(), path_,
251 /*memory_size=*/static_cast<size_t>(1 * 1024 * 1024 * kStressFactor),
252 /*block_cache_size*/ static_cast<size_t>(200 * 1024 * 1024 * kStressFactor));
253 RunInsertTestWithEviction(nthreads, static_cast<size_t>(max_keys));
254 }
255 }
256 }
257
MakeVolatileCache(const std::string &)258 std::shared_ptr<PersistentCacheTier> MakeVolatileCache(
259 const std::string& /*dbname*/) {
260 return std::make_shared<VolatileCacheTier>();
261 }
262
MakeBlockCache(const std::string & dbname)263 std::shared_ptr<PersistentCacheTier> MakeBlockCache(const std::string& dbname) {
264 return NewBlockCache(Env::Default(), dbname);
265 }
266
MakeTieredCache(const std::string & dbname)267 std::shared_ptr<PersistentCacheTier> MakeTieredCache(
268 const std::string& dbname) {
269 const auto memory_size = 1 * 1024 * 1024 * kStressFactor;
270 return NewTieredCache(Env::Default(), dbname, static_cast<size_t>(memory_size));
271 }
272
273 #ifdef OS_LINUX
UniqueIdCallback(void * arg)274 static void UniqueIdCallback(void* arg) {
275 int* result = reinterpret_cast<int*>(arg);
276 if (*result == -1) {
277 *result = 0;
278 }
279
280 ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearTrace();
281 ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
282 "GetUniqueIdFromFile:FS_IOC_GETVERSION", UniqueIdCallback);
283 }
284 #endif
285
TEST_F(PersistentCacheTierTest,FactoryTest)286 TEST_F(PersistentCacheTierTest, FactoryTest) {
287 for (auto nvm_opt : {true, false}) {
288 ASSERT_FALSE(cache_);
289 auto log = std::make_shared<ConsoleLogger>();
290 std::shared_ptr<PersistentCache> cache;
291 ASSERT_OK(NewPersistentCache(Env::Default(), path_,
292 /*size=*/1 * 1024 * 1024 * 1024, log, nvm_opt,
293 &cache));
294 ASSERT_TRUE(cache);
295 ASSERT_EQ(cache->Stats().size(), 1);
296 ASSERT_TRUE(cache->Stats()[0].size());
297 cache.reset();
298 }
299 }
300
PersistentCacheDBTest()301 PersistentCacheDBTest::PersistentCacheDBTest() : DBTestBase("/cache_test") {
302 #ifdef OS_LINUX
303 ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
304 ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
305 "GetUniqueIdFromFile:FS_IOC_GETVERSION", UniqueIdCallback);
306 ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
307 "NewRandomAccessFile:O_DIRECT", OnOpenForRead);
308 #endif
309 }
310
311 // test template
RunTest(const std::function<std::shared_ptr<PersistentCacheTier> (bool)> & new_pcache,const size_t max_keys=100* 1024,const size_t max_usecase=5)312 void PersistentCacheDBTest::RunTest(
313 const std::function<std::shared_ptr<PersistentCacheTier>(bool)>& new_pcache,
314 const size_t max_keys = 100 * 1024, const size_t max_usecase = 5) {
315
316 // number of insertion interations
317 int num_iter = static_cast<int>(max_keys * kStressFactor);
318
319 for (size_t iter = 0; iter < max_usecase; iter++) {
320 Options options;
321 options.write_buffer_size =
322 static_cast<size_t>(64 * 1024 * kStressFactor); // small write buffer
323 options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
324 options = CurrentOptions(options);
325
326 // setup page cache
327 std::shared_ptr<PersistentCacheTier> pcache;
328 BlockBasedTableOptions table_options;
329 table_options.cache_index_and_filter_blocks = true;
330
331 const size_t size_max = std::numeric_limits<size_t>::max();
332
333 switch (iter) {
334 case 0:
335 // page cache, block cache, no-compressed cache
336 pcache = new_pcache(/*is_compressed=*/true);
337 table_options.persistent_cache = pcache;
338 table_options.block_cache = NewLRUCache(size_max);
339 table_options.block_cache_compressed = nullptr;
340 options.table_factory.reset(NewBlockBasedTableFactory(table_options));
341 break;
342 case 1:
343 // page cache, block cache, compressed cache
344 pcache = new_pcache(/*is_compressed=*/true);
345 table_options.persistent_cache = pcache;
346 table_options.block_cache = NewLRUCache(size_max);
347 table_options.block_cache_compressed = NewLRUCache(size_max);
348 options.table_factory.reset(NewBlockBasedTableFactory(table_options));
349 break;
350 case 2:
351 // page cache, block cache, compressed cache + KNoCompression
352 // both block cache and compressed cache, but DB is not compressed
353 // also, make block cache sizes bigger, to trigger block cache hits
354 pcache = new_pcache(/*is_compressed=*/true);
355 table_options.persistent_cache = pcache;
356 table_options.block_cache = NewLRUCache(size_max);
357 table_options.block_cache_compressed = NewLRUCache(size_max);
358 options.table_factory.reset(NewBlockBasedTableFactory(table_options));
359 options.compression = kNoCompression;
360 break;
361 case 3:
362 // page cache, no block cache, no compressed cache
363 pcache = new_pcache(/*is_compressed=*/false);
364 table_options.persistent_cache = pcache;
365 table_options.block_cache = nullptr;
366 table_options.block_cache_compressed = nullptr;
367 options.table_factory.reset(NewBlockBasedTableFactory(table_options));
368 break;
369 case 4:
370 // page cache, no block cache, no compressed cache
371 // Page cache caches compressed blocks
372 pcache = new_pcache(/*is_compressed=*/true);
373 table_options.persistent_cache = pcache;
374 table_options.block_cache = nullptr;
375 table_options.block_cache_compressed = nullptr;
376 options.table_factory.reset(NewBlockBasedTableFactory(table_options));
377 break;
378 default:
379 FAIL();
380 }
381
382 std::vector<std::string> values;
383 // insert data
384 Insert(options, table_options, num_iter, &values);
385 // flush all data in cache to device
386 pcache->TEST_Flush();
387 // verify data
388 Verify(num_iter, values);
389
390 auto block_miss = TestGetTickerCount(options, BLOCK_CACHE_MISS);
391 auto compressed_block_hit =
392 TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_HIT);
393 auto compressed_block_miss =
394 TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_MISS);
395 auto page_hit = TestGetTickerCount(options, PERSISTENT_CACHE_HIT);
396 auto page_miss = TestGetTickerCount(options, PERSISTENT_CACHE_MISS);
397
398 // check that we triggered the appropriate code paths in the cache
399 switch (iter) {
400 case 0:
401 // page cache, block cache, no-compressed cache
402 ASSERT_GT(page_miss, 0);
403 ASSERT_GT(page_hit, 0);
404 ASSERT_GT(block_miss, 0);
405 ASSERT_EQ(compressed_block_miss, 0);
406 ASSERT_EQ(compressed_block_hit, 0);
407 break;
408 case 1:
409 // page cache, block cache, compressed cache
410 ASSERT_GT(page_miss, 0);
411 ASSERT_GT(block_miss, 0);
412 ASSERT_GT(compressed_block_miss, 0);
413 break;
414 case 2:
415 // page cache, block cache, compressed cache + KNoCompression
416 ASSERT_GT(page_miss, 0);
417 ASSERT_GT(page_hit, 0);
418 ASSERT_GT(block_miss, 0);
419 ASSERT_GT(compressed_block_miss, 0);
420 // remember kNoCompression
421 ASSERT_EQ(compressed_block_hit, 0);
422 break;
423 case 3:
424 case 4:
425 // page cache, no block cache, no compressed cache
426 ASSERT_GT(page_miss, 0);
427 ASSERT_GT(page_hit, 0);
428 ASSERT_EQ(compressed_block_hit, 0);
429 ASSERT_EQ(compressed_block_miss, 0);
430 break;
431 default:
432 FAIL();
433 }
434
435 options.create_if_missing = true;
436 DestroyAndReopen(options);
437
438 pcache->Close();
439 }
440 }
441
442 // Travis is unable to handle the normal version of the tests running out of
443 // fds, out of space and timeouts. This is an easier version of the test
444 // specifically written for Travis.
445 // Now used generally because main tests are too expensive as unit tests.
TEST_F(PersistentCacheDBTest,BasicTest)446 TEST_F(PersistentCacheDBTest, BasicTest) {
447 RunTest(std::bind(&MakeBlockCache, dbname_), /*max_keys=*/1024,
448 /*max_usecase=*/1);
449 }
450
451 // test table with block page cache
452 // DISABLED for now (very expensive, especially memory)
TEST_F(PersistentCacheDBTest,DISABLED_BlockCacheTest)453 TEST_F(PersistentCacheDBTest, DISABLED_BlockCacheTest) {
454 RunTest(std::bind(&MakeBlockCache, dbname_));
455 }
456
457 // test table with volatile page cache
458 // DISABLED for now (very expensive, especially memory)
TEST_F(PersistentCacheDBTest,DISABLED_VolatileCacheTest)459 TEST_F(PersistentCacheDBTest, DISABLED_VolatileCacheTest) {
460 RunTest(std::bind(&MakeVolatileCache, dbname_));
461 }
462
463 // test table with tiered page cache
464 // DISABLED for now (very expensive, especially memory)
TEST_F(PersistentCacheDBTest,DISABLED_TieredCacheTest)465 TEST_F(PersistentCacheDBTest, DISABLED_TieredCacheTest) {
466 RunTest(std::bind(&MakeTieredCache, dbname_));
467 }
468
469 } // namespace ROCKSDB_NAMESPACE
470
main(int argc,char ** argv)471 int main(int argc, char** argv) {
472 ::testing::InitGoogleTest(&argc, argv);
473 return RUN_ALL_TESTS();
474 }
475 #else // !defined(ROCKSDB_LITE) && !defined(OS_WIN)
main()476 int main() { return 0; }
477 #endif // !defined(ROCKSDB_LITE) && !defined(OS_WIN)
478