1 // Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
2 // This source code is licensed under both the GPLv2 (found in the
3 // COPYING file in the root directory) and Apache 2.0 License
4 // (found in the LICENSE.Apache file in the root directory).
5
6 #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"
12 #include "test_util/fault_injection_test_env.h"
13 #include "test_util/testutil.h"
14
15 namespace ROCKSDB_NAMESPACE {
16
17 #ifndef ROCKSDB_LITE
18 class ExternalSSTFileBasicTest
19 : public DBTestBase,
20 public ::testing::WithParamInterface<std::tuple<bool, bool>> {
21 public:
ExternalSSTFileBasicTest()22 ExternalSSTFileBasicTest() : DBTestBase("/external_sst_file_basic_test") {
23 sst_files_dir_ = dbname_ + "/sst_files/";
24 fault_injection_test_env_.reset(new FaultInjectionTestEnv(Env::Default()));
25 DestroyAndRecreateExternalSSTFilesDir();
26 }
27
DestroyAndRecreateExternalSSTFilesDir()28 void DestroyAndRecreateExternalSSTFilesDir() {
29 test::DestroyDir(env_, sst_files_dir_);
30 env_->CreateDir(sst_files_dir_);
31 }
32
DeprecatedAddFile(const std::vector<std::string> & files,bool move_files=false,bool skip_snapshot_check=false)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
GenerateAndAddExternalFile(const Options options,std::vector<int> keys,const std::vector<ValueType> & value_types,std::vector<std::pair<int,int>> range_deletions,int file_id,bool write_global_seqno,bool verify_checksums_before_ingest,std::map<std::string,std::string> * true_data)44 Status GenerateAndAddExternalFile(
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,
48 bool write_global_seqno, bool verify_checksums_before_ingest,
49 std::map<std::string, std::string>* true_data) {
50 assert(value_types.size() == 1 || keys.size() == value_types.size());
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 }
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 }
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;
113 ifo.write_global_seqno = write_global_seqno;
114 ifo.verify_checksums_before_ingest = verify_checksums_before_ingest;
115 s = db_->IngestExternalFile({file_path}, ifo);
116 }
117 return s;
118 }
119
GenerateAndAddExternalFile(const Options options,std::vector<int> keys,const std::vector<ValueType> & value_types,int file_id,bool write_global_seqno,bool verify_checksums_before_ingest,std::map<std::string,std::string> * true_data)120 Status GenerateAndAddExternalFile(
121 const Options options, std::vector<int> keys,
122 const std::vector<ValueType>& value_types, int file_id,
123 bool write_global_seqno, bool verify_checksums_before_ingest,
124 std::map<std::string, std::string>* true_data) {
125 return GenerateAndAddExternalFile(
126 options, keys, value_types, {}, file_id, write_global_seqno,
127 verify_checksums_before_ingest, true_data);
128 }
129
GenerateAndAddExternalFile(const Options options,std::vector<int> keys,const ValueType value_type,int file_id,bool write_global_seqno,bool verify_checksums_before_ingest,std::map<std::string,std::string> * true_data)130 Status GenerateAndAddExternalFile(
131 const Options options, std::vector<int> keys, const ValueType value_type,
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);
137 }
138
~ExternalSSTFileBasicTest()139 ~ExternalSSTFileBasicTest() override {
140 test::DestroyDir(env_, sst_files_dir_);
141 }
142
143 protected:
144 std::string sst_files_dir_;
145 std::unique_ptr<FaultInjectionTestEnv> fault_injection_test_env_;
146 };
147
TEST_F(ExternalSSTFileBasicTest,Basic)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++) {
161 ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val"));
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));
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, "");
177 // sst_file_writer already finished, cannot add this value
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));
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
TEST_F(ExternalSSTFileBasicTest,NoCopy)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++) {
205 ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val"));
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++) {
219 ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val"));
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++) {
233 ASSERT_OK(sst_file_writer.Put(Key(k), Key(k) + "_val_overlap"));
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));
242
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
251 // This file has overlapping values with the existing data
252 s = DeprecatedAddFile({file3}, true /* move file */);
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
TEST_P(ExternalSSTFileBasicTest,IngestFileWithGlobalSeqnoPickedSeqno)261 TEST_P(ExternalSSTFileBasicTest, IngestFileWithGlobalSeqnoPickedSeqno) {
262 bool write_global_seqno = std::get<0>(GetParam());
263 bool verify_checksums_before_ingest = std::get<1>(GetParam());
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
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));
274 // File doesn't overwrite any keys, no seqno needed
275 ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0);
276
277 ASSERT_OK(GenerateAndAddExternalFile(
278 options, {10, 11, 12, 13}, ValueType::kTypeValue, file_id++,
279 write_global_seqno, verify_checksums_before_ingest, &true_data));
280 // File doesn't overwrite any keys, no seqno needed
281 ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0);
282
283 ASSERT_OK(GenerateAndAddExternalFile(
284 options, {1, 4, 6}, ValueType::kTypeValue, file_id++,
285 write_global_seqno, verify_checksums_before_ingest, &true_data));
286 // File overwrites some keys, a seqno will be assigned
287 ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 1);
288
289 ASSERT_OK(GenerateAndAddExternalFile(
290 options, {11, 15, 19}, ValueType::kTypeValue, file_id++,
291 write_global_seqno, verify_checksums_before_ingest, &true_data));
292 // File overwrites some keys, a seqno will be assigned
293 ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 2);
294
295 ASSERT_OK(GenerateAndAddExternalFile(
296 options, {120, 130}, ValueType::kTypeValue, file_id++,
297 write_global_seqno, verify_checksums_before_ingest, &true_data));
298 // File doesn't overwrite any keys, no seqno needed
299 ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 2);
300
301 ASSERT_OK(GenerateAndAddExternalFile(
302 options, {1, 130}, ValueType::kTypeValue, file_id++, write_global_seqno,
303 verify_checksums_before_ingest, &true_data));
304 // File overwrites some keys, a seqno will be assigned
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
314 ASSERT_OK(GenerateAndAddExternalFile(
315 options, {60, 61, 62}, ValueType::kTypeValue, file_id++,
316 write_global_seqno, verify_checksums_before_ingest, &true_data));
317 // File doesn't overwrite any keys, no seqno needed
318 ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno);
319
320 ASSERT_OK(GenerateAndAddExternalFile(
321 options, {40, 41, 42}, ValueType::kTypeValue, file_id++,
322 write_global_seqno, verify_checksums_before_ingest, &true_data));
323 // File overwrites some keys, a seqno will be assigned
324 ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 1);
325
326 ASSERT_OK(GenerateAndAddExternalFile(
327 options, {20, 30, 40}, ValueType::kTypeValue, file_id++,
328 write_global_seqno, verify_checksums_before_ingest, &true_data));
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(
337 options, {1000, 1002}, ValueType::kTypeValue, file_id++,
338 write_global_seqno, verify_checksums_before_ingest, &true_data));
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(
343 options, {2000, 3002}, ValueType::kTypeValue, file_id++,
344 write_global_seqno, verify_checksums_before_ingest, &true_data));
345 // A global seqno will be assigned anyway because of the snapshot
346 ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 4);
347
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));
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(
357 options, {5000, 5001}, ValueType::kTypeValue, file_id++,
358 write_global_seqno, verify_checksums_before_ingest, &true_data));
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);
364 } while (ChangeOptionsForFileIngestionTest());
365 }
366
TEST_P(ExternalSSTFileBasicTest,IngestFileWithMultipleValueType)367 TEST_P(ExternalSSTFileBasicTest, IngestFileWithMultipleValueType) {
368 bool write_global_seqno = std::get<0>(GetParam());
369 bool verify_checksums_before_ingest = std::get<1>(GetParam());
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
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));
381 // File doesn't overwrite any keys, no seqno needed
382 ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0);
383
384 ASSERT_OK(GenerateAndAddExternalFile(
385 options, {10, 11, 12, 13}, ValueType::kTypeValue, file_id++,
386 write_global_seqno, verify_checksums_before_ingest, &true_data));
387 // File doesn't overwrite any keys, no seqno needed
388 ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0);
389
390 ASSERT_OK(GenerateAndAddExternalFile(
391 options, {1, 4, 6}, ValueType::kTypeMerge, file_id++,
392 write_global_seqno, verify_checksums_before_ingest, &true_data));
393 // File overwrites some keys, a seqno will be assigned
394 ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 1);
395
396 ASSERT_OK(GenerateAndAddExternalFile(
397 options, {11, 15, 19}, ValueType::kTypeDeletion, file_id++,
398 write_global_seqno, verify_checksums_before_ingest, &true_data));
399 // File overwrites some keys, a seqno will be assigned
400 ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 2);
401
402 ASSERT_OK(GenerateAndAddExternalFile(
403 options, {120, 130}, ValueType::kTypeMerge, file_id++,
404 write_global_seqno, verify_checksums_before_ingest, &true_data));
405 // File doesn't overwrite any keys, no seqno needed
406 ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 2);
407
408 ASSERT_OK(GenerateAndAddExternalFile(
409 options, {1, 130}, ValueType::kTypeDeletion, file_id++,
410 write_global_seqno, verify_checksums_before_ingest, &true_data));
411 // File overwrites some keys, a seqno will be assigned
412 ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 3);
413
414 ASSERT_OK(GenerateAndAddExternalFile(
415 options, {120}, {ValueType::kTypeValue}, {{120, 135}}, file_id++,
416 write_global_seqno, verify_checksums_before_ingest, &true_data));
417 // File overwrites some keys, a seqno will be assigned
418 ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 4);
419
420 ASSERT_OK(GenerateAndAddExternalFile(
421 options, {}, {}, {{110, 120}}, file_id++, write_global_seqno,
422 verify_checksums_before_ingest, &true_data));
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
428 ASSERT_OK(GenerateAndAddExternalFile(
429 options, {}, {}, {{100, 109}}, file_id++, write_global_seqno,
430 verify_checksums_before_ingest, &true_data));
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(
442 options, {60, 61, 62}, ValueType::kTypeValue, file_id++,
443 write_global_seqno, verify_checksums_before_ingest, &true_data));
444 // File doesn't overwrite any keys, no seqno needed
445 ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno);
446
447 ASSERT_OK(GenerateAndAddExternalFile(
448 options, {40, 41, 42}, ValueType::kTypeMerge, file_id++,
449 write_global_seqno, verify_checksums_before_ingest, &true_data));
450 // File overwrites some keys, a seqno will be assigned
451 ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 1);
452
453 ASSERT_OK(GenerateAndAddExternalFile(
454 options, {20, 30, 40}, ValueType::kTypeDeletion, file_id++,
455 write_global_seqno, verify_checksums_before_ingest, &true_data));
456 // File overwrites some keys, a seqno will be assigned
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
463 ASSERT_OK(GenerateAndAddExternalFile(
464 options, {1000, 1002}, ValueType::kTypeMerge, file_id++,
465 write_global_seqno, verify_checksums_before_ingest, &true_data));
466 // A global seqno will be assigned anyway because of the snapshot
467 ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 3);
468
469 ASSERT_OK(GenerateAndAddExternalFile(
470 options, {2000, 3002}, ValueType::kTypeMerge, file_id++,
471 write_global_seqno, verify_checksums_before_ingest, &true_data));
472 // A global seqno will be assigned anyway because of the snapshot
473 ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno + 4);
474
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));
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
483 ASSERT_OK(GenerateAndAddExternalFile(
484 options, {5000, 5001}, ValueType::kTypeValue, file_id++,
485 write_global_seqno, verify_checksums_before_ingest, &true_data));
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);
491 } while (ChangeOptionsForFileIngestionTest());
492 }
493
TEST_P(ExternalSSTFileBasicTest,IngestFileWithMixedValueType)494 TEST_P(ExternalSSTFileBasicTest, IngestFileWithMixedValueType) {
495 bool write_global_seqno = std::get<0>(GetParam());
496 bool verify_checksums_before_ingest = std::get<1>(GetParam());
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},
509 file_id++, write_global_seqno, verify_checksums_before_ingest,
510 &true_data));
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},
518 file_id++, write_global_seqno, verify_checksums_before_ingest,
519 &true_data));
520 // File doesn't overwrite any keys, no seqno needed
521 ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 0);
522
523 ASSERT_OK(GenerateAndAddExternalFile(
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));
529 // File overwrites some keys, a seqno will be assigned
530 ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), 1);
531
532 ASSERT_OK(GenerateAndAddExternalFile(
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));
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},
543 file_id++, write_global_seqno, verify_checksums_before_ingest,
544 &true_data));
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},
550 file_id++, write_global_seqno, verify_checksums_before_ingest,
551 &true_data));
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},
559 {{150, 160}, {180, 190}}, file_id++, write_global_seqno,
560 verify_checksums_before_ingest, &true_data));
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},
567 {{200, 250}}, file_id++, write_global_seqno,
568 verify_checksums_before_ingest, &true_data));
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},
576 {{1, 2}, {152, 154}}, file_id++, write_global_seqno,
577 verify_checksums_before_ingest, &true_data));
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},
591 file_id++, write_global_seqno, verify_checksums_before_ingest,
592 &true_data));
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},
600 file_id++, write_global_seqno, verify_checksums_before_ingest,
601 &true_data));
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},
609 file_id++, write_global_seqno, verify_checksums_before_ingest,
610 &true_data));
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},
620 file_id++, write_global_seqno, verify_checksums_before_ingest,
621 &true_data));
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},
627 file_id++, write_global_seqno, verify_checksums_before_ingest,
628 &true_data));
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},
636 file_id++, write_global_seqno, verify_checksums_before_ingest,
637 &true_data));
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},
645 file_id++, write_global_seqno, verify_checksums_before_ingest,
646 &true_data));
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);
652 } while (ChangeOptionsForFileIngestionTest());
653 }
654
TEST_F(ExternalSSTFileBasicTest,FadviseTrigger)655 TEST_F(ExternalSSTFileBasicTest, FadviseTrigger) {
656 Options options = CurrentOptions();
657 const int kNumKeys = 10000;
658
659 size_t total_fadvised_bytes = 0;
660 ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
661 "SstFileWriter::Rep::InvalidatePageCache", [&](void* arg) {
662 size_t fadvise_size = *(reinterpret_cast<size_t*>(arg));
663 total_fadvised_bytes += fadvise_size;
664 });
665 ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
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++) {
674 ASSERT_OK(sst_file_writer->Put(Key(i), Key(i)));
675 }
676 ASSERT_OK(sst_file_writer->Finish());
677 // fadvise disabled
678 ASSERT_EQ(total_fadvised_bytes, 0);
679
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++) {
685 ASSERT_OK(sst_file_writer->Put(Key(i), Key(i)));
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
692 ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
693 }
694
TEST_F(ExternalSSTFileBasicTest,SyncFailure)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
TEST_F(ExternalSSTFileBasicTest,VerifyChecksumReadahead)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);
797 }
798
TEST_F(ExternalSSTFileBasicTest,IngestRangeDeletionTombstoneWithGlobalSeqno)799 TEST_F(ExternalSSTFileBasicTest, IngestRangeDeletionTombstoneWithGlobalSeqno) {
800 for (int i = 5; i < 25; i++) {
801 ASSERT_OK(db_->Put(WriteOptions(), db_->DefaultColumnFamily(), Key(i),
802 Key(i) + "_val"));
803 }
804
805 Options options = CurrentOptions();
806 options.disable_auto_compactions = true;
807 Reopen(options);
808 SstFileWriter sst_file_writer(EnvOptions(), options);
809
810 // file.sst (delete 0 => 30)
811 std::string file = sst_files_dir_ + "file.sst";
812 ASSERT_OK(sst_file_writer.Open(file));
813 ASSERT_OK(sst_file_writer.DeleteRange(Key(0), Key(30)));
814 ExternalSstFileInfo file_info;
815 ASSERT_OK(sst_file_writer.Finish(&file_info));
816 ASSERT_EQ(file_info.file_path, file);
817 ASSERT_EQ(file_info.num_entries, 0);
818 ASSERT_EQ(file_info.smallest_key, "");
819 ASSERT_EQ(file_info.largest_key, "");
820 ASSERT_EQ(file_info.num_range_del_entries, 1);
821 ASSERT_EQ(file_info.smallest_range_del_key, Key(0));
822 ASSERT_EQ(file_info.largest_range_del_key, Key(30));
823
824 IngestExternalFileOptions ifo;
825 ifo.move_files = true;
826 ifo.snapshot_consistency = true;
827 ifo.allow_global_seqno = true;
828 ifo.write_global_seqno = true;
829 ifo.verify_checksums_before_ingest = false;
830 ASSERT_OK(db_->IngestExternalFile({file}, ifo));
831
832 for (int i = 5; i < 25; i++) {
833 std::string res;
834 ASSERT_TRUE(db_->Get(ReadOptions(), Key(i), &res).IsNotFound());
835 }
836 }
837
TEST_P(ExternalSSTFileBasicTest,IngestionWithRangeDeletions)838 TEST_P(ExternalSSTFileBasicTest, IngestionWithRangeDeletions) {
839 int kNumLevels = 7;
840 Options options = CurrentOptions();
841 options.disable_auto_compactions = true;
842 options.num_levels = kNumLevels;
843 Reopen(options);
844
845 std::map<std::string, std::string> true_data;
846 int file_id = 1;
847 // prevent range deletions from being dropped due to becoming obsolete.
848 const Snapshot* snapshot = db_->GetSnapshot();
849
850 // range del [0, 50) in L6 file, [50, 100) in L0 file, [100, 150) in memtable
851 for (int i = 0; i < 3; i++) {
852 if (i != 0) {
853 db_->Flush(FlushOptions());
854 if (i == 1) {
855 MoveFilesToLevel(kNumLevels - 1);
856 }
857 }
858 ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(),
859 Key(50 * i), Key(50 * (i + 1))));
860 }
861 ASSERT_EQ(1, NumTableFilesAtLevel(0));
862 ASSERT_EQ(0, NumTableFilesAtLevel(kNumLevels - 2));
863 ASSERT_EQ(1, NumTableFilesAtLevel(kNumLevels - 1));
864
865 bool write_global_seqno = std::get<0>(GetParam());
866 bool verify_checksums_before_ingest = std::get<1>(GetParam());
867 // overlaps with L0 file but not memtable, so flush is skipped and file is
868 // ingested into L0
869 SequenceNumber last_seqno = dbfull()->GetLatestSequenceNumber();
870 ASSERT_OK(GenerateAndAddExternalFile(
871 options, {60, 90}, {ValueType::kTypeValue, ValueType::kTypeValue},
872 {{65, 70}, {70, 85}}, file_id++, write_global_seqno,
873 verify_checksums_before_ingest, &true_data));
874 ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), ++last_seqno);
875 ASSERT_EQ(2, NumTableFilesAtLevel(0));
876 ASSERT_EQ(0, NumTableFilesAtLevel(kNumLevels - 2));
877 ASSERT_EQ(1, NumTableFilesAtLevel(options.num_levels - 1));
878
879 // overlaps with L6 file but not memtable or L0 file, so flush is skipped and
880 // file is ingested into L5
881 ASSERT_OK(GenerateAndAddExternalFile(
882 options, {10, 40}, {ValueType::kTypeValue, ValueType::kTypeValue},
883 file_id++, write_global_seqno, verify_checksums_before_ingest,
884 &true_data));
885 ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), ++last_seqno);
886 ASSERT_EQ(2, NumTableFilesAtLevel(0));
887 ASSERT_EQ(1, NumTableFilesAtLevel(kNumLevels - 2));
888 ASSERT_EQ(1, NumTableFilesAtLevel(options.num_levels - 1));
889
890 // overlaps with L5 file but not memtable or L0 file, so flush is skipped and
891 // file is ingested into L4
892 ASSERT_OK(GenerateAndAddExternalFile(
893 options, {}, {}, {{5, 15}}, file_id++, write_global_seqno,
894 verify_checksums_before_ingest, &true_data));
895 ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), ++last_seqno);
896 ASSERT_EQ(2, NumTableFilesAtLevel(0));
897 ASSERT_EQ(1, NumTableFilesAtLevel(kNumLevels - 2));
898 ASSERT_EQ(1, NumTableFilesAtLevel(options.num_levels - 2));
899 ASSERT_EQ(1, NumTableFilesAtLevel(options.num_levels - 1));
900
901 // ingested file overlaps with memtable, so flush is triggered before the file
902 // is ingested such that the ingested data is considered newest. So L0 file
903 // count increases by two.
904 ASSERT_OK(GenerateAndAddExternalFile(
905 options, {100, 140}, {ValueType::kTypeValue, ValueType::kTypeValue},
906 file_id++, write_global_seqno, verify_checksums_before_ingest,
907 &true_data));
908 ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), ++last_seqno);
909 ASSERT_EQ(4, NumTableFilesAtLevel(0));
910 ASSERT_EQ(1, NumTableFilesAtLevel(kNumLevels - 2));
911 ASSERT_EQ(1, NumTableFilesAtLevel(options.num_levels - 1));
912
913 // snapshot unneeded now that all range deletions are persisted
914 db_->ReleaseSnapshot(snapshot);
915
916 // overlaps with nothing, so places at bottom level and skips incrementing
917 // seqnum.
918 ASSERT_OK(GenerateAndAddExternalFile(
919 options, {151, 175}, {ValueType::kTypeValue, ValueType::kTypeValue},
920 {{160, 200}}, file_id++, write_global_seqno,
921 verify_checksums_before_ingest, &true_data));
922 ASSERT_EQ(dbfull()->GetLatestSequenceNumber(), last_seqno);
923 ASSERT_EQ(4, NumTableFilesAtLevel(0));
924 ASSERT_EQ(1, NumTableFilesAtLevel(kNumLevels - 2));
925 ASSERT_EQ(2, NumTableFilesAtLevel(options.num_levels - 1));
926 }
927
TEST_F(ExternalSSTFileBasicTest,AdjacentRangeDeletionTombstones)928 TEST_F(ExternalSSTFileBasicTest, AdjacentRangeDeletionTombstones) {
929 Options options = CurrentOptions();
930 SstFileWriter sst_file_writer(EnvOptions(), options);
931
932 // file8.sst (delete 300 => 400)
933 std::string file8 = sst_files_dir_ + "file8.sst";
934 ASSERT_OK(sst_file_writer.Open(file8));
935 ASSERT_OK(sst_file_writer.DeleteRange(Key(300), Key(400)));
936 ExternalSstFileInfo file8_info;
937 Status s = sst_file_writer.Finish(&file8_info);
938 ASSERT_TRUE(s.ok()) << s.ToString();
939 ASSERT_EQ(file8_info.file_path, file8);
940 ASSERT_EQ(file8_info.num_entries, 0);
941 ASSERT_EQ(file8_info.smallest_key, "");
942 ASSERT_EQ(file8_info.largest_key, "");
943 ASSERT_EQ(file8_info.num_range_del_entries, 1);
944 ASSERT_EQ(file8_info.smallest_range_del_key, Key(300));
945 ASSERT_EQ(file8_info.largest_range_del_key, Key(400));
946
947 // file9.sst (delete 400 => 500)
948 std::string file9 = sst_files_dir_ + "file9.sst";
949 ASSERT_OK(sst_file_writer.Open(file9));
950 ASSERT_OK(sst_file_writer.DeleteRange(Key(400), Key(500)));
951 ExternalSstFileInfo file9_info;
952 s = sst_file_writer.Finish(&file9_info);
953 ASSERT_TRUE(s.ok()) << s.ToString();
954 ASSERT_EQ(file9_info.file_path, file9);
955 ASSERT_EQ(file9_info.num_entries, 0);
956 ASSERT_EQ(file9_info.smallest_key, "");
957 ASSERT_EQ(file9_info.largest_key, "");
958 ASSERT_EQ(file9_info.num_range_del_entries, 1);
959 ASSERT_EQ(file9_info.smallest_range_del_key, Key(400));
960 ASSERT_EQ(file9_info.largest_range_del_key, Key(500));
961
962 // Range deletion tombstones are exclusive on their end key, so these SSTs
963 // should not be considered as overlapping.
964 s = DeprecatedAddFile({file8, file9});
965 ASSERT_TRUE(s.ok()) << s.ToString();
966 ASSERT_EQ(db_->GetLatestSequenceNumber(), 0U);
967 DestroyAndRecreateExternalSSTFilesDir();
968 }
969
TEST_P(ExternalSSTFileBasicTest,IngestFileWithBadBlockChecksum)970 TEST_P(ExternalSSTFileBasicTest, IngestFileWithBadBlockChecksum) {
971 bool change_checksum_called = false;
972 const auto& change_checksum = [&](void* arg) {
973 if (!change_checksum_called) {
974 char* buf = reinterpret_cast<char*>(arg);
975 assert(nullptr != buf);
976 buf[0] ^= 0x1;
977 change_checksum_called = true;
978 }
979 };
980 SyncPoint::GetInstance()->DisableProcessing();
981 SyncPoint::GetInstance()->ClearAllCallBacks();
982 SyncPoint::GetInstance()->SetCallBack(
983 "BlockBasedTableBuilder::WriteRawBlock:TamperWithChecksum",
984 change_checksum);
985 SyncPoint::GetInstance()->EnableProcessing();
986 int file_id = 0;
987 bool write_global_seqno = std::get<0>(GetParam());
988 bool verify_checksums_before_ingest = std::get<1>(GetParam());
989 do {
990 Options options = CurrentOptions();
991 DestroyAndReopen(options);
992 std::map<std::string, std::string> true_data;
993 Status s = GenerateAndAddExternalFile(
994 options, {1, 2, 3, 4, 5, 6}, ValueType::kTypeValue, file_id++,
995 write_global_seqno, verify_checksums_before_ingest, &true_data);
996 if (verify_checksums_before_ingest) {
997 ASSERT_NOK(s);
998 } else {
999 ASSERT_OK(s);
1000 }
1001 change_checksum_called = false;
1002 } while (ChangeOptionsForFileIngestionTest());
1003 }
1004
TEST_P(ExternalSSTFileBasicTest,IngestFileWithFirstByteTampered)1005 TEST_P(ExternalSSTFileBasicTest, IngestFileWithFirstByteTampered) {
1006 SyncPoint::GetInstance()->DisableProcessing();
1007 int file_id = 0;
1008 EnvOptions env_options;
1009 do {
1010 Options options = CurrentOptions();
1011 std::string file_path = sst_files_dir_ + ToString(file_id++);
1012 SstFileWriter sst_file_writer(env_options, options);
1013 Status s = sst_file_writer.Open(file_path);
1014 ASSERT_OK(s);
1015 for (int i = 0; i != 100; ++i) {
1016 std::string key = Key(i);
1017 std::string value = Key(i) + ToString(0);
1018 ASSERT_OK(sst_file_writer.Put(key, value));
1019 }
1020 ASSERT_OK(sst_file_writer.Finish());
1021 {
1022 // Get file size
1023 uint64_t file_size = 0;
1024 ASSERT_OK(env_->GetFileSize(file_path, &file_size));
1025 ASSERT_GT(file_size, 8);
1026 std::unique_ptr<RandomRWFile> rwfile;
1027 ASSERT_OK(env_->NewRandomRWFile(file_path, &rwfile, EnvOptions()));
1028 // Manually corrupt the file
1029 // We deterministically corrupt the first byte because we currently
1030 // cannot choose a random offset. The reason for this limitation is that
1031 // we do not checksum property block at present.
1032 const uint64_t offset = 0;
1033 char scratch[8] = {0};
1034 Slice buf;
1035 ASSERT_OK(rwfile->Read(offset, sizeof(scratch), &buf, scratch));
1036 scratch[0] ^= 0xff; // flip one bit
1037 ASSERT_OK(rwfile->Write(offset, buf));
1038 }
1039 // Ingest file.
1040 IngestExternalFileOptions ifo;
1041 ifo.write_global_seqno = std::get<0>(GetParam());
1042 ifo.verify_checksums_before_ingest = std::get<1>(GetParam());
1043 s = db_->IngestExternalFile({file_path}, ifo);
1044 if (ifo.verify_checksums_before_ingest) {
1045 ASSERT_NOK(s);
1046 } else {
1047 ASSERT_OK(s);
1048 }
1049 } while (ChangeOptionsForFileIngestionTest());
1050 }
1051
TEST_P(ExternalSSTFileBasicTest,IngestExternalFileWithCorruptedPropsBlock)1052 TEST_P(ExternalSSTFileBasicTest, IngestExternalFileWithCorruptedPropsBlock) {
1053 bool verify_checksums_before_ingest = std::get<1>(GetParam());
1054 if (!verify_checksums_before_ingest) {
1055 return;
1056 }
1057 uint64_t props_block_offset = 0;
1058 size_t props_block_size = 0;
1059 const auto& get_props_block_offset = [&](void* arg) {
1060 props_block_offset = *reinterpret_cast<uint64_t*>(arg);
1061 };
1062 const auto& get_props_block_size = [&](void* arg) {
1063 props_block_size = *reinterpret_cast<uint64_t*>(arg);
1064 };
1065 SyncPoint::GetInstance()->DisableProcessing();
1066 SyncPoint::GetInstance()->ClearAllCallBacks();
1067 SyncPoint::GetInstance()->SetCallBack(
1068 "BlockBasedTableBuilder::WritePropertiesBlock:GetPropsBlockOffset",
1069 get_props_block_offset);
1070 SyncPoint::GetInstance()->SetCallBack(
1071 "BlockBasedTableBuilder::WritePropertiesBlock:GetPropsBlockSize",
1072 get_props_block_size);
1073 SyncPoint::GetInstance()->EnableProcessing();
1074 int file_id = 0;
1075 Random64 rand(time(nullptr));
1076 do {
1077 std::string file_path = sst_files_dir_ + ToString(file_id++);
1078 Options options = CurrentOptions();
1079 SstFileWriter sst_file_writer(EnvOptions(), options);
1080 Status s = sst_file_writer.Open(file_path);
1081 ASSERT_OK(s);
1082 for (int i = 0; i != 100; ++i) {
1083 std::string key = Key(i);
1084 std::string value = Key(i) + ToString(0);
1085 ASSERT_OK(sst_file_writer.Put(key, value));
1086 }
1087 ASSERT_OK(sst_file_writer.Finish());
1088
1089 {
1090 std::unique_ptr<RandomRWFile> rwfile;
1091 ASSERT_OK(env_->NewRandomRWFile(file_path, &rwfile, EnvOptions()));
1092 // Manually corrupt the file
1093 ASSERT_GT(props_block_size, 8);
1094 uint64_t offset =
1095 props_block_offset + rand.Next() % (props_block_size - 8);
1096 char scratch[8] = {0};
1097 Slice buf;
1098 ASSERT_OK(rwfile->Read(offset, sizeof(scratch), &buf, scratch));
1099 scratch[0] ^= 0xff; // flip one bit
1100 ASSERT_OK(rwfile->Write(offset, buf));
1101 }
1102
1103 // Ingest file.
1104 IngestExternalFileOptions ifo;
1105 ifo.write_global_seqno = std::get<0>(GetParam());
1106 ifo.verify_checksums_before_ingest = true;
1107 s = db_->IngestExternalFile({file_path}, ifo);
1108 ASSERT_NOK(s);
1109 } while (ChangeOptionsForFileIngestionTest());
1110 }
1111
TEST_F(ExternalSSTFileBasicTest,OverlappingFiles)1112 TEST_F(ExternalSSTFileBasicTest, OverlappingFiles) {
1113 Options options = CurrentOptions();
1114
1115 std::vector<std::string> files;
1116 {
1117 SstFileWriter sst_file_writer(EnvOptions(), options);
1118 std::string file1 = sst_files_dir_ + "file1.sst";
1119 ASSERT_OK(sst_file_writer.Open(file1));
1120 ASSERT_OK(sst_file_writer.Put("a", "z"));
1121 ASSERT_OK(sst_file_writer.Put("i", "m"));
1122 ExternalSstFileInfo file1_info;
1123 ASSERT_OK(sst_file_writer.Finish(&file1_info));
1124 files.push_back(std::move(file1));
1125 }
1126 {
1127 SstFileWriter sst_file_writer(EnvOptions(), options);
1128 std::string file2 = sst_files_dir_ + "file2.sst";
1129 ASSERT_OK(sst_file_writer.Open(file2));
1130 ASSERT_OK(sst_file_writer.Put("i", "k"));
1131 ExternalSstFileInfo file2_info;
1132 ASSERT_OK(sst_file_writer.Finish(&file2_info));
1133 files.push_back(std::move(file2));
1134 }
1135
1136 IngestExternalFileOptions ifo;
1137 ASSERT_OK(db_->IngestExternalFile(files, ifo));
1138 ASSERT_EQ(Get("a"), "z");
1139 ASSERT_EQ(Get("i"), "k");
1140
1141 int total_keys = 0;
1142 Iterator* iter = db_->NewIterator(ReadOptions());
1143 for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
1144 ASSERT_OK(iter->status());
1145 total_keys++;
1146 }
1147 delete iter;
1148 ASSERT_EQ(total_keys, 2);
1149
1150 ASSERT_EQ(2, NumTableFilesAtLevel(0));
1151 }
1152
1153 INSTANTIATE_TEST_CASE_P(ExternalSSTFileBasicTest, ExternalSSTFileBasicTest,
1154 testing::Values(std::make_tuple(true, true),
1155 std::make_tuple(true, false),
1156 std::make_tuple(false, true),
1157 std::make_tuple(false, false)));
1158
1159 #endif // ROCKSDB_LITE
1160
1161 } // namespace ROCKSDB_NAMESPACE
1162
main(int argc,char ** argv)1163 int main(int argc, char** argv) {
1164 ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
1165 ::testing::InitGoogleTest(&argc, argv);
1166 return RUN_ALL_TESTS();
1167 }
1168