db_block_cache_test.cc 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761
  1. // Copyright (c) 2011-present, 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. #include <cstdlib>
  10. #include "cache/lru_cache.h"
  11. #include "db/db_test_util.h"
  12. #include "port/stack_trace.h"
  13. #include "util/compression.h"
  14. namespace ROCKSDB_NAMESPACE {
  15. class DBBlockCacheTest : public DBTestBase {
  16. private:
  17. size_t miss_count_ = 0;
  18. size_t hit_count_ = 0;
  19. size_t insert_count_ = 0;
  20. size_t failure_count_ = 0;
  21. size_t compression_dict_miss_count_ = 0;
  22. size_t compression_dict_hit_count_ = 0;
  23. size_t compression_dict_insert_count_ = 0;
  24. size_t compressed_miss_count_ = 0;
  25. size_t compressed_hit_count_ = 0;
  26. size_t compressed_insert_count_ = 0;
  27. size_t compressed_failure_count_ = 0;
  28. public:
  29. const size_t kNumBlocks = 10;
  30. const size_t kValueSize = 100;
  31. DBBlockCacheTest() : DBTestBase("/db_block_cache_test") {}
  32. BlockBasedTableOptions GetTableOptions() {
  33. BlockBasedTableOptions table_options;
  34. // Set a small enough block size so that each key-value get its own block.
  35. table_options.block_size = 1;
  36. return table_options;
  37. }
  38. Options GetOptions(const BlockBasedTableOptions& table_options) {
  39. Options options = CurrentOptions();
  40. options.create_if_missing = true;
  41. options.avoid_flush_during_recovery = false;
  42. // options.compression = kNoCompression;
  43. options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
  44. options.table_factory.reset(new BlockBasedTableFactory(table_options));
  45. return options;
  46. }
  47. void InitTable(const Options& /*options*/) {
  48. std::string value(kValueSize, 'a');
  49. for (size_t i = 0; i < kNumBlocks; i++) {
  50. ASSERT_OK(Put(ToString(i), value.c_str()));
  51. }
  52. }
  53. void RecordCacheCounters(const Options& options) {
  54. miss_count_ = TestGetTickerCount(options, BLOCK_CACHE_MISS);
  55. hit_count_ = TestGetTickerCount(options, BLOCK_CACHE_HIT);
  56. insert_count_ = TestGetTickerCount(options, BLOCK_CACHE_ADD);
  57. failure_count_ = TestGetTickerCount(options, BLOCK_CACHE_ADD_FAILURES);
  58. compressed_miss_count_ =
  59. TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_MISS);
  60. compressed_hit_count_ =
  61. TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_HIT);
  62. compressed_insert_count_ =
  63. TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_ADD);
  64. compressed_failure_count_ =
  65. TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_ADD_FAILURES);
  66. }
  67. void RecordCacheCountersForCompressionDict(const Options& options) {
  68. compression_dict_miss_count_ =
  69. TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_MISS);
  70. compression_dict_hit_count_ =
  71. TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_HIT);
  72. compression_dict_insert_count_ =
  73. TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_ADD);
  74. }
  75. void CheckCacheCounters(const Options& options, size_t expected_misses,
  76. size_t expected_hits, size_t expected_inserts,
  77. size_t expected_failures) {
  78. size_t new_miss_count = TestGetTickerCount(options, BLOCK_CACHE_MISS);
  79. size_t new_hit_count = TestGetTickerCount(options, BLOCK_CACHE_HIT);
  80. size_t new_insert_count = TestGetTickerCount(options, BLOCK_CACHE_ADD);
  81. size_t new_failure_count =
  82. TestGetTickerCount(options, BLOCK_CACHE_ADD_FAILURES);
  83. ASSERT_EQ(miss_count_ + expected_misses, new_miss_count);
  84. ASSERT_EQ(hit_count_ + expected_hits, new_hit_count);
  85. ASSERT_EQ(insert_count_ + expected_inserts, new_insert_count);
  86. ASSERT_EQ(failure_count_ + expected_failures, new_failure_count);
  87. miss_count_ = new_miss_count;
  88. hit_count_ = new_hit_count;
  89. insert_count_ = new_insert_count;
  90. failure_count_ = new_failure_count;
  91. }
  92. void CheckCacheCountersForCompressionDict(
  93. const Options& options, size_t expected_compression_dict_misses,
  94. size_t expected_compression_dict_hits,
  95. size_t expected_compression_dict_inserts) {
  96. size_t new_compression_dict_miss_count =
  97. TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_MISS);
  98. size_t new_compression_dict_hit_count =
  99. TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_HIT);
  100. size_t new_compression_dict_insert_count =
  101. TestGetTickerCount(options, BLOCK_CACHE_COMPRESSION_DICT_ADD);
  102. ASSERT_EQ(compression_dict_miss_count_ + expected_compression_dict_misses,
  103. new_compression_dict_miss_count);
  104. ASSERT_EQ(compression_dict_hit_count_ + expected_compression_dict_hits,
  105. new_compression_dict_hit_count);
  106. ASSERT_EQ(
  107. compression_dict_insert_count_ + expected_compression_dict_inserts,
  108. new_compression_dict_insert_count);
  109. compression_dict_miss_count_ = new_compression_dict_miss_count;
  110. compression_dict_hit_count_ = new_compression_dict_hit_count;
  111. compression_dict_insert_count_ = new_compression_dict_insert_count;
  112. }
  113. void CheckCompressedCacheCounters(const Options& options,
  114. size_t expected_misses,
  115. size_t expected_hits,
  116. size_t expected_inserts,
  117. size_t expected_failures) {
  118. size_t new_miss_count =
  119. TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_MISS);
  120. size_t new_hit_count =
  121. TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_HIT);
  122. size_t new_insert_count =
  123. TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_ADD);
  124. size_t new_failure_count =
  125. TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_ADD_FAILURES);
  126. ASSERT_EQ(compressed_miss_count_ + expected_misses, new_miss_count);
  127. ASSERT_EQ(compressed_hit_count_ + expected_hits, new_hit_count);
  128. ASSERT_EQ(compressed_insert_count_ + expected_inserts, new_insert_count);
  129. ASSERT_EQ(compressed_failure_count_ + expected_failures, new_failure_count);
  130. compressed_miss_count_ = new_miss_count;
  131. compressed_hit_count_ = new_hit_count;
  132. compressed_insert_count_ = new_insert_count;
  133. compressed_failure_count_ = new_failure_count;
  134. }
  135. };
  136. TEST_F(DBBlockCacheTest, IteratorBlockCacheUsage) {
  137. ReadOptions read_options;
  138. read_options.fill_cache = false;
  139. auto table_options = GetTableOptions();
  140. auto options = GetOptions(table_options);
  141. InitTable(options);
  142. std::shared_ptr<Cache> cache = NewLRUCache(0, 0, false);
  143. table_options.block_cache = cache;
  144. options.table_factory.reset(new BlockBasedTableFactory(table_options));
  145. Reopen(options);
  146. RecordCacheCounters(options);
  147. std::vector<std::unique_ptr<Iterator>> iterators(kNumBlocks - 1);
  148. Iterator* iter = nullptr;
  149. ASSERT_EQ(0, cache->GetUsage());
  150. iter = db_->NewIterator(read_options);
  151. iter->Seek(ToString(0));
  152. ASSERT_LT(0, cache->GetUsage());
  153. delete iter;
  154. iter = nullptr;
  155. ASSERT_EQ(0, cache->GetUsage());
  156. }
  157. TEST_F(DBBlockCacheTest, TestWithoutCompressedBlockCache) {
  158. ReadOptions read_options;
  159. auto table_options = GetTableOptions();
  160. auto options = GetOptions(table_options);
  161. InitTable(options);
  162. std::shared_ptr<Cache> cache = NewLRUCache(0, 0, false);
  163. table_options.block_cache = cache;
  164. options.table_factory.reset(new BlockBasedTableFactory(table_options));
  165. Reopen(options);
  166. RecordCacheCounters(options);
  167. std::vector<std::unique_ptr<Iterator>> iterators(kNumBlocks - 1);
  168. Iterator* iter = nullptr;
  169. // Load blocks into cache.
  170. for (size_t i = 0; i < kNumBlocks - 1; i++) {
  171. iter = db_->NewIterator(read_options);
  172. iter->Seek(ToString(i));
  173. ASSERT_OK(iter->status());
  174. CheckCacheCounters(options, 1, 0, 1, 0);
  175. iterators[i].reset(iter);
  176. }
  177. size_t usage = cache->GetUsage();
  178. ASSERT_LT(0, usage);
  179. cache->SetCapacity(usage);
  180. ASSERT_EQ(usage, cache->GetPinnedUsage());
  181. // Test with strict capacity limit.
  182. cache->SetStrictCapacityLimit(true);
  183. iter = db_->NewIterator(read_options);
  184. iter->Seek(ToString(kNumBlocks - 1));
  185. ASSERT_TRUE(iter->status().IsIncomplete());
  186. CheckCacheCounters(options, 1, 0, 0, 1);
  187. delete iter;
  188. iter = nullptr;
  189. // Release iterators and access cache again.
  190. for (size_t i = 0; i < kNumBlocks - 1; i++) {
  191. iterators[i].reset();
  192. CheckCacheCounters(options, 0, 0, 0, 0);
  193. }
  194. ASSERT_EQ(0, cache->GetPinnedUsage());
  195. for (size_t i = 0; i < kNumBlocks - 1; i++) {
  196. iter = db_->NewIterator(read_options);
  197. iter->Seek(ToString(i));
  198. ASSERT_OK(iter->status());
  199. CheckCacheCounters(options, 0, 1, 0, 0);
  200. iterators[i].reset(iter);
  201. }
  202. }
  203. #ifdef SNAPPY
  204. TEST_F(DBBlockCacheTest, TestWithCompressedBlockCache) {
  205. ReadOptions read_options;
  206. auto table_options = GetTableOptions();
  207. auto options = GetOptions(table_options);
  208. options.compression = CompressionType::kSnappyCompression;
  209. InitTable(options);
  210. std::shared_ptr<Cache> cache = NewLRUCache(0, 0, false);
  211. std::shared_ptr<Cache> compressed_cache = NewLRUCache(1 << 25, 0, false);
  212. table_options.block_cache = cache;
  213. table_options.block_cache_compressed = compressed_cache;
  214. options.table_factory.reset(new BlockBasedTableFactory(table_options));
  215. Reopen(options);
  216. RecordCacheCounters(options);
  217. std::vector<std::unique_ptr<Iterator>> iterators(kNumBlocks - 1);
  218. Iterator* iter = nullptr;
  219. // Load blocks into cache.
  220. for (size_t i = 0; i < kNumBlocks - 1; i++) {
  221. iter = db_->NewIterator(read_options);
  222. iter->Seek(ToString(i));
  223. ASSERT_OK(iter->status());
  224. CheckCacheCounters(options, 1, 0, 1, 0);
  225. CheckCompressedCacheCounters(options, 1, 0, 1, 0);
  226. iterators[i].reset(iter);
  227. }
  228. size_t usage = cache->GetUsage();
  229. ASSERT_LT(0, usage);
  230. ASSERT_EQ(usage, cache->GetPinnedUsage());
  231. size_t compressed_usage = compressed_cache->GetUsage();
  232. ASSERT_LT(0, compressed_usage);
  233. // Compressed block cache cannot be pinned.
  234. ASSERT_EQ(0, compressed_cache->GetPinnedUsage());
  235. // Set strict capacity limit flag. Now block will only load into compressed
  236. // block cache.
  237. cache->SetCapacity(usage);
  238. cache->SetStrictCapacityLimit(true);
  239. ASSERT_EQ(usage, cache->GetPinnedUsage());
  240. iter = db_->NewIterator(read_options);
  241. iter->Seek(ToString(kNumBlocks - 1));
  242. ASSERT_TRUE(iter->status().IsIncomplete());
  243. CheckCacheCounters(options, 1, 0, 0, 1);
  244. CheckCompressedCacheCounters(options, 1, 0, 1, 0);
  245. delete iter;
  246. iter = nullptr;
  247. // Clear strict capacity limit flag. This time we shall hit compressed block
  248. // cache.
  249. cache->SetStrictCapacityLimit(false);
  250. iter = db_->NewIterator(read_options);
  251. iter->Seek(ToString(kNumBlocks - 1));
  252. ASSERT_OK(iter->status());
  253. CheckCacheCounters(options, 1, 0, 1, 0);
  254. CheckCompressedCacheCounters(options, 0, 1, 0, 0);
  255. delete iter;
  256. iter = nullptr;
  257. }
  258. #endif // SNAPPY
  259. #ifndef ROCKSDB_LITE
  260. // Make sure that when options.block_cache is set, after a new table is
  261. // created its index/filter blocks are added to block cache.
  262. TEST_F(DBBlockCacheTest, IndexAndFilterBlocksOfNewTableAddedToCache) {
  263. Options options = CurrentOptions();
  264. options.create_if_missing = true;
  265. options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
  266. BlockBasedTableOptions table_options;
  267. table_options.cache_index_and_filter_blocks = true;
  268. table_options.filter_policy.reset(NewBloomFilterPolicy(20));
  269. options.table_factory.reset(new BlockBasedTableFactory(table_options));
  270. CreateAndReopenWithCF({"pikachu"}, options);
  271. ASSERT_OK(Put(1, "key", "val"));
  272. // Create a new table.
  273. ASSERT_OK(Flush(1));
  274. // index/filter blocks added to block cache right after table creation.
  275. ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
  276. ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
  277. ASSERT_EQ(2, /* only index/filter were added */
  278. TestGetTickerCount(options, BLOCK_CACHE_ADD));
  279. ASSERT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_DATA_MISS));
  280. uint64_t int_num;
  281. ASSERT_TRUE(
  282. dbfull()->GetIntProperty("rocksdb.estimate-table-readers-mem", &int_num));
  283. ASSERT_EQ(int_num, 0U);
  284. // Make sure filter block is in cache.
  285. std::string value;
  286. ReadOptions ropt;
  287. db_->KeyMayExist(ReadOptions(), handles_[1], "key", &value);
  288. // Miss count should remain the same.
  289. ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
  290. ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
  291. db_->KeyMayExist(ReadOptions(), handles_[1], "key", &value);
  292. ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
  293. ASSERT_EQ(2, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
  294. // Make sure index block is in cache.
  295. auto index_block_hit = TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT);
  296. value = Get(1, "key");
  297. ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
  298. ASSERT_EQ(index_block_hit + 1,
  299. TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT));
  300. value = Get(1, "key");
  301. ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
  302. ASSERT_EQ(index_block_hit + 2,
  303. TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT));
  304. }
  305. // With fill_cache = false, fills up the cache, then iterates over the entire
  306. // db, verify dummy entries inserted in `BlockBasedTable::NewDataBlockIterator`
  307. // does not cause heap-use-after-free errors in COMPILE_WITH_ASAN=1 runs
  308. TEST_F(DBBlockCacheTest, FillCacheAndIterateDB) {
  309. ReadOptions read_options;
  310. read_options.fill_cache = false;
  311. auto table_options = GetTableOptions();
  312. auto options = GetOptions(table_options);
  313. InitTable(options);
  314. std::shared_ptr<Cache> cache = NewLRUCache(10, 0, true);
  315. table_options.block_cache = cache;
  316. options.table_factory.reset(new BlockBasedTableFactory(table_options));
  317. Reopen(options);
  318. ASSERT_OK(Put("key1", "val1"));
  319. ASSERT_OK(Put("key2", "val2"));
  320. ASSERT_OK(Flush());
  321. ASSERT_OK(Put("key3", "val3"));
  322. ASSERT_OK(Put("key4", "val4"));
  323. ASSERT_OK(Flush());
  324. ASSERT_OK(Put("key5", "val5"));
  325. ASSERT_OK(Put("key6", "val6"));
  326. ASSERT_OK(Flush());
  327. Iterator* iter = nullptr;
  328. iter = db_->NewIterator(read_options);
  329. iter->Seek(ToString(0));
  330. while (iter->Valid()) {
  331. iter->Next();
  332. }
  333. delete iter;
  334. iter = nullptr;
  335. }
  336. TEST_F(DBBlockCacheTest, IndexAndFilterBlocksStats) {
  337. Options options = CurrentOptions();
  338. options.create_if_missing = true;
  339. options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
  340. BlockBasedTableOptions table_options;
  341. table_options.cache_index_and_filter_blocks = true;
  342. LRUCacheOptions co;
  343. // 500 bytes are enough to hold the first two blocks
  344. co.capacity = 500;
  345. co.num_shard_bits = 0;
  346. co.strict_capacity_limit = false;
  347. co.metadata_charge_policy = kDontChargeCacheMetadata;
  348. std::shared_ptr<Cache> cache = NewLRUCache(co);
  349. table_options.block_cache = cache;
  350. table_options.filter_policy.reset(NewBloomFilterPolicy(20, true));
  351. options.table_factory.reset(new BlockBasedTableFactory(table_options));
  352. CreateAndReopenWithCF({"pikachu"}, options);
  353. ASSERT_OK(Put(1, "longer_key", "val"));
  354. // Create a new table
  355. ASSERT_OK(Flush(1));
  356. size_t index_bytes_insert =
  357. TestGetTickerCount(options, BLOCK_CACHE_INDEX_BYTES_INSERT);
  358. size_t filter_bytes_insert =
  359. TestGetTickerCount(options, BLOCK_CACHE_FILTER_BYTES_INSERT);
  360. ASSERT_GT(index_bytes_insert, 0);
  361. ASSERT_GT(filter_bytes_insert, 0);
  362. ASSERT_EQ(cache->GetUsage(), index_bytes_insert + filter_bytes_insert);
  363. // set the cache capacity to the current usage
  364. cache->SetCapacity(index_bytes_insert + filter_bytes_insert);
  365. // The index and filter eviction statistics were broken by the refactoring
  366. // that moved the readers out of the block cache. Disabling these until we can
  367. // bring the stats back.
  368. // ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_INDEX_BYTES_EVICT), 0);
  369. // ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_FILTER_BYTES_EVICT), 0);
  370. // Note that the second key needs to be no longer than the first one.
  371. // Otherwise the second index block may not fit in cache.
  372. ASSERT_OK(Put(1, "key", "val"));
  373. // Create a new table
  374. ASSERT_OK(Flush(1));
  375. // cache evicted old index and block entries
  376. ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_INDEX_BYTES_INSERT),
  377. index_bytes_insert);
  378. ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_FILTER_BYTES_INSERT),
  379. filter_bytes_insert);
  380. // The index and filter eviction statistics were broken by the refactoring
  381. // that moved the readers out of the block cache. Disabling these until we can
  382. // bring the stats back.
  383. // ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_INDEX_BYTES_EVICT),
  384. // index_bytes_insert);
  385. // ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_FILTER_BYTES_EVICT),
  386. // filter_bytes_insert);
  387. }
  388. namespace {
  389. // A mock cache wraps LRUCache, and record how many entries have been
  390. // inserted for each priority.
  391. class MockCache : public LRUCache {
  392. public:
  393. static uint32_t high_pri_insert_count;
  394. static uint32_t low_pri_insert_count;
  395. MockCache()
  396. : LRUCache((size_t)1 << 25 /*capacity*/, 0 /*num_shard_bits*/,
  397. false /*strict_capacity_limit*/, 0.0 /*high_pri_pool_ratio*/) {
  398. }
  399. Status Insert(const Slice& key, void* value, size_t charge,
  400. void (*deleter)(const Slice& key, void* value), Handle** handle,
  401. Priority priority) override {
  402. if (priority == Priority::LOW) {
  403. low_pri_insert_count++;
  404. } else {
  405. high_pri_insert_count++;
  406. }
  407. return LRUCache::Insert(key, value, charge, deleter, handle, priority);
  408. }
  409. };
  410. uint32_t MockCache::high_pri_insert_count = 0;
  411. uint32_t MockCache::low_pri_insert_count = 0;
  412. } // anonymous namespace
  413. TEST_F(DBBlockCacheTest, IndexAndFilterBlocksCachePriority) {
  414. for (auto priority : {Cache::Priority::LOW, Cache::Priority::HIGH}) {
  415. Options options = CurrentOptions();
  416. options.create_if_missing = true;
  417. options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
  418. BlockBasedTableOptions table_options;
  419. table_options.cache_index_and_filter_blocks = true;
  420. table_options.block_cache.reset(new MockCache());
  421. table_options.filter_policy.reset(NewBloomFilterPolicy(20));
  422. table_options.cache_index_and_filter_blocks_with_high_priority =
  423. priority == Cache::Priority::HIGH ? true : false;
  424. options.table_factory.reset(new BlockBasedTableFactory(table_options));
  425. DestroyAndReopen(options);
  426. MockCache::high_pri_insert_count = 0;
  427. MockCache::low_pri_insert_count = 0;
  428. // Create a new table.
  429. ASSERT_OK(Put("foo", "value"));
  430. ASSERT_OK(Put("bar", "value"));
  431. ASSERT_OK(Flush());
  432. ASSERT_EQ(1, NumTableFilesAtLevel(0));
  433. // index/filter blocks added to block cache right after table creation.
  434. ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
  435. ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
  436. ASSERT_EQ(2, /* only index/filter were added */
  437. TestGetTickerCount(options, BLOCK_CACHE_ADD));
  438. ASSERT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_DATA_MISS));
  439. if (priority == Cache::Priority::LOW) {
  440. ASSERT_EQ(0u, MockCache::high_pri_insert_count);
  441. ASSERT_EQ(2u, MockCache::low_pri_insert_count);
  442. } else {
  443. ASSERT_EQ(2u, MockCache::high_pri_insert_count);
  444. ASSERT_EQ(0u, MockCache::low_pri_insert_count);
  445. }
  446. // Access data block.
  447. ASSERT_EQ("value", Get("foo"));
  448. ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
  449. ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
  450. ASSERT_EQ(3, /*adding data block*/
  451. TestGetTickerCount(options, BLOCK_CACHE_ADD));
  452. ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_DATA_MISS));
  453. // Data block should be inserted with low priority.
  454. if (priority == Cache::Priority::LOW) {
  455. ASSERT_EQ(0u, MockCache::high_pri_insert_count);
  456. ASSERT_EQ(3u, MockCache::low_pri_insert_count);
  457. } else {
  458. ASSERT_EQ(2u, MockCache::high_pri_insert_count);
  459. ASSERT_EQ(1u, MockCache::low_pri_insert_count);
  460. }
  461. }
  462. }
  463. TEST_F(DBBlockCacheTest, ParanoidFileChecks) {
  464. Options options = CurrentOptions();
  465. options.create_if_missing = true;
  466. options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
  467. options.level0_file_num_compaction_trigger = 2;
  468. options.paranoid_file_checks = true;
  469. BlockBasedTableOptions table_options;
  470. table_options.cache_index_and_filter_blocks = false;
  471. table_options.filter_policy.reset(NewBloomFilterPolicy(20));
  472. options.table_factory.reset(new BlockBasedTableFactory(table_options));
  473. CreateAndReopenWithCF({"pikachu"}, options);
  474. ASSERT_OK(Put(1, "1_key", "val"));
  475. ASSERT_OK(Put(1, "9_key", "val"));
  476. // Create a new table.
  477. ASSERT_OK(Flush(1));
  478. ASSERT_EQ(1, /* read and cache data block */
  479. TestGetTickerCount(options, BLOCK_CACHE_ADD));
  480. ASSERT_OK(Put(1, "1_key2", "val2"));
  481. ASSERT_OK(Put(1, "9_key2", "val2"));
  482. // Create a new SST file. This will further trigger a compaction
  483. // and generate another file.
  484. ASSERT_OK(Flush(1));
  485. dbfull()->TEST_WaitForCompact();
  486. ASSERT_EQ(3, /* Totally 3 files created up to now */
  487. TestGetTickerCount(options, BLOCK_CACHE_ADD));
  488. // After disabling options.paranoid_file_checks. NO further block
  489. // is added after generating a new file.
  490. ASSERT_OK(
  491. dbfull()->SetOptions(handles_[1], {{"paranoid_file_checks", "false"}}));
  492. ASSERT_OK(Put(1, "1_key3", "val3"));
  493. ASSERT_OK(Put(1, "9_key3", "val3"));
  494. ASSERT_OK(Flush(1));
  495. ASSERT_OK(Put(1, "1_key4", "val4"));
  496. ASSERT_OK(Put(1, "9_key4", "val4"));
  497. ASSERT_OK(Flush(1));
  498. dbfull()->TEST_WaitForCompact();
  499. ASSERT_EQ(3, /* Totally 3 files created up to now */
  500. TestGetTickerCount(options, BLOCK_CACHE_ADD));
  501. }
  502. TEST_F(DBBlockCacheTest, CompressedCache) {
  503. if (!Snappy_Supported()) {
  504. return;
  505. }
  506. int num_iter = 80;
  507. // Run this test three iterations.
  508. // Iteration 1: only a uncompressed block cache
  509. // Iteration 2: only a compressed block cache
  510. // Iteration 3: both block cache and compressed cache
  511. // Iteration 4: both block cache and compressed cache, but DB is not
  512. // compressed
  513. for (int iter = 0; iter < 4; iter++) {
  514. Options options = CurrentOptions();
  515. options.write_buffer_size = 64 * 1024; // small write buffer
  516. options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
  517. BlockBasedTableOptions table_options;
  518. switch (iter) {
  519. case 0:
  520. // only uncompressed block cache
  521. table_options.block_cache = NewLRUCache(8 * 1024);
  522. table_options.block_cache_compressed = nullptr;
  523. options.table_factory.reset(NewBlockBasedTableFactory(table_options));
  524. break;
  525. case 1:
  526. // no block cache, only compressed cache
  527. table_options.no_block_cache = true;
  528. table_options.block_cache = nullptr;
  529. table_options.block_cache_compressed = NewLRUCache(8 * 1024);
  530. options.table_factory.reset(NewBlockBasedTableFactory(table_options));
  531. break;
  532. case 2:
  533. // both compressed and uncompressed block cache
  534. table_options.block_cache = NewLRUCache(1024);
  535. table_options.block_cache_compressed = NewLRUCache(8 * 1024);
  536. options.table_factory.reset(NewBlockBasedTableFactory(table_options));
  537. break;
  538. case 3:
  539. // both block cache and compressed cache, but DB is not compressed
  540. // also, make block cache sizes bigger, to trigger block cache hits
  541. table_options.block_cache = NewLRUCache(1024 * 1024);
  542. table_options.block_cache_compressed = NewLRUCache(8 * 1024 * 1024);
  543. options.table_factory.reset(NewBlockBasedTableFactory(table_options));
  544. options.compression = kNoCompression;
  545. break;
  546. default:
  547. FAIL();
  548. }
  549. CreateAndReopenWithCF({"pikachu"}, options);
  550. // default column family doesn't have block cache
  551. Options no_block_cache_opts;
  552. no_block_cache_opts.statistics = options.statistics;
  553. no_block_cache_opts = CurrentOptions(no_block_cache_opts);
  554. BlockBasedTableOptions table_options_no_bc;
  555. table_options_no_bc.no_block_cache = true;
  556. no_block_cache_opts.table_factory.reset(
  557. NewBlockBasedTableFactory(table_options_no_bc));
  558. ReopenWithColumnFamilies(
  559. {"default", "pikachu"},
  560. std::vector<Options>({no_block_cache_opts, options}));
  561. Random rnd(301);
  562. // Write 8MB (80 values, each 100K)
  563. ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0);
  564. std::vector<std::string> values;
  565. std::string str;
  566. for (int i = 0; i < num_iter; i++) {
  567. if (i % 4 == 0) { // high compression ratio
  568. str = RandomString(&rnd, 1000);
  569. }
  570. values.push_back(str);
  571. ASSERT_OK(Put(1, Key(i), values[i]));
  572. }
  573. // flush all data from memtable so that reads are from block cache
  574. ASSERT_OK(Flush(1));
  575. for (int i = 0; i < num_iter; i++) {
  576. ASSERT_EQ(Get(1, Key(i)), values[i]);
  577. }
  578. // check that we triggered the appropriate code paths in the cache
  579. switch (iter) {
  580. case 0:
  581. // only uncompressed block cache
  582. ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_MISS), 0);
  583. ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_MISS), 0);
  584. break;
  585. case 1:
  586. // no block cache, only compressed cache
  587. ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_MISS), 0);
  588. ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_MISS), 0);
  589. break;
  590. case 2:
  591. // both compressed and uncompressed block cache
  592. ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_MISS), 0);
  593. ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_MISS), 0);
  594. break;
  595. case 3:
  596. // both compressed and uncompressed block cache
  597. ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_MISS), 0);
  598. ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_HIT), 0);
  599. ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_MISS), 0);
  600. // compressed doesn't have any hits since blocks are not compressed on
  601. // storage
  602. ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_HIT), 0);
  603. break;
  604. default:
  605. FAIL();
  606. }
  607. options.create_if_missing = true;
  608. DestroyAndReopen(options);
  609. }
  610. }
  611. TEST_F(DBBlockCacheTest, CacheCompressionDict) {
  612. const int kNumFiles = 4;
  613. const int kNumEntriesPerFile = 128;
  614. const int kNumBytesPerEntry = 1024;
  615. // Try all the available libraries that support dictionary compression
  616. std::vector<CompressionType> compression_types;
  617. if (Zlib_Supported()) {
  618. compression_types.push_back(kZlibCompression);
  619. }
  620. if (LZ4_Supported()) {
  621. compression_types.push_back(kLZ4Compression);
  622. compression_types.push_back(kLZ4HCCompression);
  623. }
  624. if (ZSTD_Supported()) {
  625. compression_types.push_back(kZSTD);
  626. } else if (ZSTDNotFinal_Supported()) {
  627. compression_types.push_back(kZSTDNotFinalCompression);
  628. }
  629. Random rnd(301);
  630. for (auto compression_type : compression_types) {
  631. Options options = CurrentOptions();
  632. options.compression = compression_type;
  633. options.compression_opts.max_dict_bytes = 4096;
  634. options.create_if_missing = true;
  635. options.num_levels = 2;
  636. options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
  637. options.target_file_size_base = kNumEntriesPerFile * kNumBytesPerEntry;
  638. BlockBasedTableOptions table_options;
  639. table_options.cache_index_and_filter_blocks = true;
  640. table_options.block_cache.reset(new MockCache());
  641. options.table_factory.reset(new BlockBasedTableFactory(table_options));
  642. DestroyAndReopen(options);
  643. RecordCacheCountersForCompressionDict(options);
  644. for (int i = 0; i < kNumFiles; ++i) {
  645. ASSERT_EQ(i, NumTableFilesAtLevel(0, 0));
  646. for (int j = 0; j < kNumEntriesPerFile; ++j) {
  647. std::string value = RandomString(&rnd, kNumBytesPerEntry);
  648. ASSERT_OK(Put(Key(j * kNumFiles + i), value.c_str()));
  649. }
  650. ASSERT_OK(Flush());
  651. }
  652. dbfull()->TEST_WaitForCompact();
  653. ASSERT_EQ(0, NumTableFilesAtLevel(0));
  654. ASSERT_EQ(kNumFiles, NumTableFilesAtLevel(1));
  655. // Compression dictionary blocks are preloaded.
  656. CheckCacheCountersForCompressionDict(
  657. options, kNumFiles /* expected_compression_dict_misses */,
  658. 0 /* expected_compression_dict_hits */,
  659. kNumFiles /* expected_compression_dict_inserts */);
  660. // Seek to a key in a file. It should cause the SST's dictionary meta-block
  661. // to be read.
  662. RecordCacheCounters(options);
  663. RecordCacheCountersForCompressionDict(options);
  664. ReadOptions read_options;
  665. ASSERT_NE("NOT_FOUND", Get(Key(kNumFiles * kNumEntriesPerFile - 1)));
  666. // Two block hits: index and dictionary since they are prefetched
  667. // One block missed/added: data block
  668. CheckCacheCounters(options, 1 /* expected_misses */, 2 /* expected_hits */,
  669. 1 /* expected_inserts */, 0 /* expected_failures */);
  670. CheckCacheCountersForCompressionDict(
  671. options, 0 /* expected_compression_dict_misses */,
  672. 1 /* expected_compression_dict_hits */,
  673. 0 /* expected_compression_dict_inserts */);
  674. }
  675. }
  676. #endif // ROCKSDB_LITE
  677. } // namespace ROCKSDB_NAMESPACE
  678. int main(int argc, char** argv) {
  679. ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
  680. ::testing::InitGoogleTest(&argc, argv);
  681. return RUN_ALL_TESTS();
  682. }