cuckoo_table_db_test.cc 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  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. #include "db/db_impl/db_impl.h"
  6. #include "db/db_test_util.h"
  7. #include "rocksdb/db.h"
  8. #include "rocksdb/env.h"
  9. #include "table/cuckoo/cuckoo_table_factory.h"
  10. #include "table/cuckoo/cuckoo_table_reader.h"
  11. #include "table/meta_blocks.h"
  12. #include "test_util/testharness.h"
  13. #include "test_util/testutil.h"
  14. #include "util/cast_util.h"
  15. #include "util/string_util.h"
  16. namespace ROCKSDB_NAMESPACE {
  17. class CuckooTableDBTest : public testing::Test {
  18. private:
  19. std::string dbname_;
  20. Env* env_;
  21. DB* db_;
  22. public:
  23. CuckooTableDBTest() : env_(Env::Default()) {
  24. dbname_ = test::PerThreadDBPath("cuckoo_table_db_test");
  25. EXPECT_OK(DestroyDB(dbname_, Options()));
  26. db_ = nullptr;
  27. Reopen();
  28. }
  29. ~CuckooTableDBTest() override {
  30. delete db_;
  31. EXPECT_OK(DestroyDB(dbname_, Options()));
  32. }
  33. Options CurrentOptions() {
  34. Options options;
  35. options.level_compaction_dynamic_level_bytes = false;
  36. options.table_factory.reset(NewCuckooTableFactory());
  37. options.memtable_factory.reset(NewHashLinkListRepFactory(4, 0, 3, true));
  38. options.allow_mmap_reads = true;
  39. options.create_if_missing = true;
  40. options.allow_concurrent_memtable_write = false;
  41. return options;
  42. }
  43. DBImpl* dbfull() { return static_cast_with_check<DBImpl>(db_); }
  44. // The following util methods are copied from plain_table_db_test.
  45. void Reopen(Options* options = nullptr) {
  46. delete db_;
  47. db_ = nullptr;
  48. Options opts;
  49. if (options != nullptr) {
  50. opts = *options;
  51. } else {
  52. opts = CurrentOptions();
  53. opts.create_if_missing = true;
  54. }
  55. ASSERT_OK(DB::Open(opts, dbname_, &db_));
  56. }
  57. void DestroyAndReopen(Options* options) {
  58. assert(options);
  59. ASSERT_OK(db_->Close());
  60. delete db_;
  61. db_ = nullptr;
  62. ASSERT_OK(DestroyDB(dbname_, *options));
  63. Reopen(options);
  64. }
  65. Status Put(const Slice& k, const Slice& v) {
  66. return db_->Put(WriteOptions(), k, v);
  67. }
  68. Status Delete(const std::string& k) { return db_->Delete(WriteOptions(), k); }
  69. std::string Get(const std::string& k) {
  70. ReadOptions options;
  71. std::string result;
  72. Status s = db_->Get(options, k, &result);
  73. if (s.IsNotFound()) {
  74. result = "NOT_FOUND";
  75. } else if (!s.ok()) {
  76. result = s.ToString();
  77. }
  78. return result;
  79. }
  80. int NumTableFilesAtLevel(int level) {
  81. std::string property;
  82. EXPECT_TRUE(db_->GetProperty(
  83. "rocksdb.num-files-at-level" + std::to_string(level), &property));
  84. return atoi(property.c_str());
  85. }
  86. // Return spread of files per level
  87. std::string FilesPerLevel() {
  88. std::string result;
  89. size_t last_non_zero_offset = 0;
  90. for (int level = 0; level < db_->NumberLevels(); level++) {
  91. int f = NumTableFilesAtLevel(level);
  92. char buf[100];
  93. snprintf(buf, sizeof(buf), "%s%d", (level ? "," : ""), f);
  94. result += buf;
  95. if (f > 0) {
  96. last_non_zero_offset = result.size();
  97. }
  98. }
  99. result.resize(last_non_zero_offset);
  100. return result;
  101. }
  102. };
  103. TEST_F(CuckooTableDBTest, Flush) {
  104. // Try with empty DB first.
  105. ASSERT_TRUE(dbfull() != nullptr);
  106. ASSERT_EQ("NOT_FOUND", Get("key2"));
  107. // Add some values to db.
  108. Options options = CurrentOptions();
  109. Reopen(&options);
  110. ASSERT_OK(Put("key1", "v1"));
  111. ASSERT_OK(Put("key2", "v2"));
  112. ASSERT_OK(Put("key3", "v3"));
  113. ASSERT_OK(dbfull()->TEST_FlushMemTable());
  114. TablePropertiesCollection ptc;
  115. ASSERT_OK(static_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc));
  116. VerifySstUniqueIds(ptc);
  117. ASSERT_EQ(1U, ptc.size());
  118. ASSERT_EQ(3U, ptc.begin()->second->num_entries);
  119. ASSERT_EQ("1", FilesPerLevel());
  120. ASSERT_EQ("v1", Get("key1"));
  121. ASSERT_EQ("v2", Get("key2"));
  122. ASSERT_EQ("v3", Get("key3"));
  123. ASSERT_EQ("NOT_FOUND", Get("key4"));
  124. // Now add more keys and flush.
  125. ASSERT_OK(Put("key4", "v4"));
  126. ASSERT_OK(Put("key5", "v5"));
  127. ASSERT_OK(Put("key6", "v6"));
  128. ASSERT_OK(dbfull()->TEST_FlushMemTable());
  129. ASSERT_OK(static_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc));
  130. VerifySstUniqueIds(ptc);
  131. ASSERT_EQ(2U, ptc.size());
  132. auto row = ptc.begin();
  133. ASSERT_EQ(3U, row->second->num_entries);
  134. ASSERT_EQ(3U, (++row)->second->num_entries);
  135. ASSERT_EQ("2", FilesPerLevel());
  136. ASSERT_EQ("v1", Get("key1"));
  137. ASSERT_EQ("v2", Get("key2"));
  138. ASSERT_EQ("v3", Get("key3"));
  139. ASSERT_EQ("v4", Get("key4"));
  140. ASSERT_EQ("v5", Get("key5"));
  141. ASSERT_EQ("v6", Get("key6"));
  142. ASSERT_OK(Delete("key6"));
  143. ASSERT_OK(Delete("key5"));
  144. ASSERT_OK(Delete("key4"));
  145. ASSERT_OK(dbfull()->TEST_FlushMemTable());
  146. ASSERT_OK(static_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc));
  147. VerifySstUniqueIds(ptc);
  148. ASSERT_EQ(3U, ptc.size());
  149. row = ptc.begin();
  150. ASSERT_EQ(3U, row->second->num_entries);
  151. ASSERT_EQ(3U, (++row)->second->num_entries);
  152. ASSERT_EQ(3U, (++row)->second->num_entries);
  153. ASSERT_EQ("3", FilesPerLevel());
  154. ASSERT_EQ("v1", Get("key1"));
  155. ASSERT_EQ("v2", Get("key2"));
  156. ASSERT_EQ("v3", Get("key3"));
  157. ASSERT_EQ("NOT_FOUND", Get("key4"));
  158. ASSERT_EQ("NOT_FOUND", Get("key5"));
  159. ASSERT_EQ("NOT_FOUND", Get("key6"));
  160. }
  161. TEST_F(CuckooTableDBTest, FlushWithDuplicateKeys) {
  162. Options options = CurrentOptions();
  163. Reopen(&options);
  164. ASSERT_OK(Put("key1", "v1"));
  165. ASSERT_OK(Put("key2", "v2"));
  166. ASSERT_OK(Put("key1", "v3")); // Duplicate
  167. ASSERT_OK(dbfull()->TEST_FlushMemTable());
  168. TablePropertiesCollection ptc;
  169. ASSERT_OK(static_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc));
  170. VerifySstUniqueIds(ptc);
  171. ASSERT_EQ(1U, ptc.size());
  172. ASSERT_EQ(2U, ptc.begin()->second->num_entries);
  173. ASSERT_EQ("1", FilesPerLevel());
  174. ASSERT_EQ("v3", Get("key1"));
  175. ASSERT_EQ("v2", Get("key2"));
  176. }
  177. namespace {
  178. static std::string Key(int i) {
  179. char buf[100];
  180. snprintf(buf, sizeof(buf), "key_______%06d", i);
  181. return std::string(buf);
  182. }
  183. static std::string Uint64Key(uint64_t i) {
  184. std::string str;
  185. str.resize(8);
  186. memcpy(str.data(), static_cast<void*>(&i), 8);
  187. return str;
  188. }
  189. } // namespace.
  190. TEST_F(CuckooTableDBTest, Uint64Comparator) {
  191. Options options = CurrentOptions();
  192. options.comparator = test::Uint64Comparator();
  193. DestroyAndReopen(&options);
  194. ASSERT_OK(Put(Uint64Key(1), "v1"));
  195. ASSERT_OK(Put(Uint64Key(2), "v2"));
  196. ASSERT_OK(Put(Uint64Key(3), "v3"));
  197. ASSERT_OK(dbfull()->TEST_FlushMemTable());
  198. ASSERT_EQ("v1", Get(Uint64Key(1)));
  199. ASSERT_EQ("v2", Get(Uint64Key(2)));
  200. ASSERT_EQ("v3", Get(Uint64Key(3)));
  201. ASSERT_EQ("NOT_FOUND", Get(Uint64Key(4)));
  202. // Add more keys.
  203. ASSERT_OK(Delete(Uint64Key(2))); // Delete.
  204. ASSERT_OK(dbfull()->TEST_FlushMemTable());
  205. ASSERT_OK(Put(Uint64Key(3), "v0")); // Update.
  206. ASSERT_OK(Put(Uint64Key(4), "v4"));
  207. ASSERT_OK(dbfull()->TEST_FlushMemTable());
  208. ASSERT_EQ("v1", Get(Uint64Key(1)));
  209. ASSERT_EQ("NOT_FOUND", Get(Uint64Key(2)));
  210. ASSERT_EQ("v0", Get(Uint64Key(3)));
  211. ASSERT_EQ("v4", Get(Uint64Key(4)));
  212. }
  213. TEST_F(CuckooTableDBTest, CompactionIntoMultipleFiles) {
  214. // Create a big L0 file and check it compacts into multiple files in L1.
  215. Options options = CurrentOptions();
  216. options.write_buffer_size = 270 << 10;
  217. // Two SST files should be created, each containing 14 keys.
  218. // Number of buckets will be 16. Total size ~156 KB.
  219. options.target_file_size_base = 160 << 10;
  220. Reopen(&options);
  221. // Write 28 values, each 10016 B ~ 10KB
  222. for (int idx = 0; idx < 28; ++idx) {
  223. ASSERT_OK(Put(Key(idx), std::string(10000, 'a' + char(idx))));
  224. }
  225. ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
  226. ASSERT_EQ("1", FilesPerLevel());
  227. ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr,
  228. true /* disallow trivial move */));
  229. ASSERT_EQ("0,2", FilesPerLevel());
  230. for (int idx = 0; idx < 28; ++idx) {
  231. ASSERT_EQ(std::string(10000, 'a' + char(idx)), Get(Key(idx)));
  232. }
  233. }
  234. TEST_F(CuckooTableDBTest, SameKeyInsertedInTwoDifferentFilesAndCompacted) {
  235. // Insert same key twice so that they go to different SST files. Then wait for
  236. // compaction and check if the latest value is stored and old value removed.
  237. Options options = CurrentOptions();
  238. options.write_buffer_size = 100 << 10; // 100KB
  239. options.level0_file_num_compaction_trigger = 2;
  240. Reopen(&options);
  241. // Write 11 values, each 10016 B
  242. for (int idx = 0; idx < 11; ++idx) {
  243. ASSERT_OK(Put(Key(idx), std::string(10000, 'a')));
  244. }
  245. ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
  246. ASSERT_EQ("1", FilesPerLevel());
  247. // Generate one more file in level-0, and should trigger level-0 compaction
  248. for (int idx = 0; idx < 11; ++idx) {
  249. ASSERT_OK(Put(Key(idx), std::string(10000, 'a' + char(idx))));
  250. }
  251. ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
  252. ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr));
  253. ASSERT_EQ("0,1", FilesPerLevel());
  254. for (int idx = 0; idx < 11; ++idx) {
  255. ASSERT_EQ(std::string(10000, 'a' + char(idx)), Get(Key(idx)));
  256. }
  257. }
  258. TEST_F(CuckooTableDBTest, AdaptiveTable) {
  259. Options options = CurrentOptions();
  260. // Ensure options compatible with PlainTable
  261. options.prefix_extractor.reset(NewCappedPrefixTransform(8));
  262. // Write some keys using cuckoo table.
  263. options.table_factory.reset(NewCuckooTableFactory());
  264. Reopen(&options);
  265. ASSERT_OK(Put("key1", "v1"));
  266. ASSERT_OK(Put("key2", "v2"));
  267. ASSERT_OK(Put("key3", "v3"));
  268. ASSERT_OK(dbfull()->TEST_FlushMemTable());
  269. // Write some keys using plain table.
  270. std::shared_ptr<TableFactory> block_based_factory(
  271. NewBlockBasedTableFactory());
  272. std::shared_ptr<TableFactory> plain_table_factory(NewPlainTableFactory());
  273. std::shared_ptr<TableFactory> cuckoo_table_factory(NewCuckooTableFactory());
  274. options.create_if_missing = false;
  275. options.table_factory.reset(
  276. NewAdaptiveTableFactory(plain_table_factory, block_based_factory,
  277. plain_table_factory, cuckoo_table_factory));
  278. Reopen(&options);
  279. ASSERT_OK(Put("key4", "v4"));
  280. ASSERT_OK(Put("key1", "v5"));
  281. ASSERT_OK(dbfull()->TEST_FlushMemTable());
  282. // Write some keys using block based table.
  283. options.table_factory.reset(
  284. NewAdaptiveTableFactory(block_based_factory, block_based_factory,
  285. plain_table_factory, cuckoo_table_factory));
  286. Reopen(&options);
  287. ASSERT_OK(Put("key5", "v6"));
  288. ASSERT_OK(Put("key2", "v7"));
  289. ASSERT_OK(dbfull()->TEST_FlushMemTable());
  290. ASSERT_EQ("v5", Get("key1"));
  291. ASSERT_EQ("v7", Get("key2"));
  292. ASSERT_EQ("v3", Get("key3"));
  293. ASSERT_EQ("v4", Get("key4"));
  294. ASSERT_EQ("v6", Get("key5"));
  295. }
  296. } // namespace ROCKSDB_NAMESPACE
  297. int main(int argc, char** argv) {
  298. if (ROCKSDB_NAMESPACE::port::kLittleEndian) {
  299. ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
  300. ::testing::InitGoogleTest(&argc, argv);
  301. return RUN_ALL_TESTS();
  302. } else {
  303. fprintf(stderr, "SKIPPED as Cuckoo table doesn't support Big Endian\n");
  304. return 0;
  305. }
  306. }