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