diff options
author | Hui Xiao <huixiao@fb.com> | 2024-05-13 13:12:06 -0700 |
---|---|---|
committer | Facebook GitHub Bot <facebook-github-bot@users.noreply.github.com> | 2024-05-13 13:12:06 -0700 |
commit | 20213d01a333aca20182e5aab6e4513537d9758c (patch) | |
tree | d2764f8d169fd057000a78c4bc3bc04f63d18089 /db/compaction | |
parent | 7747abdc151be4227a3885379403bfd7a268208b (diff) |
Fix crash in CompactFiles() of conflict range under `preclude_last_level_data_seconds > 0` (#12628)
Summary:
**Context/Summary:**
Previously `CompactFiles()` used `RangeOverlapWithCompaction()` to check for conflict when sanitizing input files while later used `FilesRangeOverlapWithCompaction()` to assert for no conflict. The latter function checks for more conflict scenarios than the former does, particularly the ones arising from `preclude_last_level_data_seconds > 0` (i.e, compaction can output to second-to-the-last level). So we ran into assertion violation in `CompactFiles()` like below
```
Assertion `output_level == 0 || !FilesRangeOverlapWithCompaction( input_files, output_level, Compaction::EvaluatePenultimateLevel(vstorage, ioptions_, start_level, output_level))' failed.
```
This PR make `CompactFiles()` used `FilesRangeOverlapWithCompaction()` and return Aborted status upon range conflict instead of crashing (during debug build) or proceed incorrectly (during non-debug build). To do so cleanly, I included a refactoring to make `FilesRangeOverlapWithCompaction()` part of `SanitizeAndConvertCompactionInputFiles()`, replacing `RangeOverlapWithCompaction()`.
Pull Request resolved: https://github.com/facebook/rocksdb/pull/12628
Test Plan: New UT crashed before the fix and return correct status after the fix.
Reviewed By: cbi42
Differential Revision: D57123536
Pulled By: hx235
fbshipit-source-id: f963a2c9e7ba1a9927a67fcc87f0dce126d3a430
Diffstat (limited to 'db/compaction')
-rw-r--r-- | db/compaction/compaction_picker.cc | 34 | ||||
-rw-r--r-- | db/compaction/compaction_picker.h | 33 |
2 files changed, 43 insertions, 24 deletions
diff --git a/db/compaction/compaction_picker.cc b/db/compaction/compaction_picker.cc index 57e4457db..e049d95b2 100644 --- a/db/compaction/compaction_picker.cc +++ b/db/compaction/compaction_picker.cc @@ -340,8 +340,8 @@ Compaction* CompactionPicker::CompactFiles( #ifndef NDEBUG assert(input_files.size()); // This compaction output should not overlap with a running compaction as - // `SanitizeCompactionInputFiles` should've checked earlier and db mutex - // shouldn't have been released since. + // `SanitizeAndConvertCompactionInputFiles` should've checked earlier and db + // mutex shouldn't have been released since. int start_level = Compaction::kInvalidLevel; for (const auto& in : input_files) { // input_files should already be sorted by level @@ -1040,19 +1040,18 @@ Status CompactionPicker::SanitizeCompactionInputFilesForAllLevels( } } } - if (RangeOverlapWithCompaction(smallestkey, largestkey, output_level)) { - return Status::Aborted( - "A running compaction is writing to the same output level in an " - "overlapping key range"); - } return Status::OK(); } -Status CompactionPicker::SanitizeCompactionInputFiles( +Status CompactionPicker::SanitizeAndConvertCompactionInputFiles( std::unordered_set<uint64_t>* input_files, - const ColumnFamilyMetaData& cf_meta, const int output_level) const { + const ColumnFamilyMetaData& cf_meta, const int output_level, + const VersionStorageInfo* vstorage, + std::vector<CompactionInputFiles>* converted_input_files) const { assert(static_cast<int>(cf_meta.levels.size()) - 1 == cf_meta.levels[cf_meta.levels.size() - 1].level); + assert(converted_input_files); + if (output_level >= static_cast<int>(cf_meta.levels.size())) { return Status::InvalidArgument( "Output level for column family " + cf_meta.name + @@ -1078,7 +1077,6 @@ Status CompactionPicker::SanitizeCompactionInputFiles( Status s = SanitizeCompactionInputFilesForAllLevels(input_files, cf_meta, output_level); - if (!s.ok()) { return s; } @@ -1119,6 +1117,22 @@ Status CompactionPicker::SanitizeCompactionInputFiles( } } + s = GetCompactionInputsFromFileNumbers(converted_input_files, input_files, + vstorage, CompactionOptions()); + if (!s.ok()) { + return s; + } + assert(converted_input_files->size() > 0); + if (output_level != 0 && + FilesRangeOverlapWithCompaction( + *converted_input_files, output_level, + Compaction::EvaluatePenultimateLevel( + vstorage, ioptions_, (*converted_input_files)[0].level, + output_level))) { + return Status::Aborted( + "A running compaction is writing to the same output level(s) in an " + "overlapping key range"); + } return Status::OK(); } diff --git a/db/compaction/compaction_picker.h b/db/compaction/compaction_picker.h index 0cec71b47..88915d459 100644 --- a/db/compaction/compaction_picker.h +++ b/db/compaction/compaction_picker.h @@ -86,16 +86,20 @@ class CompactionPicker { virtual bool NeedsCompaction(const VersionStorageInfo* vstorage) const = 0; -// Sanitize the input set of compaction input files. -// When the input parameters do not describe a valid compaction, the -// function will try to fix the input_files by adding necessary -// files. If it's not possible to conver an invalid input_files -// into a valid one by adding more files, the function will return a -// non-ok status with specific reason. -// - Status SanitizeCompactionInputFiles(std::unordered_set<uint64_t>* input_files, - const ColumnFamilyMetaData& cf_meta, - const int output_level) const; + // Sanitize the input set of compaction input files and convert it to + // `std::vector<CompactionInputFiles>` in the output parameter + // `converted_input_files`. + // When the input parameters do not describe a valid + // compaction, the function will try to fix the input_files by adding + // necessary files. If it's not possible to convert an invalid input_files + // into a valid one by adding more files, the function will return a + // non-ok status with specific reason. + // + Status SanitizeAndConvertCompactionInputFiles( + std::unordered_set<uint64_t>* input_files, + const ColumnFamilyMetaData& cf_meta, const int output_level, + const VersionStorageInfo* vstorage, + std::vector<CompactionInputFiles>* converted_input_files) const; // Free up the files that participated in a compaction // @@ -109,8 +113,8 @@ class CompactionPicker { // object. // // Caller must provide a set of input files that has been passed through - // `SanitizeCompactionInputFiles` earlier. The lock should not be released - // between that call and this one. + // `SanitizeAndConvertCompactionInputFiles` earlier. The lock should not be + // released between that call and this one. Compaction* CompactFiles(const CompactionOptions& compact_options, const std::vector<CompactionInputFiles>& input_files, int output_level, VersionStorageInfo* vstorage, @@ -120,6 +124,7 @@ class CompactionPicker { // Converts a set of compaction input file numbers into // a list of CompactionInputFiles. + // TODO(hx235): remove the unused paramter `compact_options` Status GetCompactionInputsFromFileNumbers( std::vector<CompactionInputFiles>* input_files, std::unordered_set<uint64_t>* input_set, @@ -225,8 +230,8 @@ class CompactionPicker { protected: const ImmutableOptions& ioptions_; -// A helper function to SanitizeCompactionInputFiles() that -// sanitizes "input_files" by adding necessary files. + // A helper function to SanitizeAndConvertCompactionInputFiles() that + // sanitizes "input_files" by adding necessary files. virtual Status SanitizeCompactionInputFilesForAllLevels( std::unordered_set<uint64_t>* input_files, const ColumnFamilyMetaData& cf_meta, const int output_level) const; |