]>
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 | #include <functional> | |
7 | ||
8 | #include "db/db_test_util.h" | |
9 | #include "port/port.h" | |
10 | #include "port/stack_trace.h" | |
11 | #include "rocksdb/sst_file_writer.h" | |
f67539c2 TL |
12 | #include "test_util/fault_injection_test_env.h" |
13 | #include "test_util/testutil.h" | |
7c673cae | 14 | |
f67539c2 | 15 | namespace ROCKSDB_NAMESPACE { |
7c673cae FG |
16 | |
17 | #ifndef ROCKSDB_LITE | |
494da23a TL |
18 | class ExternalSSTFileBasicTest |
19 | : public DBTestBase, | |
20 | public ::testing::WithParamInterface<std::tuple<bool, bool>> { | |
7c673cae | 21 | public: |
494da23a | 22 | ExternalSSTFileBasicTest() : DBTestBase("/external_sst_file_basic_test") { |
7c673cae | 23 | sst_files_dir_ = dbname_ + "/sst_files/"; |
f67539c2 | 24 | fault_injection_test_env_.reset(new FaultInjectionTestEnv(Env::Default())); |
7c673cae FG |
25 | DestroyAndRecreateExternalSSTFilesDir(); |
26 | } | |
27 | ||
28 | void DestroyAndRecreateExternalSSTFilesDir() { | |
29 | test::DestroyDir(env_, sst_files_dir_); | |
30 | env_->CreateDir(sst_files_dir_); | |
31 | } | |
32 | ||
33 | Status DeprecatedAddFile(const std::vector<std::string>& files, | |
34 | bool move_files = false, | |
35 | bool skip_snapshot_check = false) { | |
36 | IngestExternalFileOptions opts; | |
37 | opts.move_files = move_files; | |
38 | opts.snapshot_consistency = !skip_snapshot_check; | |
39 | opts.allow_global_seqno = false; | |
40 | opts.allow_blocking_flush = false; | |
41 | return db_->IngestExternalFile(files, opts); | |
42 | } | |
43 | ||
44 | Status GenerateAndAddExternalFile( | |
11fdf7f2 TL |
45 | const Options options, std::vector<int> keys, |
46 | const std::vector<ValueType>& value_types, | |
47 | std::vector<std::pair<int, int>> range_deletions, int file_id, | |
494da23a | 48 | bool write_global_seqno, bool verify_checksums_before_ingest, |
7c673cae | 49 | std::map<std::string, std::string>* true_data) { |
11fdf7f2 | 50 | assert(value_types.size() == 1 || keys.size() == value_types.size()); |
7c673cae FG |
51 | std::string file_path = sst_files_dir_ + ToString(file_id); |
52 | SstFileWriter sst_file_writer(EnvOptions(), options); | |
53 | ||
54 | Status s = sst_file_writer.Open(file_path); | |
55 | if (!s.ok()) { | |
56 | return s; | |
57 | } | |
11fdf7f2 TL |
58 | for (size_t i = 0; i < range_deletions.size(); i++) { |
59 | // Account for the effect of range deletions on true_data before | |
60 | // all point operators, even though sst_file_writer.DeleteRange | |
61 | // must be called before other sst_file_writer methods. This is | |
62 | // because point writes take precedence over range deletions | |
63 | // in the same ingested sst. | |
64 | std::string start_key = Key(range_deletions[i].first); | |
65 | std::string end_key = Key(range_deletions[i].second); | |
66 | s = sst_file_writer.DeleteRange(start_key, end_key); | |
67 | if (!s.ok()) { | |
68 | sst_file_writer.Finish(); | |
69 | return s; | |
70 | } | |
71 | auto start_key_it = true_data->find(start_key); | |
72 | if (start_key_it == true_data->end()) { | |
73 | start_key_it = true_data->upper_bound(start_key); | |
74 | } | |
75 | auto end_key_it = true_data->find(end_key); | |
76 | if (end_key_it == true_data->end()) { | |
77 | end_key_it = true_data->upper_bound(end_key); | |
78 | } | |
79 | true_data->erase(start_key_it, end_key_it); | |
80 | } | |
81 | for (size_t i = 0; i < keys.size(); i++) { | |
82 | std::string key = Key(keys[i]); | |
83 | std::string value = Key(keys[i]) + ToString(file_id); | |
84 | ValueType value_type = | |
85 | (value_types.size() == 1 ? value_types[0] : value_types[i]); | |
86 | switch (value_type) { | |
87 | case ValueType::kTypeValue: | |
88 | s = sst_file_writer.Put(key, value); | |
89 | (*true_data)[key] = value; | |
90 | break; | |
91 | case ValueType::kTypeMerge: | |
92 | s = sst_file_writer.Merge(key, value); | |
93 | // we only use TestPutOperator in this test | |
94 | (*true_data)[key] = value; | |
95 | break; | |
96 | case ValueType::kTypeDeletion: | |
97 | s = sst_file_writer.Delete(key); | |
98 | true_data->erase(key); | |
99 | break; | |
100 | default: | |
101 | return Status::InvalidArgument("Value type is not supported"); | |
102 | } | |
7c673cae FG |
103 | if (!s.ok()) { |
104 | sst_file_writer.Finish(); | |
105 | return s; | |
106 | } | |
107 | } | |
108 | s = sst_file_writer.Finish(); | |
109 | ||
110 | if (s.ok()) { | |
111 | IngestExternalFileOptions ifo; | |
112 | ifo.allow_global_seqno = true; | |
494da23a TL |
113 | ifo.write_global_seqno = write_global_seqno; |
114 | ifo.verify_checksums_before_ingest = verify_checksums_before_ingest; | |
7c673cae FG |
115 | s = db_->IngestExternalFile({file_path}, ifo); |
116 | } | |
7c673cae FG |
117 | return s; |
118 | } | |
119 | ||
11fdf7f2 TL |
120 | Status GenerateAndAddExternalFile( |
121 | const Options options, std::vector<int> keys, | |
122 | const std::vector<ValueType>& value_types, int file_id, | |
494da23a | 123 | bool write_global_seqno, bool verify_checksums_before_ingest, |
11fdf7f2 | 124 | std::map<std::string, std::string>* true_data) { |
494da23a TL |
125 | return GenerateAndAddExternalFile( |
126 | options, keys, value_types, {}, file_id, write_global_seqno, | |
127 | verify_checksums_before_ingest, true_data); | |
11fdf7f2 TL |
128 | } |
129 | ||
130 | Status GenerateAndAddExternalFile( | |
131 | const Options options, std::vector<int> keys, const ValueType value_type, | |
494da23a TL |
132 | int file_id, bool write_global_seqno, bool verify_checksums_before_ingest, |
133 | std::map<std::string, std::string>* true_data) { | |
134 | return GenerateAndAddExternalFile( | |
135 | options, keys, std::vector<ValueType>(1, value_type), file_id, | |
136 | write_global_seqno, verify_checksums_before_ingest, true_data); | |
11fdf7f2 TL |
137 | } |
138 | ||
494da23a TL |
139 | ~ExternalSSTFileBasicTest() override { |
140 | test::DestroyDir(env_, sst_files_dir_); | |
141 | } | |
7c673cae FG |
142 | |
143 | protected: | |
144 | std::string sst_files_dir_; | |
f67539c2 | 145 | std::unique_ptr<FaultInjectionTestEnv> fault_injection_test_env_; |
7c673cae FG |
146 | }; |
147 | ||
148 | TEST_F(ExternalSSTFileBasicTest, Basic) { | |
149 | Options options = CurrentOptions(); | |
150 | ||
151 | SstFileWriter sst_file_writer(EnvOptions(), options); | |
152 | ||
153 | // Current file size should be 0 after sst_file_writer init and before open a | |
154 | // file. | |
155 | ASSERT_EQ(sst_file_writer.FileSize(), 0); | |
156 | ||
157 | // file1.sst (0 => 99) | |
158 | std::string file1 = sst_files_dir_ + "file1.sst"; | |
159 | ASSERT_OK(sst_file_writer.Open(file1)); | |
160 | for (int k = 0; k < 100; k++) { | |
11fdf7f2 | 161 | ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val")); |
7c673cae FG |
162 | } |
163 | ExternalSstFileInfo file1_info; | |
164 | Status s = sst_file_writer.Finish(&file1_info); | |
165 | ASSERT_TRUE(s.ok()) << s.ToString(); | |
166 | ||
167 | // Current file size should be non-zero after success write. | |
168 | ASSERT_GT(sst_file_writer.FileSize(), 0); | |
169 | ||
170 | ASSERT_EQ(file1_info.file_path, file1); | |
171 | ASSERT_EQ(file1_info.num_entries, 100); | |
172 | ASSERT_EQ(file1_info.smallest_key, Key(0)); | |
173 | ASSERT_EQ(file1_info.largest_key, Key(99)); | |
11fdf7f2 TL |
174 | ASSERT_EQ(file1_info.num_range_del_entries, 0); |
175 | ASSERT_EQ(file1_info.smallest_range_del_key, ""); | |
176 | ASSERT_EQ(file1_info.largest_range_del_key, ""); | |
7c673cae | 177 | // sst_file_writer already finished, cannot add this value |
11fdf7f2 TL |
178 | s = sst_file_writer.Put(Key(100), "bad_val"); |
179 | ASSERT_FALSE(s.ok()) << s.ToString(); | |
180 | s = sst_file_writer.DeleteRange(Key(100), Key(200)); | |
7c673cae FG |
181 | ASSERT_FALSE(s.ok()) << s.ToString(); |
182 | ||
183 | DestroyAndReopen(options); | |
184 | // Add file using file path | |
185 | s = DeprecatedAddFile({file1}); | |
186 | ASSERT_TRUE(s.ok()) << s.ToString(); | |
187 | ASSERT_EQ(db_->GetLatestSequenceNumber(), 0U); | |
188 | for (int k = 0; k < 100; k++) { | |
189 | ASSERT_EQ(Get(Key(k)), Key(k) + "_val"); | |
190 | } | |
191 | ||
192 | DestroyAndRecreateExternalSSTFilesDir(); | |
193 | } | |
194 | ||
195 | TEST_F(ExternalSSTFileBasicTest, NoCopy) { | |
196 | Options options = CurrentOptions(); | |
197 | const ImmutableCFOptions ioptions(options); | |
198 | ||
199 | SstFileWriter sst_file_writer(EnvOptions(), options); | |
200 | ||
201 | // file1.sst (0 => 99) | |
202 | std::string file1 = sst_files_dir_ + "file1.sst"; | |
203 | ASSERT_OK(sst_file_writer.Open(file1)); | |
204 | for (int k = 0; k < 100; k++) { | |
11fdf7f2 | 205 | ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val")); |
7c673cae FG |
206 | } |
207 | ExternalSstFileInfo file1_info; | |
208 | Status s = sst_file_writer.Finish(&file1_info); | |
209 | ASSERT_TRUE(s.ok()) << s.ToString(); | |
210 | ASSERT_EQ(file1_info.file_path, file1); | |
211 | ASSERT_EQ(file1_info.num_entries, 100); | |
212 | ASSERT_EQ(file1_info.smallest_key, Key(0)); | |
213 | ASSERT_EQ(file1_info.largest_key, Key(99)); | |
214 | ||
215 | // file2.sst (100 => 299) | |
216 | std::string file2 = sst_files_dir_ + "file2.sst"; | |
217 | ASSERT_OK(sst_file_writer.Open(file2)); | |
218 | for (int k = 100; k < 300; k++) { | |
11fdf7f2 | 219 | ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val")); |
7c673cae FG |
220 | } |
221 | ExternalSstFileInfo file2_info; | |
222 | s = sst_file_writer.Finish(&file2_info); | |
223 | ASSERT_TRUE(s.ok()) << s.ToString(); | |
224 | ASSERT_EQ(file2_info.file_path, file2); | |
225 | ASSERT_EQ(file2_info.num_entries, 200); | |
226 | ASSERT_EQ(file2_info.smallest_key, Key(100)); | |
227 | ASSERT_EQ(file2_info.largest_key, Key(299)); | |
228 | ||
229 | // file3.sst (110 => 124) .. overlap with file2.sst | |
230 | std::string file3 = sst_files_dir_ + "file3.sst"; | |
231 | ASSERT_OK(sst_file_writer.Open(file3)); | |
232 | for (int k = 110; k < 125; k++) { | |
11fdf7f2 | 233 | ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val_overlap")); |
7c673cae FG |
234 | } |
235 | ExternalSstFileInfo file3_info; | |
236 | s = sst_file_writer.Finish(&file3_info); | |
237 | ASSERT_TRUE(s.ok()) << s.ToString(); | |
238 | ASSERT_EQ(file3_info.file_path, file3); | |
239 | ASSERT_EQ(file3_info.num_entries, 15); | |
240 | ASSERT_EQ(file3_info.smallest_key, Key(110)); | |
241 | ASSERT_EQ(file3_info.largest_key, Key(124)); | |
11fdf7f2 | 242 | |
7c673cae FG |
243 | s = DeprecatedAddFile({file1}, true /* move file */); |
244 | ASSERT_TRUE(s.ok()) << s.ToString(); | |
245 | ASSERT_EQ(Status::NotFound(), env_->FileExists(file1)); | |
246 | ||
247 | s = DeprecatedAddFile({file2}, false /* copy file */); | |
248 | ASSERT_TRUE(s.ok()) << s.ToString(); | |
249 | ASSERT_OK(env_->FileExists(file2)); | |
250 | ||
11fdf7f2 TL |
251 | // This file has overlapping values with the existing data |
252 | s = DeprecatedAddFile({file3}, true /* move file */); | |
7c673cae FG |
253 | ASSERT_FALSE(s.ok()) << s.ToString(); |
254 | ASSERT_OK(env_->FileExists(file3)); | |
255 | ||
256 | for (int k = 0; k < 300; k++) { | |
257 | ASSERT_EQ(Get(Key(k)), Key(k) + "_val"); | |
258 | } | |
259 | } | |
260 | ||
494da23a TL |
261 | TEST_P(ExternalSSTFileBasicTest, IngestFileWithGlobalSeqnoPickedSeqno) { |
262 | bool write_global_seqno = std::get<0>(GetParam()); | |
263 | bool verify_checksums_before_ingest = std::get<1>(GetParam()); | |
7c673cae FG |
264 | do { |
265 | Options options = CurrentOptions(); | |
266 | DestroyAndReopen(options); | |
267 | std::map<std::string, std::string> true_data; | |
268 | ||
269 | int file_id = 1; | |
270 | ||
494da23a TL |
271 | ASSERT_OK(GenerateAndAddExternalFile( |
272 | options, {1, 2, 3, 4, 5, 6}, ValueType::kTypeValue, file_id++, | |
273 | write_global_seqno, verify_checksums_before_ingest, &true_data)); | |
11fdf7f2 | 274 | // File doesn't overwrite any keys, no seqno needed |
7c673cae FG |
275 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0); |
276 | ||
494da23a TL |
277 | ASSERT_OK(GenerateAndAddExternalFile( |
278 | options, {10, 11, 12, 13}, ValueType::kTypeValue, file_id++, | |
279 | write_global_seqno, verify_checksums_before_ingest, &true_data)); | |
11fdf7f2 | 280 | // File doesn't overwrite any keys, no seqno needed |
7c673cae FG |
281 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0); |
282 | ||
11fdf7f2 | 283 | ASSERT_OK(GenerateAndAddExternalFile( |
494da23a TL |
284 | options, {1, 4, 6}, ValueType::kTypeValue, file_id++, |
285 | write_global_seqno, verify_checksums_before_ingest, &true_data)); | |
11fdf7f2 | 286 | // File overwrites some keys, a seqno will be assigned |
7c673cae FG |
287 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 1); |
288 | ||
11fdf7f2 | 289 | ASSERT_OK(GenerateAndAddExternalFile( |
494da23a TL |
290 | options, {11, 15, 19}, ValueType::kTypeValue, file_id++, |
291 | write_global_seqno, verify_checksums_before_ingest, &true_data)); | |
11fdf7f2 | 292 | // File overwrites some keys, a seqno will be assigned |
7c673cae FG |
293 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 2); |
294 | ||
11fdf7f2 | 295 | ASSERT_OK(GenerateAndAddExternalFile( |
494da23a TL |
296 | options, {120, 130}, ValueType::kTypeValue, file_id++, |
297 | write_global_seqno, verify_checksums_before_ingest, &true_data)); | |
11fdf7f2 | 298 | // File doesn't overwrite any keys, no seqno needed |
7c673cae FG |
299 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 2); |
300 | ||
11fdf7f2 | 301 | ASSERT_OK(GenerateAndAddExternalFile( |
494da23a TL |
302 | options, {1, 130}, ValueType::kTypeValue, file_id++, write_global_seqno, |
303 | verify_checksums_before_ingest, &true_data)); | |
11fdf7f2 | 304 | // File overwrites some keys, a seqno will be assigned |
7c673cae FG |
305 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 3); |
306 | ||
307 | // Write some keys through normal write path | |
308 | for (int i = 0; i < 50; i++) { | |
309 | ASSERT_OK(Put(Key(i), "memtable")); | |
310 | true_data[Key(i)] = "memtable"; | |
311 | } | |
312 | SequenceNumber last_seqno = dbfull()->GetLatestSequenceNumber(); | |
313 | ||
11fdf7f2 | 314 | ASSERT_OK(GenerateAndAddExternalFile( |
494da23a TL |
315 | options, {60, 61, 62}, ValueType::kTypeValue, file_id++, |
316 | write_global_seqno, verify_checksums_before_ingest, &true_data)); | |
11fdf7f2 | 317 | // File doesn't overwrite any keys, no seqno needed |
7c673cae FG |
318 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno); |
319 | ||
11fdf7f2 | 320 | ASSERT_OK(GenerateAndAddExternalFile( |
494da23a TL |
321 | options, {40, 41, 42}, ValueType::kTypeValue, file_id++, |
322 | write_global_seqno, verify_checksums_before_ingest, &true_data)); | |
11fdf7f2 TL |
323 | // File overwrites some keys, a seqno will be assigned |
324 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 1); | |
325 | ||
326 | ASSERT_OK(GenerateAndAddExternalFile( | |
494da23a TL |
327 | options, {20, 30, 40}, ValueType::kTypeValue, file_id++, |
328 | write_global_seqno, verify_checksums_before_ingest, &true_data)); | |
11fdf7f2 TL |
329 | // File overwrites some keys, a seqno will be assigned |
330 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 2); | |
331 | ||
332 | const Snapshot* snapshot = db_->GetSnapshot(); | |
333 | ||
334 | // We will need a seqno for the file regardless if the file overwrite | |
335 | // keys in the DB or not because we have a snapshot | |
336 | ASSERT_OK(GenerateAndAddExternalFile( | |
494da23a TL |
337 | options, {1000, 1002}, ValueType::kTypeValue, file_id++, |
338 | write_global_seqno, verify_checksums_before_ingest, &true_data)); | |
11fdf7f2 TL |
339 | // A global seqno will be assigned anyway because of the snapshot |
340 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 3); | |
341 | ||
342 | ASSERT_OK(GenerateAndAddExternalFile( | |
494da23a TL |
343 | options, {2000, 3002}, ValueType::kTypeValue, file_id++, |
344 | write_global_seqno, verify_checksums_before_ingest, &true_data)); | |
11fdf7f2 TL |
345 | // A global seqno will be assigned anyway because of the snapshot |
346 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 4); | |
347 | ||
494da23a TL |
348 | ASSERT_OK(GenerateAndAddExternalFile( |
349 | options, {1, 20, 40, 100, 150}, ValueType::kTypeValue, file_id++, | |
350 | write_global_seqno, verify_checksums_before_ingest, &true_data)); | |
11fdf7f2 TL |
351 | // A global seqno will be assigned anyway because of the snapshot |
352 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 5); | |
353 | ||
354 | db_->ReleaseSnapshot(snapshot); | |
355 | ||
356 | ASSERT_OK(GenerateAndAddExternalFile( | |
494da23a TL |
357 | options, {5000, 5001}, ValueType::kTypeValue, file_id++, |
358 | write_global_seqno, verify_checksums_before_ingest, &true_data)); | |
11fdf7f2 TL |
359 | // No snapshot anymore, no need to assign a seqno |
360 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 5); | |
361 | ||
362 | size_t kcnt = 0; | |
363 | VerifyDBFromMap(true_data, &kcnt, false); | |
494da23a | 364 | } while (ChangeOptionsForFileIngestionTest()); |
11fdf7f2 TL |
365 | } |
366 | ||
494da23a TL |
367 | TEST_P(ExternalSSTFileBasicTest, IngestFileWithMultipleValueType) { |
368 | bool write_global_seqno = std::get<0>(GetParam()); | |
369 | bool verify_checksums_before_ingest = std::get<1>(GetParam()); | |
11fdf7f2 TL |
370 | do { |
371 | Options options = CurrentOptions(); | |
372 | options.merge_operator.reset(new TestPutOperator()); | |
373 | DestroyAndReopen(options); | |
374 | std::map<std::string, std::string> true_data; | |
375 | ||
376 | int file_id = 1; | |
377 | ||
494da23a TL |
378 | ASSERT_OK(GenerateAndAddExternalFile( |
379 | options, {1, 2, 3, 4, 5, 6}, ValueType::kTypeValue, file_id++, | |
380 | write_global_seqno, verify_checksums_before_ingest, &true_data)); | |
11fdf7f2 TL |
381 | // File doesn't overwrite any keys, no seqno needed |
382 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0); | |
383 | ||
494da23a TL |
384 | ASSERT_OK(GenerateAndAddExternalFile( |
385 | options, {10, 11, 12, 13}, ValueType::kTypeValue, file_id++, | |
386 | write_global_seqno, verify_checksums_before_ingest, &true_data)); | |
11fdf7f2 TL |
387 | // File doesn't overwrite any keys, no seqno needed |
388 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0); | |
389 | ||
390 | ASSERT_OK(GenerateAndAddExternalFile( | |
494da23a TL |
391 | options, {1, 4, 6}, ValueType::kTypeMerge, file_id++, |
392 | write_global_seqno, verify_checksums_before_ingest, &true_data)); | |
11fdf7f2 TL |
393 | // File overwrites some keys, a seqno will be assigned |
394 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 1); | |
395 | ||
494da23a TL |
396 | ASSERT_OK(GenerateAndAddExternalFile( |
397 | options, {11, 15, 19}, ValueType::kTypeDeletion, file_id++, | |
398 | write_global_seqno, verify_checksums_before_ingest, &true_data)); | |
11fdf7f2 TL |
399 | // File overwrites some keys, a seqno will be assigned |
400 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 2); | |
401 | ||
402 | ASSERT_OK(GenerateAndAddExternalFile( | |
494da23a TL |
403 | options, {120, 130}, ValueType::kTypeMerge, file_id++, |
404 | write_global_seqno, verify_checksums_before_ingest, &true_data)); | |
11fdf7f2 TL |
405 | // File doesn't overwrite any keys, no seqno needed |
406 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 2); | |
407 | ||
408 | ASSERT_OK(GenerateAndAddExternalFile( | |
494da23a TL |
409 | options, {1, 130}, ValueType::kTypeDeletion, file_id++, |
410 | write_global_seqno, verify_checksums_before_ingest, &true_data)); | |
11fdf7f2 TL |
411 | // File overwrites some keys, a seqno will be assigned |
412 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 3); | |
413 | ||
494da23a TL |
414 | ASSERT_OK(GenerateAndAddExternalFile( |
415 | options, {120}, {ValueType::kTypeValue}, {{120, 135}}, file_id++, | |
416 | write_global_seqno, verify_checksums_before_ingest, &true_data)); | |
11fdf7f2 TL |
417 | // File overwrites some keys, a seqno will be assigned |
418 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 4); | |
419 | ||
494da23a TL |
420 | ASSERT_OK(GenerateAndAddExternalFile( |
421 | options, {}, {}, {{110, 120}}, file_id++, write_global_seqno, | |
422 | verify_checksums_before_ingest, &true_data)); | |
11fdf7f2 TL |
423 | // The range deletion ends on a key, but it doesn't actually delete |
424 | // this key because the largest key in the range is exclusive. Still, | |
425 | // it counts as an overlap so a new seqno will be assigned. | |
426 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 5); | |
427 | ||
494da23a TL |
428 | ASSERT_OK(GenerateAndAddExternalFile( |
429 | options, {}, {}, {{100, 109}}, file_id++, write_global_seqno, | |
430 | verify_checksums_before_ingest, &true_data)); | |
11fdf7f2 TL |
431 | // File doesn't overwrite any keys, no seqno needed |
432 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 5); | |
433 | ||
434 | // Write some keys through normal write path | |
435 | for (int i = 0; i < 50; i++) { | |
436 | ASSERT_OK(Put(Key(i), "memtable")); | |
437 | true_data[Key(i)] = "memtable"; | |
438 | } | |
439 | SequenceNumber last_seqno = dbfull()->GetLatestSequenceNumber(); | |
440 | ||
441 | ASSERT_OK(GenerateAndAddExternalFile( | |
494da23a TL |
442 | options, {60, 61, 62}, ValueType::kTypeValue, file_id++, |
443 | write_global_seqno, verify_checksums_before_ingest, &true_data)); | |
11fdf7f2 TL |
444 | // File doesn't overwrite any keys, no seqno needed |
445 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno); | |
446 | ||
447 | ASSERT_OK(GenerateAndAddExternalFile( | |
494da23a TL |
448 | options, {40, 41, 42}, ValueType::kTypeMerge, file_id++, |
449 | write_global_seqno, verify_checksums_before_ingest, &true_data)); | |
11fdf7f2 | 450 | // File overwrites some keys, a seqno will be assigned |
7c673cae FG |
451 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 1); |
452 | ||
494da23a TL |
453 | ASSERT_OK(GenerateAndAddExternalFile( |
454 | options, {20, 30, 40}, ValueType::kTypeDeletion, file_id++, | |
455 | write_global_seqno, verify_checksums_before_ingest, &true_data)); | |
11fdf7f2 | 456 | // File overwrites some keys, a seqno will be assigned |
7c673cae FG |
457 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 2); |
458 | ||
459 | const Snapshot* snapshot = db_->GetSnapshot(); | |
460 | ||
461 | // We will need a seqno for the file regardless if the file overwrite | |
462 | // keys in the DB or not because we have a snapshot | |
11fdf7f2 | 463 | ASSERT_OK(GenerateAndAddExternalFile( |
494da23a TL |
464 | options, {1000, 1002}, ValueType::kTypeMerge, file_id++, |
465 | write_global_seqno, verify_checksums_before_ingest, &true_data)); | |
7c673cae FG |
466 | // A global seqno will be assigned anyway because of the snapshot |
467 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 3); | |
468 | ||
11fdf7f2 | 469 | ASSERT_OK(GenerateAndAddExternalFile( |
494da23a TL |
470 | options, {2000, 3002}, ValueType::kTypeMerge, file_id++, |
471 | write_global_seqno, verify_checksums_before_ingest, &true_data)); | |
7c673cae FG |
472 | // A global seqno will be assigned anyway because of the snapshot |
473 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 4); | |
474 | ||
494da23a TL |
475 | ASSERT_OK(GenerateAndAddExternalFile( |
476 | options, {1, 20, 40, 100, 150}, ValueType::kTypeMerge, file_id++, | |
477 | write_global_seqno, verify_checksums_before_ingest, &true_data)); | |
7c673cae FG |
478 | // A global seqno will be assigned anyway because of the snapshot |
479 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 5); | |
480 | ||
481 | db_->ReleaseSnapshot(snapshot); | |
482 | ||
11fdf7f2 | 483 | ASSERT_OK(GenerateAndAddExternalFile( |
494da23a TL |
484 | options, {5000, 5001}, ValueType::kTypeValue, file_id++, |
485 | write_global_seqno, verify_checksums_before_ingest, &true_data)); | |
11fdf7f2 TL |
486 | // No snapshot anymore, no need to assign a seqno |
487 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 5); | |
488 | ||
489 | size_t kcnt = 0; | |
490 | VerifyDBFromMap(true_data, &kcnt, false); | |
494da23a | 491 | } while (ChangeOptionsForFileIngestionTest()); |
11fdf7f2 TL |
492 | } |
493 | ||
494da23a TL |
494 | TEST_P(ExternalSSTFileBasicTest, IngestFileWithMixedValueType) { |
495 | bool write_global_seqno = std::get<0>(GetParam()); | |
496 | bool verify_checksums_before_ingest = std::get<1>(GetParam()); | |
11fdf7f2 TL |
497 | do { |
498 | Options options = CurrentOptions(); | |
499 | options.merge_operator.reset(new TestPutOperator()); | |
500 | DestroyAndReopen(options); | |
501 | std::map<std::string, std::string> true_data; | |
502 | ||
503 | int file_id = 1; | |
504 | ||
505 | ASSERT_OK(GenerateAndAddExternalFile( | |
506 | options, {1, 2, 3, 4, 5, 6}, | |
507 | {ValueType::kTypeValue, ValueType::kTypeMerge, ValueType::kTypeValue, | |
508 | ValueType::kTypeMerge, ValueType::kTypeValue, ValueType::kTypeMerge}, | |
494da23a TL |
509 | file_id++, write_global_seqno, verify_checksums_before_ingest, |
510 | &true_data)); | |
11fdf7f2 TL |
511 | // File doesn't overwrite any keys, no seqno needed |
512 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0); | |
513 | ||
514 | ASSERT_OK(GenerateAndAddExternalFile( | |
515 | options, {10, 11, 12, 13}, | |
516 | {ValueType::kTypeValue, ValueType::kTypeMerge, ValueType::kTypeValue, | |
517 | ValueType::kTypeMerge}, | |
494da23a TL |
518 | file_id++, write_global_seqno, verify_checksums_before_ingest, |
519 | &true_data)); | |
11fdf7f2 TL |
520 | // File doesn't overwrite any keys, no seqno needed |
521 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0); | |
522 | ||
523 | ASSERT_OK(GenerateAndAddExternalFile( | |
494da23a TL |
524 | options, {1, 4, 6}, |
525 | {ValueType::kTypeDeletion, ValueType::kTypeValue, | |
526 | ValueType::kTypeMerge}, | |
527 | file_id++, write_global_seqno, verify_checksums_before_ingest, | |
528 | &true_data)); | |
11fdf7f2 TL |
529 | // File overwrites some keys, a seqno will be assigned |
530 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 1); | |
531 | ||
532 | ASSERT_OK(GenerateAndAddExternalFile( | |
494da23a TL |
533 | options, {11, 15, 19}, |
534 | {ValueType::kTypeDeletion, ValueType::kTypeMerge, | |
535 | ValueType::kTypeValue}, | |
536 | file_id++, write_global_seqno, verify_checksums_before_ingest, | |
537 | &true_data)); | |
11fdf7f2 TL |
538 | // File overwrites some keys, a seqno will be assigned |
539 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 2); | |
540 | ||
541 | ASSERT_OK(GenerateAndAddExternalFile( | |
542 | options, {120, 130}, {ValueType::kTypeValue, ValueType::kTypeMerge}, | |
494da23a TL |
543 | file_id++, write_global_seqno, verify_checksums_before_ingest, |
544 | &true_data)); | |
11fdf7f2 TL |
545 | // File doesn't overwrite any keys, no seqno needed |
546 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 2); | |
547 | ||
548 | ASSERT_OK(GenerateAndAddExternalFile( | |
549 | options, {1, 130}, {ValueType::kTypeMerge, ValueType::kTypeDeletion}, | |
494da23a TL |
550 | file_id++, write_global_seqno, verify_checksums_before_ingest, |
551 | &true_data)); | |
11fdf7f2 TL |
552 | // File overwrites some keys, a seqno will be assigned |
553 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 3); | |
554 | ||
555 | ASSERT_OK(GenerateAndAddExternalFile( | |
556 | options, {150, 151, 152}, | |
557 | {ValueType::kTypeValue, ValueType::kTypeMerge, | |
558 | ValueType::kTypeDeletion}, | |
494da23a TL |
559 | {{150, 160}, {180, 190}}, file_id++, write_global_seqno, |
560 | verify_checksums_before_ingest, &true_data)); | |
11fdf7f2 TL |
561 | // File doesn't overwrite any keys, no seqno needed |
562 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 3); | |
563 | ||
564 | ASSERT_OK(GenerateAndAddExternalFile( | |
565 | options, {150, 151, 152}, | |
566 | {ValueType::kTypeValue, ValueType::kTypeMerge, ValueType::kTypeValue}, | |
494da23a TL |
567 | {{200, 250}}, file_id++, write_global_seqno, |
568 | verify_checksums_before_ingest, &true_data)); | |
11fdf7f2 TL |
569 | // File overwrites some keys, a seqno will be assigned |
570 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 4); | |
571 | ||
572 | ASSERT_OK(GenerateAndAddExternalFile( | |
573 | options, {300, 301, 302}, | |
574 | {ValueType::kTypeValue, ValueType::kTypeMerge, | |
575 | ValueType::kTypeDeletion}, | |
494da23a TL |
576 | {{1, 2}, {152, 154}}, file_id++, write_global_seqno, |
577 | verify_checksums_before_ingest, &true_data)); | |
11fdf7f2 TL |
578 | // File overwrites some keys, a seqno will be assigned |
579 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 5); | |
580 | ||
581 | // Write some keys through normal write path | |
582 | for (int i = 0; i < 50; i++) { | |
583 | ASSERT_OK(Put(Key(i), "memtable")); | |
584 | true_data[Key(i)] = "memtable"; | |
585 | } | |
586 | SequenceNumber last_seqno = dbfull()->GetLatestSequenceNumber(); | |
587 | ||
588 | ASSERT_OK(GenerateAndAddExternalFile( | |
589 | options, {60, 61, 62}, | |
590 | {ValueType::kTypeValue, ValueType::kTypeMerge, ValueType::kTypeValue}, | |
494da23a TL |
591 | file_id++, write_global_seqno, verify_checksums_before_ingest, |
592 | &true_data)); | |
11fdf7f2 TL |
593 | // File doesn't overwrite any keys, no seqno needed |
594 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno); | |
595 | ||
596 | ASSERT_OK(GenerateAndAddExternalFile( | |
597 | options, {40, 41, 42}, | |
598 | {ValueType::kTypeValue, ValueType::kTypeDeletion, | |
599 | ValueType::kTypeDeletion}, | |
494da23a TL |
600 | file_id++, write_global_seqno, verify_checksums_before_ingest, |
601 | &true_data)); | |
11fdf7f2 TL |
602 | // File overwrites some keys, a seqno will be assigned |
603 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 1); | |
604 | ||
605 | ASSERT_OK(GenerateAndAddExternalFile( | |
606 | options, {20, 30, 40}, | |
607 | {ValueType::kTypeDeletion, ValueType::kTypeDeletion, | |
608 | ValueType::kTypeDeletion}, | |
494da23a TL |
609 | file_id++, write_global_seqno, verify_checksums_before_ingest, |
610 | &true_data)); | |
11fdf7f2 TL |
611 | // File overwrites some keys, a seqno will be assigned |
612 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 2); | |
613 | ||
614 | const Snapshot* snapshot = db_->GetSnapshot(); | |
615 | ||
616 | // We will need a seqno for the file regardless if the file overwrite | |
617 | // keys in the DB or not because we have a snapshot | |
618 | ASSERT_OK(GenerateAndAddExternalFile( | |
619 | options, {1000, 1002}, {ValueType::kTypeValue, ValueType::kTypeMerge}, | |
494da23a TL |
620 | file_id++, write_global_seqno, verify_checksums_before_ingest, |
621 | &true_data)); | |
11fdf7f2 TL |
622 | // A global seqno will be assigned anyway because of the snapshot |
623 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 3); | |
624 | ||
625 | ASSERT_OK(GenerateAndAddExternalFile( | |
626 | options, {2000, 3002}, {ValueType::kTypeValue, ValueType::kTypeMerge}, | |
494da23a TL |
627 | file_id++, write_global_seqno, verify_checksums_before_ingest, |
628 | &true_data)); | |
11fdf7f2 TL |
629 | // A global seqno will be assigned anyway because of the snapshot |
630 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 4); | |
631 | ||
632 | ASSERT_OK(GenerateAndAddExternalFile( | |
633 | options, {1, 20, 40, 100, 150}, | |
634 | {ValueType::kTypeDeletion, ValueType::kTypeDeletion, | |
635 | ValueType::kTypeValue, ValueType::kTypeMerge, ValueType::kTypeMerge}, | |
494da23a TL |
636 | file_id++, write_global_seqno, verify_checksums_before_ingest, |
637 | &true_data)); | |
11fdf7f2 TL |
638 | // A global seqno will be assigned anyway because of the snapshot |
639 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 5); | |
640 | ||
641 | db_->ReleaseSnapshot(snapshot); | |
642 | ||
643 | ASSERT_OK(GenerateAndAddExternalFile( | |
644 | options, {5000, 5001}, {ValueType::kTypeValue, ValueType::kTypeMerge}, | |
494da23a TL |
645 | file_id++, write_global_seqno, verify_checksums_before_ingest, |
646 | &true_data)); | |
7c673cae FG |
647 | // No snapshot anymore, no need to assign a seqno |
648 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 5); | |
649 | ||
650 | size_t kcnt = 0; | |
651 | VerifyDBFromMap(true_data, &kcnt, false); | |
494da23a | 652 | } while (ChangeOptionsForFileIngestionTest()); |
7c673cae FG |
653 | } |
654 | ||
655 | TEST_F(ExternalSSTFileBasicTest, FadviseTrigger) { | |
656 | Options options = CurrentOptions(); | |
657 | const int kNumKeys = 10000; | |
658 | ||
659 | size_t total_fadvised_bytes = 0; | |
f67539c2 | 660 | ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack( |
11fdf7f2 | 661 | "SstFileWriter::Rep::InvalidatePageCache", [&](void* arg) { |
7c673cae FG |
662 | size_t fadvise_size = *(reinterpret_cast<size_t*>(arg)); |
663 | total_fadvised_bytes += fadvise_size; | |
664 | }); | |
f67539c2 | 665 | ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing(); |
7c673cae FG |
666 | |
667 | std::unique_ptr<SstFileWriter> sst_file_writer; | |
668 | ||
669 | std::string sst_file_path = sst_files_dir_ + "file_fadvise_disable.sst"; | |
670 | sst_file_writer.reset( | |
671 | new SstFileWriter(EnvOptions(), options, nullptr, false)); | |
672 | ASSERT_OK(sst_file_writer->Open(sst_file_path)); | |
673 | for (int i = 0; i < kNumKeys; i++) { | |
11fdf7f2 | 674 | ASSERT_OK(sst_file_writer->Put(Key(i), Key(i))); |
7c673cae FG |
675 | } |
676 | ASSERT_OK(sst_file_writer->Finish()); | |
677 | // fadvise disabled | |
678 | ASSERT_EQ(total_fadvised_bytes, 0); | |
679 | ||
7c673cae FG |
680 | sst_file_path = sst_files_dir_ + "file_fadvise_enable.sst"; |
681 | sst_file_writer.reset( | |
682 | new SstFileWriter(EnvOptions(), options, nullptr, true)); | |
683 | ASSERT_OK(sst_file_writer->Open(sst_file_path)); | |
684 | for (int i = 0; i < kNumKeys; i++) { | |
11fdf7f2 | 685 | ASSERT_OK(sst_file_writer->Put(Key(i), Key(i))); |
7c673cae FG |
686 | } |
687 | ASSERT_OK(sst_file_writer->Finish()); | |
688 | // fadvise enabled | |
689 | ASSERT_EQ(total_fadvised_bytes, sst_file_writer->FileSize()); | |
690 | ASSERT_GT(total_fadvised_bytes, 0); | |
691 | ||
f67539c2 TL |
692 | ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing(); |
693 | } | |
694 | ||
695 | TEST_F(ExternalSSTFileBasicTest, SyncFailure) { | |
696 | Options options; | |
697 | options.create_if_missing = true; | |
698 | options.env = fault_injection_test_env_.get(); | |
699 | ||
700 | std::vector<std::pair<std::string, std::string>> test_cases = { | |
701 | {"ExternalSstFileIngestionJob::BeforeSyncIngestedFile", | |
702 | "ExternalSstFileIngestionJob::AfterSyncIngestedFile"}, | |
703 | {"ExternalSstFileIngestionJob::BeforeSyncDir", | |
704 | "ExternalSstFileIngestionJob::AfterSyncDir"}, | |
705 | {"ExternalSstFileIngestionJob::BeforeSyncGlobalSeqno", | |
706 | "ExternalSstFileIngestionJob::AfterSyncGlobalSeqno"}}; | |
707 | ||
708 | for (size_t i = 0; i < test_cases.size(); i++) { | |
709 | SyncPoint::GetInstance()->SetCallBack(test_cases[i].first, [&](void*) { | |
710 | fault_injection_test_env_->SetFilesystemActive(false); | |
711 | }); | |
712 | SyncPoint::GetInstance()->SetCallBack(test_cases[i].second, [&](void*) { | |
713 | fault_injection_test_env_->SetFilesystemActive(true); | |
714 | }); | |
715 | SyncPoint::GetInstance()->EnableProcessing(); | |
716 | ||
717 | DestroyAndReopen(options); | |
718 | if (i == 2) { | |
719 | ASSERT_OK(Put("foo", "v1")); | |
720 | } | |
721 | ||
722 | Options sst_file_writer_options; | |
723 | std::unique_ptr<SstFileWriter> sst_file_writer( | |
724 | new SstFileWriter(EnvOptions(), sst_file_writer_options)); | |
725 | std::string file_name = | |
726 | sst_files_dir_ + "sync_failure_test_" + ToString(i) + ".sst"; | |
727 | ASSERT_OK(sst_file_writer->Open(file_name)); | |
728 | ASSERT_OK(sst_file_writer->Put("bar", "v2")); | |
729 | ASSERT_OK(sst_file_writer->Finish()); | |
730 | ||
731 | IngestExternalFileOptions ingest_opt; | |
732 | if (i == 0) { | |
733 | ingest_opt.move_files = true; | |
734 | } | |
735 | const Snapshot* snapshot = db_->GetSnapshot(); | |
736 | if (i == 2) { | |
737 | ingest_opt.write_global_seqno = true; | |
738 | } | |
739 | ASSERT_FALSE(db_->IngestExternalFile({file_name}, ingest_opt).ok()); | |
740 | db_->ReleaseSnapshot(snapshot); | |
741 | ||
742 | SyncPoint::GetInstance()->DisableProcessing(); | |
743 | SyncPoint::GetInstance()->ClearAllCallBacks(); | |
744 | Destroy(options); | |
745 | } | |
746 | } | |
747 | ||
748 | TEST_F(ExternalSSTFileBasicTest, VerifyChecksumReadahead) { | |
749 | Options options; | |
750 | options.create_if_missing = true; | |
751 | SpecialEnv senv(Env::Default()); | |
752 | options.env = &senv; | |
753 | DestroyAndReopen(options); | |
754 | ||
755 | Options sst_file_writer_options; | |
756 | std::unique_ptr<SstFileWriter> sst_file_writer( | |
757 | new SstFileWriter(EnvOptions(), sst_file_writer_options)); | |
758 | std::string file_name = sst_files_dir_ + "verify_checksum_readahead_test.sst"; | |
759 | ASSERT_OK(sst_file_writer->Open(file_name)); | |
760 | Random rnd(301); | |
761 | std::string value = DBTestBase::RandomString(&rnd, 4000); | |
762 | for (int i = 0; i < 5000; i++) { | |
763 | ASSERT_OK(sst_file_writer->Put(DBTestBase::Key(i), value)); | |
764 | } | |
765 | ASSERT_OK(sst_file_writer->Finish()); | |
766 | ||
767 | // Ingest it once without verifying checksums to see the baseline | |
768 | // preads. | |
769 | IngestExternalFileOptions ingest_opt; | |
770 | ingest_opt.move_files = false; | |
771 | senv.count_random_reads_ = true; | |
772 | senv.random_read_bytes_counter_ = 0; | |
773 | ASSERT_OK(db_->IngestExternalFile({file_name}, ingest_opt)); | |
774 | ||
775 | auto base_num_reads = senv.random_read_counter_.Read(); | |
776 | // Make sure the counter is enabled. | |
777 | ASSERT_GT(base_num_reads, 0); | |
778 | ||
779 | // Ingest again and observe the reads made for for readahead. | |
780 | ingest_opt.move_files = false; | |
781 | ingest_opt.verify_checksums_before_ingest = true; | |
782 | ingest_opt.verify_checksums_readahead_size = size_t{2 * 1024 * 1024}; | |
783 | ||
784 | senv.count_random_reads_ = true; | |
785 | senv.random_read_bytes_counter_ = 0; | |
786 | ASSERT_OK(db_->IngestExternalFile({file_name}, ingest_opt)); | |
787 | ||
788 | // Make sure the counter is enabled. | |
789 | ASSERT_GT(senv.random_read_counter_.Read() - base_num_reads, 0); | |
790 | ||
791 | // The SST file is about 20MB. Readahead size is 2MB. | |
792 | // Give a conservative 15 reads for metadata blocks, the number | |
793 | // of random reads should be within 20 MB / 2MB + 15 = 25. | |
794 | ASSERT_LE(senv.random_read_counter_.Read() - base_num_reads, 40); | |
795 | ||
796 | Destroy(options); | |
7c673cae FG |
797 | } |
798 | ||
494da23a | 799 | TEST_P(ExternalSSTFileBasicTest, IngestionWithRangeDeletions) { |
11fdf7f2 TL |
800 | int kNumLevels = 7; |
801 | Options options = CurrentOptions(); | |
802 | options.disable_auto_compactions = true; | |
803 | options.num_levels = kNumLevels; | |
804 | Reopen(options); | |
805 | ||
806 | std::map<std::string, std::string> true_data; | |
807 | int file_id = 1; | |
808 | // prevent range deletions from being dropped due to becoming obsolete. | |
809 | const Snapshot* snapshot = db_->GetSnapshot(); | |
810 | ||
811 | // range del [0, 50) in L6 file, [50, 100) in L0 file, [100, 150) in memtable | |
812 | for (int i = 0; i < 3; i++) { | |
813 | if (i != 0) { | |
814 | db_->Flush(FlushOptions()); | |
815 | if (i == 1) { | |
816 | MoveFilesToLevel(kNumLevels - 1); | |
817 | } | |
818 | } | |
819 | ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), | |
820 | Key(50 * i), Key(50 * (i + 1)))); | |
821 | } | |
822 | ASSERT_EQ(1, NumTableFilesAtLevel(0)); | |
823 | ASSERT_EQ(0, NumTableFilesAtLevel(kNumLevels - 2)); | |
824 | ASSERT_EQ(1, NumTableFilesAtLevel(kNumLevels - 1)); | |
825 | ||
494da23a TL |
826 | bool write_global_seqno = std::get<0>(GetParam()); |
827 | bool verify_checksums_before_ingest = std::get<1>(GetParam()); | |
11fdf7f2 TL |
828 | // overlaps with L0 file but not memtable, so flush is skipped and file is |
829 | // ingested into L0 | |
830 | SequenceNumber last_seqno = dbfull()->GetLatestSequenceNumber(); | |
831 | ASSERT_OK(GenerateAndAddExternalFile( | |
832 | options, {60, 90}, {ValueType::kTypeValue, ValueType::kTypeValue}, | |
494da23a TL |
833 | {{65, 70}, {70, 85}}, file_id++, write_global_seqno, |
834 | verify_checksums_before_ingest, &true_data)); | |
11fdf7f2 TL |
835 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), ++last_seqno); |
836 | ASSERT_EQ(2, NumTableFilesAtLevel(0)); | |
837 | ASSERT_EQ(0, NumTableFilesAtLevel(kNumLevels - 2)); | |
838 | ASSERT_EQ(1, NumTableFilesAtLevel(options.num_levels - 1)); | |
839 | ||
840 | // overlaps with L6 file but not memtable or L0 file, so flush is skipped and | |
841 | // file is ingested into L5 | |
842 | ASSERT_OK(GenerateAndAddExternalFile( | |
843 | options, {10, 40}, {ValueType::kTypeValue, ValueType::kTypeValue}, | |
494da23a TL |
844 | file_id++, write_global_seqno, verify_checksums_before_ingest, |
845 | &true_data)); | |
11fdf7f2 TL |
846 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), ++last_seqno); |
847 | ASSERT_EQ(2, NumTableFilesAtLevel(0)); | |
848 | ASSERT_EQ(1, NumTableFilesAtLevel(kNumLevels - 2)); | |
849 | ASSERT_EQ(1, NumTableFilesAtLevel(options.num_levels - 1)); | |
850 | ||
851 | // overlaps with L5 file but not memtable or L0 file, so flush is skipped and | |
852 | // file is ingested into L4 | |
494da23a TL |
853 | ASSERT_OK(GenerateAndAddExternalFile( |
854 | options, {}, {}, {{5, 15}}, file_id++, write_global_seqno, | |
855 | verify_checksums_before_ingest, &true_data)); | |
11fdf7f2 TL |
856 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), ++last_seqno); |
857 | ASSERT_EQ(2, NumTableFilesAtLevel(0)); | |
858 | ASSERT_EQ(1, NumTableFilesAtLevel(kNumLevels - 2)); | |
859 | ASSERT_EQ(1, NumTableFilesAtLevel(options.num_levels - 2)); | |
860 | ASSERT_EQ(1, NumTableFilesAtLevel(options.num_levels - 1)); | |
861 | ||
862 | // ingested file overlaps with memtable, so flush is triggered before the file | |
863 | // is ingested such that the ingested data is considered newest. So L0 file | |
864 | // count increases by two. | |
865 | ASSERT_OK(GenerateAndAddExternalFile( | |
866 | options, {100, 140}, {ValueType::kTypeValue, ValueType::kTypeValue}, | |
494da23a TL |
867 | file_id++, write_global_seqno, verify_checksums_before_ingest, |
868 | &true_data)); | |
11fdf7f2 TL |
869 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), ++last_seqno); |
870 | ASSERT_EQ(4, NumTableFilesAtLevel(0)); | |
871 | ASSERT_EQ(1, NumTableFilesAtLevel(kNumLevels - 2)); | |
872 | ASSERT_EQ(1, NumTableFilesAtLevel(options.num_levels - 1)); | |
873 | ||
874 | // snapshot unneeded now that all range deletions are persisted | |
875 | db_->ReleaseSnapshot(snapshot); | |
876 | ||
877 | // overlaps with nothing, so places at bottom level and skips incrementing | |
878 | // seqnum. | |
879 | ASSERT_OK(GenerateAndAddExternalFile( | |
880 | options, {151, 175}, {ValueType::kTypeValue, ValueType::kTypeValue}, | |
494da23a TL |
881 | {{160, 200}}, file_id++, write_global_seqno, |
882 | verify_checksums_before_ingest, &true_data)); | |
11fdf7f2 TL |
883 | ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno); |
884 | ASSERT_EQ(4, NumTableFilesAtLevel(0)); | |
885 | ASSERT_EQ(1, NumTableFilesAtLevel(kNumLevels - 2)); | |
886 | ASSERT_EQ(2, NumTableFilesAtLevel(options.num_levels - 1)); | |
887 | } | |
888 | ||
f67539c2 TL |
889 | TEST_F(ExternalSSTFileBasicTest, AdjacentRangeDeletionTombstones) { |
890 | Options options = CurrentOptions(); | |
891 | SstFileWriter sst_file_writer(EnvOptions(), options); | |
892 | ||
893 | // file8.sst (delete 300 => 400) | |
894 | std::string file8 = sst_files_dir_ + "file8.sst"; | |
895 | ASSERT_OK(sst_file_writer.Open(file8)); | |
896 | ASSERT_OK(sst_file_writer.DeleteRange(Key(300), Key(400))); | |
897 | ExternalSstFileInfo file8_info; | |
898 | Status s = sst_file_writer.Finish(&file8_info); | |
899 | ASSERT_TRUE(s.ok()) << s.ToString(); | |
900 | ASSERT_EQ(file8_info.file_path, file8); | |
901 | ASSERT_EQ(file8_info.num_entries, 0); | |
902 | ASSERT_EQ(file8_info.smallest_key, ""); | |
903 | ASSERT_EQ(file8_info.largest_key, ""); | |
904 | ASSERT_EQ(file8_info.num_range_del_entries, 1); | |
905 | ASSERT_EQ(file8_info.smallest_range_del_key, Key(300)); | |
906 | ASSERT_EQ(file8_info.largest_range_del_key, Key(400)); | |
907 | ||
908 | // file9.sst (delete 400 => 500) | |
909 | std::string file9 = sst_files_dir_ + "file9.sst"; | |
910 | ASSERT_OK(sst_file_writer.Open(file9)); | |
911 | ASSERT_OK(sst_file_writer.DeleteRange(Key(400), Key(500))); | |
912 | ExternalSstFileInfo file9_info; | |
913 | s = sst_file_writer.Finish(&file9_info); | |
914 | ASSERT_TRUE(s.ok()) << s.ToString(); | |
915 | ASSERT_EQ(file9_info.file_path, file9); | |
916 | ASSERT_EQ(file9_info.num_entries, 0); | |
917 | ASSERT_EQ(file9_info.smallest_key, ""); | |
918 | ASSERT_EQ(file9_info.largest_key, ""); | |
919 | ASSERT_EQ(file9_info.num_range_del_entries, 1); | |
920 | ASSERT_EQ(file9_info.smallest_range_del_key, Key(400)); | |
921 | ASSERT_EQ(file9_info.largest_range_del_key, Key(500)); | |
922 | ||
923 | // Range deletion tombstones are exclusive on their end key, so these SSTs | |
924 | // should not be considered as overlapping. | |
925 | s = DeprecatedAddFile({file8, file9}); | |
926 | ASSERT_TRUE(s.ok()) << s.ToString(); | |
927 | ASSERT_EQ(db_->GetLatestSequenceNumber(), 0U); | |
928 | DestroyAndRecreateExternalSSTFilesDir(); | |
929 | } | |
930 | ||
494da23a TL |
931 | TEST_P(ExternalSSTFileBasicTest, IngestFileWithBadBlockChecksum) { |
932 | bool change_checksum_called = false; | |
933 | const auto& change_checksum = [&](void* arg) { | |
934 | if (!change_checksum_called) { | |
935 | char* buf = reinterpret_cast<char*>(arg); | |
936 | assert(nullptr != buf); | |
937 | buf[0] ^= 0x1; | |
938 | change_checksum_called = true; | |
939 | } | |
940 | }; | |
941 | SyncPoint::GetInstance()->DisableProcessing(); | |
942 | SyncPoint::GetInstance()->ClearAllCallBacks(); | |
943 | SyncPoint::GetInstance()->SetCallBack( | |
944 | "BlockBasedTableBuilder::WriteRawBlock:TamperWithChecksum", | |
945 | change_checksum); | |
946 | SyncPoint::GetInstance()->EnableProcessing(); | |
947 | int file_id = 0; | |
948 | bool write_global_seqno = std::get<0>(GetParam()); | |
949 | bool verify_checksums_before_ingest = std::get<1>(GetParam()); | |
950 | do { | |
951 | Options options = CurrentOptions(); | |
952 | DestroyAndReopen(options); | |
953 | std::map<std::string, std::string> true_data; | |
954 | Status s = GenerateAndAddExternalFile( | |
955 | options, {1, 2, 3, 4, 5, 6}, ValueType::kTypeValue, file_id++, | |
956 | write_global_seqno, verify_checksums_before_ingest, &true_data); | |
957 | if (verify_checksums_before_ingest) { | |
958 | ASSERT_NOK(s); | |
959 | } else { | |
960 | ASSERT_OK(s); | |
961 | } | |
962 | change_checksum_called = false; | |
963 | } while (ChangeOptionsForFileIngestionTest()); | |
964 | } | |
965 | ||
966 | TEST_P(ExternalSSTFileBasicTest, IngestFileWithFirstByteTampered) { | |
967 | SyncPoint::GetInstance()->DisableProcessing(); | |
968 | int file_id = 0; | |
969 | EnvOptions env_options; | |
970 | do { | |
971 | Options options = CurrentOptions(); | |
972 | std::string file_path = sst_files_dir_ + ToString(file_id++); | |
973 | SstFileWriter sst_file_writer(env_options, options); | |
974 | Status s = sst_file_writer.Open(file_path); | |
975 | ASSERT_OK(s); | |
976 | for (int i = 0; i != 100; ++i) { | |
977 | std::string key = Key(i); | |
978 | std::string value = Key(i) + ToString(0); | |
979 | ASSERT_OK(sst_file_writer.Put(key, value)); | |
980 | } | |
981 | ASSERT_OK(sst_file_writer.Finish()); | |
982 | { | |
983 | // Get file size | |
984 | uint64_t file_size = 0; | |
985 | ASSERT_OK(env_->GetFileSize(file_path, &file_size)); | |
986 | ASSERT_GT(file_size, 8); | |
987 | std::unique_ptr<RandomRWFile> rwfile; | |
988 | ASSERT_OK(env_->NewRandomRWFile(file_path, &rwfile, EnvOptions())); | |
989 | // Manually corrupt the file | |
990 | // We deterministically corrupt the first byte because we currently | |
991 | // cannot choose a random offset. The reason for this limitation is that | |
992 | // we do not checksum property block at present. | |
993 | const uint64_t offset = 0; | |
994 | char scratch[8] = {0}; | |
995 | Slice buf; | |
996 | ASSERT_OK(rwfile->Read(offset, sizeof(scratch), &buf, scratch)); | |
997 | scratch[0] ^= 0xff; // flip one bit | |
998 | ASSERT_OK(rwfile->Write(offset, buf)); | |
999 | } | |
1000 | // Ingest file. | |
1001 | IngestExternalFileOptions ifo; | |
1002 | ifo.write_global_seqno = std::get<0>(GetParam()); | |
1003 | ifo.verify_checksums_before_ingest = std::get<1>(GetParam()); | |
1004 | s = db_->IngestExternalFile({file_path}, ifo); | |
1005 | if (ifo.verify_checksums_before_ingest) { | |
1006 | ASSERT_NOK(s); | |
1007 | } else { | |
1008 | ASSERT_OK(s); | |
1009 | } | |
1010 | } while (ChangeOptionsForFileIngestionTest()); | |
1011 | } | |
1012 | ||
1013 | TEST_P(ExternalSSTFileBasicTest, IngestExternalFileWithCorruptedPropsBlock) { | |
1014 | bool verify_checksums_before_ingest = std::get<1>(GetParam()); | |
1015 | if (!verify_checksums_before_ingest) { | |
1016 | return; | |
1017 | } | |
1018 | uint64_t props_block_offset = 0; | |
1019 | size_t props_block_size = 0; | |
1020 | const auto& get_props_block_offset = [&](void* arg) { | |
1021 | props_block_offset = *reinterpret_cast<uint64_t*>(arg); | |
1022 | }; | |
1023 | const auto& get_props_block_size = [&](void* arg) { | |
1024 | props_block_size = *reinterpret_cast<uint64_t*>(arg); | |
1025 | }; | |
1026 | SyncPoint::GetInstance()->DisableProcessing(); | |
1027 | SyncPoint::GetInstance()->ClearAllCallBacks(); | |
1028 | SyncPoint::GetInstance()->SetCallBack( | |
1029 | "BlockBasedTableBuilder::WritePropertiesBlock:GetPropsBlockOffset", | |
1030 | get_props_block_offset); | |
1031 | SyncPoint::GetInstance()->SetCallBack( | |
1032 | "BlockBasedTableBuilder::WritePropertiesBlock:GetPropsBlockSize", | |
1033 | get_props_block_size); | |
1034 | SyncPoint::GetInstance()->EnableProcessing(); | |
1035 | int file_id = 0; | |
1036 | Random64 rand(time(nullptr)); | |
1037 | do { | |
1038 | std::string file_path = sst_files_dir_ + ToString(file_id++); | |
1039 | Options options = CurrentOptions(); | |
1040 | SstFileWriter sst_file_writer(EnvOptions(), options); | |
1041 | Status s = sst_file_writer.Open(file_path); | |
1042 | ASSERT_OK(s); | |
1043 | for (int i = 0; i != 100; ++i) { | |
1044 | std::string key = Key(i); | |
1045 | std::string value = Key(i) + ToString(0); | |
1046 | ASSERT_OK(sst_file_writer.Put(key, value)); | |
1047 | } | |
1048 | ASSERT_OK(sst_file_writer.Finish()); | |
1049 | ||
1050 | { | |
1051 | std::unique_ptr<RandomRWFile> rwfile; | |
1052 | ASSERT_OK(env_->NewRandomRWFile(file_path, &rwfile, EnvOptions())); | |
1053 | // Manually corrupt the file | |
1054 | ASSERT_GT(props_block_size, 8); | |
1055 | uint64_t offset = | |
1056 | props_block_offset + rand.Next() % (props_block_size - 8); | |
1057 | char scratch[8] = {0}; | |
1058 | Slice buf; | |
1059 | ASSERT_OK(rwfile->Read(offset, sizeof(scratch), &buf, scratch)); | |
1060 | scratch[0] ^= 0xff; // flip one bit | |
1061 | ASSERT_OK(rwfile->Write(offset, buf)); | |
1062 | } | |
1063 | ||
1064 | // Ingest file. | |
1065 | IngestExternalFileOptions ifo; | |
1066 | ifo.write_global_seqno = std::get<0>(GetParam()); | |
1067 | ifo.verify_checksums_before_ingest = true; | |
1068 | s = db_->IngestExternalFile({file_path}, ifo); | |
1069 | ASSERT_NOK(s); | |
1070 | } while (ChangeOptionsForFileIngestionTest()); | |
1071 | } | |
1072 | ||
f67539c2 TL |
1073 | TEST_F(ExternalSSTFileBasicTest, OverlappingFiles) { |
1074 | Options options = CurrentOptions(); | |
1075 | ||
1076 | std::vector<std::string> files; | |
1077 | { | |
1078 | SstFileWriter sst_file_writer(EnvOptions(), options); | |
1079 | std::string file1 = sst_files_dir_ + "file1.sst"; | |
1080 | ASSERT_OK(sst_file_writer.Open(file1)); | |
1081 | ASSERT_OK(sst_file_writer.Put("a", "z")); | |
1082 | ASSERT_OK(sst_file_writer.Put("i", "m")); | |
1083 | ExternalSstFileInfo file1_info; | |
1084 | ASSERT_OK(sst_file_writer.Finish(&file1_info)); | |
1085 | files.push_back(std::move(file1)); | |
1086 | } | |
1087 | { | |
1088 | SstFileWriter sst_file_writer(EnvOptions(), options); | |
1089 | std::string file2 = sst_files_dir_ + "file2.sst"; | |
1090 | ASSERT_OK(sst_file_writer.Open(file2)); | |
1091 | ASSERT_OK(sst_file_writer.Put("i", "k")); | |
1092 | ExternalSstFileInfo file2_info; | |
1093 | ASSERT_OK(sst_file_writer.Finish(&file2_info)); | |
1094 | files.push_back(std::move(file2)); | |
1095 | } | |
1096 | ||
1097 | IngestExternalFileOptions ifo; | |
1098 | ASSERT_OK(db_->IngestExternalFile(files, ifo)); | |
1099 | ASSERT_EQ(Get("a"), "z"); | |
1100 | ASSERT_EQ(Get("i"), "k"); | |
1101 | ||
1102 | int total_keys = 0; | |
1103 | Iterator* iter = db_->NewIterator(ReadOptions()); | |
1104 | for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { | |
1105 | ASSERT_OK(iter->status()); | |
1106 | total_keys++; | |
1107 | } | |
1108 | delete iter; | |
1109 | ASSERT_EQ(total_keys, 2); | |
1110 | ||
1111 | ASSERT_EQ(2, NumTableFilesAtLevel(0)); | |
1112 | } | |
1113 | ||
494da23a TL |
1114 | INSTANTIATE_TEST_CASE_P(ExternalSSTFileBasicTest, ExternalSSTFileBasicTest, |
1115 | testing::Values(std::make_tuple(true, true), | |
1116 | std::make_tuple(true, false), | |
1117 | std::make_tuple(false, true), | |
1118 | std::make_tuple(false, false))); | |
1119 | ||
7c673cae FG |
1120 | #endif // ROCKSDB_LITE |
1121 | ||
f67539c2 | 1122 | } // namespace ROCKSDB_NAMESPACE |
7c673cae FG |
1123 | |
1124 | int main(int argc, char** argv) { | |
f67539c2 | 1125 | ROCKSDB_NAMESPACE::port::InstallStackTraceHandler(); |
7c673cae FG |
1126 | ::testing::InitGoogleTest(&argc, argv); |
1127 | return RUN_ALL_TESTS(); | |
1128 | } |