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