cuckoo_table_db_test.cc 10 KB

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