/src/rocksdb/db/version_set.cc
Line | Count | Source |
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 | | // Copyright (c) 2011 The LevelDB Authors. All rights reserved. |
7 | | // Use of this source code is governed by a BSD-style license that can be |
8 | | // found in the LICENSE file. See the AUTHORS file for names of contributors. |
9 | | |
10 | | #include "db/version_set.h" |
11 | | |
12 | | #include <algorithm> |
13 | | #include <array> |
14 | | #include <cinttypes> |
15 | | #include <cstdio> |
16 | | #include <list> |
17 | | #include <map> |
18 | | #include <set> |
19 | | #include <stdexcept> |
20 | | #include <string> |
21 | | #include <type_traits> |
22 | | #include <unordered_map> |
23 | | #include <utility> |
24 | | #include <vector> |
25 | | |
26 | | #include "db/blob/blob_fetcher.h" |
27 | | #include "db/blob/blob_file_cache.h" |
28 | | #include "db/blob/blob_file_reader.h" |
29 | | #include "db/blob/blob_log_format.h" |
30 | | #include "db/blob/blob_source.h" |
31 | | #include "db/compaction/compaction.h" |
32 | | #include "db/compaction/file_pri.h" |
33 | | #include "db/dbformat.h" |
34 | | #include "db/internal_stats.h" |
35 | | #include "db/log_reader.h" |
36 | | #include "db/log_writer.h" |
37 | | #include "db/manifest_ops.h" |
38 | | #include "db/memtable.h" |
39 | | #include "db/merge_context.h" |
40 | | #include "db/merge_helper.h" |
41 | | #include "db/pinned_iterators_manager.h" |
42 | | #include "db/table_cache.h" |
43 | | #include "db/version_builder.h" |
44 | | #include "db/version_edit.h" |
45 | | #include "db/version_edit_handler.h" |
46 | | #include "db/wide/wide_columns_helper.h" |
47 | | #include "file/file_util.h" |
48 | | #include "file/filename.h" |
49 | | #include "file/line_file_reader.h" |
50 | | #include "file/random_access_file_reader.h" |
51 | | #include "file/read_write_util.h" |
52 | | #include "file/writable_file_writer.h" |
53 | | #include "logging/logging.h" |
54 | | #include "monitoring/file_read_sample.h" |
55 | | #include "monitoring/perf_context_imp.h" |
56 | | #include "monitoring/persistent_stats_history.h" |
57 | | #include "options/options_helper.h" |
58 | | #include "options/options_parser.h" |
59 | | #include "rocksdb/env.h" |
60 | | #include "rocksdb/merge_operator.h" |
61 | | #include "rocksdb/write_buffer_manager.h" |
62 | | #include "table/compaction_merging_iterator.h" |
63 | | #include "table/format.h" |
64 | | #include "table/get_context.h" |
65 | | #include "table/internal_iterator.h" |
66 | | #include "table/merging_iterator.h" |
67 | | #include "table/meta_blocks.h" |
68 | | #include "table/multiget_context.h" |
69 | | #include "table/plain/plain_table_factory.h" |
70 | | #include "table/table_reader.h" |
71 | | #include "table/unique_id_impl.h" |
72 | | #include "test_util/sync_point.h" |
73 | | #include "util/cast_util.h" |
74 | | #include "util/coding.h" |
75 | | #include "util/coro_utils.h" |
76 | | #include "util/fastrange.h" |
77 | | #include "util/hash.h" |
78 | | #include "util/stop_watch.h" |
79 | | #include "util/string_util.h" |
80 | | #include "util/user_comparator_wrapper.h" |
81 | | |
82 | | #if USE_COROUTINES |
83 | | #include "folly/coro/BlockingWait.h" |
84 | | #include "folly/coro/Collect.h" |
85 | | #endif // USE_COROUTINES |
86 | | |
87 | | namespace ROCKSDB_NAMESPACE { |
88 | | |
89 | 0 | void OptionsFileManifestState::ApplyCommit(uint64_t number) { |
90 | 0 | committed_options_file_number = number; |
91 | 0 | has_committed_options_file_number = true; |
92 | 0 | } |
93 | | |
94 | 0 | void OptionsFileManifestState::ApplyColumnFamilyEdit(const VersionEdit& edit) { |
95 | 0 | if (edit.IsColumnFamilyAdd()) { |
96 | 0 | column_family_names[edit.GetColumnFamily()] = edit.GetColumnFamilyName(); |
97 | 0 | } else if (edit.IsColumnFamilyDrop()) { |
98 | 0 | column_family_names.erase(edit.GetColumnFamily()); |
99 | 0 | } |
100 | 0 | } |
101 | | |
102 | | namespace { |
103 | | |
104 | | using ScanOptionsMap = std::unordered_map<size_t, MultiScanArgs>; |
105 | | |
106 | | // Find File in LevelFilesBrief data structure |
107 | | // Within an index range defined by left and right |
108 | | int FindFileInRange(const InternalKeyComparator& icmp, |
109 | | const LevelFilesBrief& file_level, const Slice& key, |
110 | 3.04k | uint32_t left, uint32_t right) { |
111 | 4.64k | auto cmp = [&](const FdWithKeyRange& f, const Slice& k) -> bool { |
112 | 4.64k | return icmp.InternalKeyComparator::Compare(f.largest_key, k) < 0; |
113 | 4.64k | }; |
114 | 3.04k | const auto& b = file_level.files; |
115 | 3.04k | return static_cast<int>(std::lower_bound(b + left, b + right, key, cmp) - b); |
116 | 3.04k | } |
117 | | |
118 | | InternalKey MultiScanInternalKey(const Slice& user_key, |
119 | 0 | const Comparator* user_comparator) { |
120 | 0 | const size_t timestamp_size = user_comparator->timestamp_size(); |
121 | 0 | if (timestamp_size == 0) { |
122 | 0 | return InternalKey(user_key, kMaxSequenceNumber, kValueTypeForSeek); |
123 | 0 | } |
124 | | |
125 | 0 | std::string key_with_ts; |
126 | 0 | AppendKeyWithMaxTimestamp(&key_with_ts, user_key, timestamp_size); |
127 | 0 | return InternalKey(key_with_ts, kMaxSequenceNumber, kValueTypeForSeek); |
128 | 0 | } |
129 | | |
130 | | bool MultiScanRangeOverlapsFile(const UserComparatorWrapper& user_comparator, |
131 | | const ScanOptions& scan_opts, |
132 | 0 | const FdWithKeyRange& file) { |
133 | 0 | assert(scan_opts.range.start.has_value()); |
134 | 0 | assert(scan_opts.range.limit.has_value()); |
135 | |
|
136 | 0 | if (user_comparator.CompareWithoutTimestamp( |
137 | 0 | scan_opts.range.limit.value(), /*a_has_ts=*/false, |
138 | 0 | ExtractUserKey(file.smallest_key), /*b_has_ts=*/true) <= 0) { |
139 | 0 | return false; |
140 | 0 | } |
141 | | |
142 | 0 | if (user_comparator.CompareWithoutTimestamp( |
143 | 0 | scan_opts.range.start.value(), /*a_has_ts=*/false, |
144 | 0 | ExtractUserKey(file.largest_key), /*b_has_ts=*/true) > 0) { |
145 | 0 | return false; |
146 | 0 | } |
147 | | |
148 | 0 | return true; |
149 | 0 | } |
150 | | |
151 | | void AddMultiScanFileIndex(std::vector<size_t>* file_indexes, |
152 | 0 | std::vector<char>* selected, size_t file_index) { |
153 | 0 | if ((*selected)[file_index]) { |
154 | 0 | return; |
155 | 0 | } |
156 | 0 | (*selected)[file_index] = true; |
157 | 0 | file_indexes->push_back(file_index); |
158 | 0 | } |
159 | | |
160 | | template <typename OnOverlappingFile> |
161 | | bool NotifyMultiScanOverlappingFile(OnOverlappingFile& on_overlapping_file, |
162 | | size_t file_index, |
163 | 0 | const ScanOptions& scan_range) { |
164 | | if constexpr (std::is_same_v<std::invoke_result_t<OnOverlappingFile, size_t, |
165 | | const ScanOptions&>, |
166 | 0 | bool>) { |
167 | 0 | return on_overlapping_file(file_index, scan_range); |
168 | 0 | } else { |
169 | 0 | on_overlapping_file(file_index, scan_range); |
170 | 0 | return true; |
171 | 0 | } |
172 | 0 | } Unexecuted instantiation: version_set.cc:bool rocksdb::(anonymous namespace)::NotifyMultiScanOverlappingFile<rocksdb::(anonymous namespace)::LevelIterator::Prepare(rocksdb::MultiScanArgs const*)::{lambda(unsigned long, rocksdb::ScanOptions const&)#1}>(rocksdb::(anonymous namespace)::LevelIterator::Prepare(rocksdb::MultiScanArgs const*)::{lambda(unsigned long, rocksdb::ScanOptions const&)#1}&, unsigned long, rocksdb::ScanOptions const&)Unexecuted instantiation: version_set.cc:bool rocksdb::(anonymous namespace)::NotifyMultiScanOverlappingFile<rocksdb::(anonymous namespace)::GetMultiScanOverlappingFiles(rocksdb::InternalKeyComparator const&, rocksdb::UserComparatorWrapper const&, rocksdb::LevelFilesBrief const&, int, rocksdb::MultiScanArgs const&, unsigned long)::$_0>(rocksdb::(anonymous namespace)::GetMultiScanOverlappingFiles(rocksdb::InternalKeyComparator const&, rocksdb::UserComparatorWrapper const&, rocksdb::LevelFilesBrief const&, int, rocksdb::MultiScanArgs const&, unsigned long)::$_0&, unsigned long, rocksdb::ScanOptions const&) |
173 | | |
174 | | template <typename OnOverlappingFile> |
175 | | void ForEachMultiScanOverlappingFile(const InternalKeyComparator& icmp, |
176 | | const UserComparatorWrapper& ucmp, |
177 | | const LevelFilesBrief& file_level, |
178 | | int level, const MultiScanArgs& scan_opts, |
179 | 0 | OnOverlappingFile on_overlapping_file) { |
180 | 0 | assert(scan_opts.HasBoundedScanRanges()); |
181 | 0 | if (file_level.num_files == 0) { |
182 | 0 | return; |
183 | 0 | } |
184 | | |
185 | 0 | if (level == 0) { |
186 | 0 | for (size_t i = 0; i < file_level.num_files; ++i) { |
187 | 0 | for (const ScanOptions& scan_range : scan_opts.GetScanRanges()) { |
188 | 0 | if (MultiScanRangeOverlapsFile(ucmp, scan_range, file_level.files[i])) { |
189 | 0 | if (!NotifyMultiScanOverlappingFile(on_overlapping_file, i, |
190 | 0 | scan_range)) { |
191 | 0 | return; |
192 | 0 | } |
193 | 0 | } |
194 | | |
195 | 0 | if (ucmp.CompareWithoutTimestamp( |
196 | 0 | scan_range.range.start.value(), /*a_has_ts=*/false, |
197 | 0 | ExtractUserKey(file_level.files[i].largest_key), |
198 | 0 | /*b_has_ts=*/true) > 0) { |
199 | 0 | break; |
200 | 0 | } |
201 | 0 | } |
202 | 0 | } |
203 | 0 | return; |
204 | 0 | } |
205 | | |
206 | 0 | for (const ScanOptions& scan_range : scan_opts.GetScanRanges()) { |
207 | 0 | const InternalKey start_key = MultiScanInternalKey( |
208 | 0 | scan_range.range.start.value(), ucmp.user_comparator()); |
209 | 0 | size_t first_file = FindFile(icmp, file_level, start_key.Encode()); |
210 | 0 | if (first_file >= file_level.num_files) { |
211 | 0 | continue; |
212 | 0 | } |
213 | | |
214 | 0 | const InternalKey limit_key = MultiScanInternalKey( |
215 | 0 | scan_range.range.limit.value(), ucmp.user_comparator()); |
216 | 0 | size_t last_file = FindFile(icmp, file_level, limit_key.Encode()); |
217 | 0 | if (last_file >= file_level.num_files) { |
218 | 0 | last_file = file_level.num_files - 1; |
219 | 0 | } |
220 | |
|
221 | 0 | for (size_t i = first_file; i <= last_file; ++i) { |
222 | 0 | if (MultiScanRangeOverlapsFile(ucmp, scan_range, file_level.files[i])) { |
223 | 0 | if (!NotifyMultiScanOverlappingFile(on_overlapping_file, i, |
224 | 0 | scan_range)) { |
225 | 0 | return; |
226 | 0 | } |
227 | 0 | } |
228 | 0 | } |
229 | 0 | } |
230 | 0 | } Unexecuted instantiation: version_set.cc:void rocksdb::(anonymous namespace)::ForEachMultiScanOverlappingFile<rocksdb::(anonymous namespace)::LevelIterator::Prepare(rocksdb::MultiScanArgs const*)::{lambda(unsigned long, rocksdb::ScanOptions const&)#1}>(rocksdb::InternalKeyComparator const&, rocksdb::UserComparatorWrapper const&, rocksdb::LevelFilesBrief const&, int, rocksdb::MultiScanArgs const&, rocksdb::(anonymous namespace)::LevelIterator::Prepare(rocksdb::MultiScanArgs const*)::{lambda(unsigned long, rocksdb::ScanOptions const&)#1})Unexecuted instantiation: version_set.cc:void rocksdb::(anonymous namespace)::ForEachMultiScanOverlappingFile<rocksdb::(anonymous namespace)::GetMultiScanOverlappingFiles(rocksdb::InternalKeyComparator const&, rocksdb::UserComparatorWrapper const&, rocksdb::LevelFilesBrief const&, int, rocksdb::MultiScanArgs const&, unsigned long)::$_0>(rocksdb::InternalKeyComparator const&, rocksdb::UserComparatorWrapper const&, rocksdb::LevelFilesBrief const&, int, rocksdb::MultiScanArgs const&, rocksdb::(anonymous namespace)::GetMultiScanOverlappingFiles(rocksdb::InternalKeyComparator const&, rocksdb::UserComparatorWrapper const&, rocksdb::LevelFilesBrief const&, int, rocksdb::MultiScanArgs const&, unsigned long)::$_0) |
231 | | |
232 | | std::vector<size_t> GetMultiScanOverlappingFiles( |
233 | | const InternalKeyComparator& icmp, const UserComparatorWrapper& ucmp, |
234 | | const LevelFilesBrief& file_level, int level, |
235 | | const MultiScanArgs& scan_opts, |
236 | 0 | size_t max_file_indexes = std::numeric_limits<size_t>::max()) { |
237 | 0 | std::vector<size_t> file_indexes; |
238 | 0 | if (!scan_opts.HasBoundedScanRanges() || file_level.num_files == 0) { |
239 | 0 | return file_indexes; |
240 | 0 | } |
241 | 0 | file_indexes.reserve( |
242 | 0 | std::min(static_cast<size_t>(file_level.num_files), max_file_indexes)); |
243 | |
|
244 | 0 | std::vector<char> selected(file_level.num_files, false); |
245 | 0 | ForEachMultiScanOverlappingFile( |
246 | 0 | icmp, ucmp, file_level, level, scan_opts, |
247 | 0 | [&](size_t file_index, const ScanOptions& /*scan_range*/) { |
248 | 0 | if (file_indexes.size() >= max_file_indexes) { |
249 | 0 | return false; |
250 | 0 | } |
251 | 0 | AddMultiScanFileIndex(&file_indexes, &selected, file_index); |
252 | 0 | return file_indexes.size() < max_file_indexes; |
253 | 0 | }); |
254 | |
|
255 | 0 | return file_indexes; |
256 | 0 | } |
257 | | |
258 | | void AddTableIteratorForLevel( |
259 | | ColumnFamilyData* cfd, const ReadOptions& read_options, |
260 | | const FileOptions& soptions, const MutableCFOptions& mutable_cf_options, |
261 | | MergeIteratorBuilder* merge_iter_builder, Arena* arena, |
262 | | const LevelFilesBrief& file_level, int level, size_t file_index, |
263 | | bool skip_filters, size_t max_file_size_for_l0_meta_pin, |
264 | 10.3k | bool allow_unprepared_value, SequenceNumber read_seq) { |
265 | 10.3k | std::unique_ptr<TruncatedRangeDelIterator> tombstone_iter = nullptr; |
266 | 10.3k | const auto& file = file_level.files[file_index]; |
267 | 10.3k | auto table_iter = cfd->table_cache()->NewIterator( |
268 | 10.3k | read_options, soptions, cfd->internal_comparator(), *file.file_metadata, |
269 | 10.3k | /*range_del_agg=*/nullptr, mutable_cf_options, nullptr, |
270 | 10.3k | cfd->internal_stats()->GetFileReadHist(level), |
271 | 10.3k | TableReaderCaller::kUserIterator, arena, skip_filters, level, |
272 | 10.3k | max_file_size_for_l0_meta_pin, |
273 | 10.3k | /*smallest_compaction_key=*/nullptr, |
274 | 10.3k | /*largest_compaction_key=*/nullptr, allow_unprepared_value, |
275 | 10.3k | /*seek_target=*/nullptr, /*prefix_filtered_table=*/nullptr, &read_seq, |
276 | 10.3k | &tombstone_iter, |
277 | 10.3k | /*maybe_pin_table_handle=*/true); |
278 | | #ifndef NDEBUG |
279 | | TEST_SYNC_POINT_CALLBACK("Version::AddIteratorsForLevel:AddedFile", |
280 | | file.file_metadata); |
281 | | std::pair<bool, bool> iterator_type(true /* is_block_based_table_iterator */, |
282 | | false /* is_level_iterator */); |
283 | | TEST_SYNC_POINT_CALLBACK("Version::AddIteratorsForLevel:IteratorType", |
284 | | &iterator_type); |
285 | | #endif |
286 | 10.3k | if (read_options.ignore_range_deletions) { |
287 | 0 | merge_iter_builder->AddIterator(table_iter); |
288 | 10.3k | } else { |
289 | 10.3k | merge_iter_builder->AddPointAndTombstoneIterator(table_iter, |
290 | 10.3k | std::move(tombstone_iter)); |
291 | 10.3k | } |
292 | 10.3k | } |
293 | | |
294 | | Status OverlapWithIterator(const Comparator* ucmp, |
295 | | const Slice& smallest_user_key, |
296 | | const Slice& largest_user_key, |
297 | 3.12k | InternalIterator* iter, bool* overlap) { |
298 | 3.12k | InternalKey range_start(smallest_user_key, kMaxSequenceNumber, |
299 | 3.12k | kValueTypeForSeek); |
300 | 3.12k | iter->Seek(range_start.Encode()); |
301 | 3.12k | if (!iter->status().ok()) { |
302 | 0 | return iter->status(); |
303 | 0 | } |
304 | | |
305 | 3.12k | *overlap = false; |
306 | 3.12k | if (iter->Valid()) { |
307 | 3.10k | ParsedInternalKey seek_result; |
308 | 3.10k | Status s = ParseInternalKey(iter->key(), &seek_result, |
309 | 3.10k | false /* log_err_key */); // TODO |
310 | 3.10k | if (!s.ok()) { |
311 | 0 | return s; |
312 | 0 | } |
313 | | |
314 | 3.10k | if (ucmp->CompareWithoutTimestamp(seek_result.user_key, largest_user_key) <= |
315 | 3.10k | 0) { |
316 | 3.01k | *overlap = true; |
317 | 3.01k | } |
318 | 3.10k | } |
319 | | |
320 | 3.12k | return iter->status(); |
321 | 3.12k | } |
322 | | |
323 | | // The status for a blob index whose blob file is not in `vstorage`. |
324 | | Status UnknownBlobFileStatus(const VersionStorageInfo& vstorage, |
325 | 0 | uint64_t blob_file_number) { |
326 | 0 | if (vstorage.IsDroppedBlobFile(blob_file_number)) { |
327 | 0 | return Status::Corruption("Blob file dropped by best-efforts BlobDB repair", |
328 | 0 | std::to_string(blob_file_number)); |
329 | 0 | } |
330 | 0 | return Status::Corruption("Invalid blob file number"); |
331 | 0 | } |
332 | | |
333 | | } // namespace |
334 | | |
335 | | // Class to help choose the next file to search for the particular key. |
336 | | // Searches and returns files level by level. |
337 | | // We can search level-by-level since entries never hop across |
338 | | // levels. Therefore we are guaranteed that if we find data |
339 | | // in a smaller level, later levels are irrelevant (unless we |
340 | | // are MergeInProgress). |
341 | | class FilePicker { |
342 | | public: |
343 | | FilePicker(const Slice& user_key, const Slice& ikey, |
344 | | autovector<LevelFilesBrief>* file_levels, unsigned int num_levels, |
345 | | FileIndexer* file_indexer, const Comparator* user_comparator, |
346 | | const InternalKeyComparator* internal_comparator) |
347 | 2.11k | : num_levels_(num_levels), |
348 | 2.11k | curr_level_(static_cast<unsigned int>(-1)), |
349 | 2.11k | returned_file_level_(static_cast<unsigned int>(-1)), |
350 | 2.11k | hit_file_level_(static_cast<unsigned int>(-1)), |
351 | 2.11k | search_left_bound_(0), |
352 | 2.11k | search_right_bound_(FileIndexer::kLevelMaxIndex), |
353 | 2.11k | level_files_brief_(file_levels), |
354 | 2.11k | is_hit_file_last_in_level_(false), |
355 | 2.11k | curr_file_level_(nullptr), |
356 | 2.11k | user_key_(user_key), |
357 | 2.11k | ikey_(ikey), |
358 | 2.11k | file_indexer_(file_indexer), |
359 | 2.11k | user_comparator_(user_comparator), |
360 | 2.11k | internal_comparator_(internal_comparator) { |
361 | | // Setup member variables to search first level. |
362 | 2.11k | search_ended_ = !PrepareNextLevel(); |
363 | 2.11k | if (!search_ended_) { |
364 | | // Prefetch Level 0 table data to avoid cache miss if possible. |
365 | 2.47k | for (unsigned int i = 0; i < (*level_files_brief_)[0].num_files; ++i) { |
366 | 1.46k | auto* r = (*level_files_brief_)[0].files[i].fd.pinned_reader.Get(); |
367 | 1.46k | if (r) { |
368 | 1.46k | r->Prepare(ikey); |
369 | 1.46k | } |
370 | 1.46k | } |
371 | 1.01k | } |
372 | 2.11k | } |
373 | | |
374 | 0 | int GetCurrentLevel() const { return curr_level_; } |
375 | | |
376 | 2.48k | FdWithKeyRange* GetNextFile() { |
377 | 2.90k | while (!search_ended_) { // Loops over different levels. |
378 | 1.70k | while (curr_index_in_curr_level_ < curr_file_level_->num_files) { |
379 | | // Loops over all files in current level. |
380 | 1.34k | FdWithKeyRange* f = &curr_file_level_->files[curr_index_in_curr_level_]; |
381 | 1.34k | hit_file_level_ = curr_level_; |
382 | 1.34k | is_hit_file_last_in_level_ = |
383 | 1.34k | curr_index_in_curr_level_ == curr_file_level_->num_files - 1; |
384 | 1.34k | int cmp_largest = -1; |
385 | | |
386 | | // Do key range filtering of files or/and fractional cascading if: |
387 | | // (1) not all the files are in level 0, or |
388 | | // (2) there are more than 3 current level files |
389 | | // If there are only 3 or less current level files in the system, we |
390 | | // skip the key range filtering. In this case, more likely, the system |
391 | | // is highly tuned to minimize number of tables queried by each query, |
392 | | // so it is unlikely that key range filtering is more efficient than |
393 | | // querying the files. |
394 | 1.34k | if (num_levels_ > 1 || curr_file_level_->num_files > 3) { |
395 | | // Check if key is within a file's range. If search left bound and |
396 | | // right bound point to the same find, we are sure key falls in |
397 | | // range. |
398 | 774 | assert(curr_level_ == 0 || |
399 | 774 | curr_index_in_curr_level_ == start_index_in_curr_level_ || |
400 | 774 | user_comparator_->CompareWithoutTimestamp( |
401 | 774 | user_key_, ExtractUserKey(f->smallest_key)) <= 0); |
402 | | |
403 | 774 | int cmp_smallest = user_comparator_->CompareWithoutTimestamp( |
404 | 774 | user_key_, ExtractUserKey(f->smallest_key)); |
405 | 774 | if (cmp_smallest >= 0) { |
406 | 535 | cmp_largest = user_comparator_->CompareWithoutTimestamp( |
407 | 535 | user_key_, ExtractUserKey(f->largest_key)); |
408 | 535 | } |
409 | | |
410 | | // Setup file search bound for the next level based on the |
411 | | // comparison results |
412 | 774 | if (curr_level_ > 0) { |
413 | 382 | file_indexer_->GetNextLevelIndex( |
414 | 382 | curr_level_, curr_index_in_curr_level_, cmp_smallest, |
415 | 382 | cmp_largest, &search_left_bound_, &search_right_bound_); |
416 | 382 | } |
417 | | // Key falls out of current file's range |
418 | 774 | if (cmp_smallest < 0 || cmp_largest > 0) { |
419 | 251 | if (curr_level_ == 0) { |
420 | 199 | ++curr_index_in_curr_level_; |
421 | 199 | continue; |
422 | 199 | } else { |
423 | | // Search next level. |
424 | 52 | break; |
425 | 52 | } |
426 | 251 | } |
427 | 774 | } |
428 | | |
429 | 1.09k | returned_file_level_ = curr_level_; |
430 | 1.09k | if (curr_level_ > 0 && cmp_largest < 0) { |
431 | | // No more files to search in this level. |
432 | 138 | search_ended_ = !PrepareNextLevel(); |
433 | 955 | } else { |
434 | 955 | ++curr_index_in_curr_level_; |
435 | 955 | } |
436 | 1.09k | return f; |
437 | 1.34k | } |
438 | | // Start searching next level. |
439 | 412 | search_ended_ = !PrepareNextLevel(); |
440 | 412 | } |
441 | | // Search ended. |
442 | 1.39k | return nullptr; |
443 | 2.48k | } |
444 | | |
445 | | // getter for current file level |
446 | | // for GET_HIT_L0, GET_HIT_L1 & GET_HIT_L2_AND_UP counts |
447 | 3.97k | unsigned int GetHitFileLevel() { return hit_file_level_; } |
448 | | |
449 | | // Returns true if the most recent "hit file" (i.e., one returned by |
450 | | // GetNextFile()) is at the last index in its level. |
451 | 1.09k | bool IsHitFileLastInLevel() { return is_hit_file_last_in_level_; } |
452 | | |
453 | | private: |
454 | | unsigned int num_levels_; |
455 | | unsigned int curr_level_; |
456 | | unsigned int returned_file_level_; |
457 | | unsigned int hit_file_level_; |
458 | | int32_t search_left_bound_; |
459 | | int32_t search_right_bound_; |
460 | | autovector<LevelFilesBrief>* level_files_brief_; |
461 | | bool search_ended_; |
462 | | bool is_hit_file_last_in_level_; |
463 | | LevelFilesBrief* curr_file_level_; |
464 | | unsigned int curr_index_in_curr_level_; |
465 | | unsigned int start_index_in_curr_level_; |
466 | | Slice user_key_; |
467 | | Slice ikey_; |
468 | | FileIndexer* file_indexer_; |
469 | | const Comparator* user_comparator_; |
470 | | const InternalKeyComparator* internal_comparator_; |
471 | | |
472 | | // Setup local variables to search next level. |
473 | | // Returns false if there are no more levels to search. |
474 | 2.66k | bool PrepareNextLevel() { |
475 | 2.66k | curr_level_++; |
476 | 4.88k | while (curr_level_ < num_levels_) { |
477 | 3.35k | curr_file_level_ = &(*level_files_brief_)[curr_level_]; |
478 | 3.35k | if (curr_file_level_->num_files == 0) { |
479 | | // When current level is empty, the search bound generated from upper |
480 | | // level must be [0, -1] or [0, FileIndexer::kLevelMaxIndex] if it is |
481 | | // also empty. |
482 | 2.20k | assert(search_left_bound_ == 0); |
483 | 2.20k | assert(search_right_bound_ == -1 || |
484 | 2.20k | search_right_bound_ == FileIndexer::kLevelMaxIndex); |
485 | | // Since current level is empty, it will need to search all files in |
486 | | // the next level |
487 | 2.20k | search_left_bound_ = 0; |
488 | 2.20k | search_right_bound_ = FileIndexer::kLevelMaxIndex; |
489 | 2.20k | curr_level_++; |
490 | 2.20k | continue; |
491 | 2.20k | } |
492 | | |
493 | | // Some files may overlap each other. We find |
494 | | // all files that overlap user_key and process them in order from |
495 | | // newest to oldest. In the context of merge-operator, this can occur at |
496 | | // any level. Otherwise, it only occurs at Level-0 (since Put/Deletes |
497 | | // are always compacted into a single entry). |
498 | 1.14k | int32_t start_index; |
499 | 1.14k | if (curr_level_ == 0) { |
500 | | // On Level-0, we read through all files to check for overlap. |
501 | 756 | start_index = 0; |
502 | 756 | } else { |
503 | | // On Level-n (n>=1), files are sorted. Binary search to find the |
504 | | // earliest file whose largest key >= ikey. Search left bound and |
505 | | // right bound are used to narrow the range. |
506 | 389 | if (search_left_bound_ <= search_right_bound_) { |
507 | 389 | if (search_right_bound_ == FileIndexer::kLevelMaxIndex) { |
508 | 389 | search_right_bound_ = |
509 | 389 | static_cast<int32_t>(curr_file_level_->num_files) - 1; |
510 | 389 | } |
511 | | // `search_right_bound_` is an inclusive upper-bound, but since it was |
512 | | // determined based on user key, it is still possible the lookup key |
513 | | // falls to the right of `search_right_bound_`'s corresponding file. |
514 | | // So, pass a limit one higher, which allows us to detect this case. |
515 | 389 | start_index = |
516 | 389 | FindFileInRange(*internal_comparator_, *curr_file_level_, ikey_, |
517 | 389 | static_cast<uint32_t>(search_left_bound_), |
518 | 389 | static_cast<uint32_t>(search_right_bound_) + 1); |
519 | 389 | if (start_index == search_right_bound_ + 1) { |
520 | | // `ikey_` comes after `search_right_bound_`. The lookup key does |
521 | | // not exist on this level, so let's skip this level and do a full |
522 | | // binary search on the next level. |
523 | 7 | search_left_bound_ = 0; |
524 | 7 | search_right_bound_ = FileIndexer::kLevelMaxIndex; |
525 | 7 | curr_level_++; |
526 | 7 | continue; |
527 | 7 | } |
528 | 389 | } else { |
529 | | // search_left_bound > search_right_bound, key does not exist in |
530 | | // this level. Since no comparison is done in this level, it will |
531 | | // need to search all files in the next level. |
532 | 0 | search_left_bound_ = 0; |
533 | 0 | search_right_bound_ = FileIndexer::kLevelMaxIndex; |
534 | 0 | curr_level_++; |
535 | 0 | continue; |
536 | 0 | } |
537 | 389 | } |
538 | 1.13k | start_index_in_curr_level_ = start_index; |
539 | 1.13k | curr_index_in_curr_level_ = start_index; |
540 | | |
541 | 1.13k | return true; |
542 | 1.14k | } |
543 | | // curr_level_ = num_levels_. So, no more levels to search. |
544 | 1.53k | return false; |
545 | 2.66k | } |
546 | | }; |
547 | | |
548 | | class FilePickerMultiGet { |
549 | | private: |
550 | | struct FilePickerContext; |
551 | | |
552 | | public: |
553 | | FilePickerMultiGet(MultiGetRange* range, |
554 | | autovector<LevelFilesBrief>* file_levels, |
555 | | unsigned int num_levels, FileIndexer* file_indexer, |
556 | | const Comparator* user_comparator, |
557 | | const InternalKeyComparator* internal_comparator) |
558 | 0 | : num_levels_(num_levels), |
559 | 0 | curr_level_(static_cast<unsigned int>(-1)), |
560 | 0 | returned_file_level_(static_cast<unsigned int>(-1)), |
561 | 0 | hit_file_level_(static_cast<unsigned int>(-1)), |
562 | 0 | range_(*range, range->begin(), range->end()), |
563 | 0 | maybe_repeat_key_(false), |
564 | 0 | current_level_range_(*range, range->begin(), range->end()), |
565 | 0 | current_file_range_(*range, range->begin(), range->end()), |
566 | 0 | batch_iter_(range->begin()), |
567 | 0 | batch_iter_prev_(range->begin()), |
568 | 0 | upper_key_(range->begin()), |
569 | 0 | level_files_brief_(file_levels), |
570 | 0 | is_hit_file_last_in_level_(false), |
571 | 0 | curr_file_level_(nullptr), |
572 | 0 | file_indexer_(file_indexer), |
573 | 0 | user_comparator_(user_comparator), |
574 | 0 | internal_comparator_(internal_comparator), |
575 | 0 | hit_file_(nullptr) { |
576 | 0 | for (auto iter = range_.begin(); iter != range_.end(); ++iter) { |
577 | 0 | fp_ctx_array_[iter.index()] = |
578 | 0 | FilePickerContext(0, FileIndexer::kLevelMaxIndex); |
579 | 0 | } |
580 | | |
581 | | // Setup member variables to search first level. |
582 | 0 | search_ended_ = !PrepareNextLevel(); |
583 | 0 | if (!search_ended_) { |
584 | | // REVISIT |
585 | | // Prefetch Level 0 table data to avoid cache miss if possible. |
586 | | // As of now, only PlainTableReader and CuckooTableReader do any |
587 | | // prefetching. This may not be necessary anymore once we implement |
588 | | // batching in those table readers |
589 | 0 | for (unsigned int i = 0; i < (*level_files_brief_)[0].num_files; ++i) { |
590 | 0 | auto* r = (*level_files_brief_)[0].files[i].fd.pinned_reader.Get(); |
591 | 0 | if (r) { |
592 | 0 | for (auto iter = range_.begin(); iter != range_.end(); ++iter) { |
593 | 0 | r->Prepare(iter->ikey); |
594 | 0 | } |
595 | 0 | } |
596 | 0 | } |
597 | 0 | } |
598 | 0 | } |
599 | | |
600 | | FilePickerMultiGet(MultiGetRange* range, const FilePickerMultiGet& other) |
601 | | : num_levels_(other.num_levels_), |
602 | | curr_level_(other.curr_level_), |
603 | | returned_file_level_(other.returned_file_level_), |
604 | | hit_file_level_(other.hit_file_level_), |
605 | | fp_ctx_array_(other.fp_ctx_array_), |
606 | | range_(*range, range->begin(), range->end()), |
607 | | maybe_repeat_key_(false), |
608 | | current_level_range_(*range, range->begin(), range->end()), |
609 | | current_file_range_(*range, range->begin(), range->end()), |
610 | | batch_iter_(range->begin()), |
611 | | batch_iter_prev_(range->begin()), |
612 | | upper_key_(range->begin()), |
613 | | level_files_brief_(other.level_files_brief_), |
614 | | is_hit_file_last_in_level_(false), |
615 | | curr_file_level_(other.curr_file_level_), |
616 | | file_indexer_(other.file_indexer_), |
617 | | user_comparator_(other.user_comparator_), |
618 | | internal_comparator_(other.internal_comparator_), |
619 | 0 | hit_file_(nullptr) { |
620 | 0 | PrepareNextLevelForSearch(); |
621 | 0 | } |
622 | | |
623 | 0 | int GetCurrentLevel() const { return curr_level_; } |
624 | | |
625 | 0 | void PrepareNextLevelForSearch() { search_ended_ = !PrepareNextLevel(); } |
626 | | |
627 | 0 | FdWithKeyRange* GetNextFileInLevel() { |
628 | 0 | if (batch_iter_ == current_level_range_.end() || search_ended_) { |
629 | 0 | hit_file_ = nullptr; |
630 | 0 | return nullptr; |
631 | 0 | } else { |
632 | 0 | if (maybe_repeat_key_) { |
633 | 0 | maybe_repeat_key_ = false; |
634 | | // Check if we found the final value for the last key in the |
635 | | // previous lookup range. If we did, then there's no need to look |
636 | | // any further for that key, so advance batch_iter_. Else, keep |
637 | | // batch_iter_ positioned on that key so we look it up again in |
638 | | // the next file |
639 | | // For L0, always advance the key because we will look in the next |
640 | | // file regardless for all keys not found yet |
641 | 0 | if (current_level_range_.CheckKeyDone(batch_iter_) || |
642 | 0 | curr_level_ == 0) { |
643 | 0 | batch_iter_ = upper_key_; |
644 | 0 | } |
645 | 0 | } |
646 | | // batch_iter_prev_ will become the start key for the next file |
647 | | // lookup |
648 | 0 | batch_iter_prev_ = batch_iter_; |
649 | 0 | } |
650 | | |
651 | 0 | MultiGetRange next_file_range(current_level_range_, batch_iter_prev_, |
652 | 0 | current_level_range_.end()); |
653 | 0 | size_t curr_file_index = |
654 | 0 | (batch_iter_ != current_level_range_.end()) |
655 | 0 | ? fp_ctx_array_[batch_iter_.index()].curr_index_in_curr_level |
656 | 0 | : curr_file_level_->num_files; |
657 | 0 | FdWithKeyRange* f; |
658 | 0 | bool is_last_key_in_file; |
659 | 0 | if (!GetNextFileInLevelWithKeys(&next_file_range, &curr_file_index, &f, |
660 | 0 | &is_last_key_in_file)) { |
661 | 0 | hit_file_ = nullptr; |
662 | 0 | return nullptr; |
663 | 0 | } else { |
664 | 0 | if (is_last_key_in_file) { |
665 | | // Since cmp_largest is 0, batch_iter_ still points to the last key |
666 | | // that falls in this file, instead of the next one. Increment |
667 | | // the file index for all keys between batch_iter_ and upper_key_ |
668 | 0 | auto tmp_iter = batch_iter_; |
669 | 0 | while (tmp_iter != upper_key_) { |
670 | 0 | ++(fp_ctx_array_[tmp_iter.index()].curr_index_in_curr_level); |
671 | 0 | ++tmp_iter; |
672 | 0 | } |
673 | 0 | maybe_repeat_key_ = true; |
674 | 0 | } |
675 | | // Set the range for this file |
676 | 0 | current_file_range_ = |
677 | 0 | MultiGetRange(next_file_range, batch_iter_prev_, upper_key_); |
678 | 0 | returned_file_level_ = curr_level_; |
679 | 0 | hit_file_level_ = curr_level_; |
680 | 0 | is_hit_file_last_in_level_ = |
681 | 0 | curr_file_index == curr_file_level_->num_files - 1; |
682 | 0 | hit_file_ = f; |
683 | 0 | return f; |
684 | 0 | } |
685 | 0 | } |
686 | | |
687 | | // getter for current file level |
688 | | // for GET_HIT_L0, GET_HIT_L1 & GET_HIT_L2_AND_UP counts |
689 | 0 | unsigned int GetHitFileLevel() { return hit_file_level_; } |
690 | | |
691 | 0 | FdWithKeyRange* GetHitFile() { return hit_file_; } |
692 | | |
693 | | // Returns true if the most recent "hit file" (i.e., one returned by |
694 | | // GetNextFile()) is at the last index in its level. |
695 | 0 | bool IsHitFileLastInLevel() { return is_hit_file_last_in_level_; } |
696 | | |
697 | 0 | bool KeyMaySpanNextFile() { return maybe_repeat_key_; } |
698 | | |
699 | 0 | bool IsSearchEnded() { return search_ended_; } |
700 | | |
701 | 0 | const MultiGetRange& CurrentFileRange() { return current_file_range_; } |
702 | | |
703 | 0 | bool RemainingOverlapInLevel() { |
704 | 0 | return !current_level_range_.Suffix(current_file_range_).empty(); |
705 | 0 | } |
706 | | |
707 | 0 | MultiGetRange& GetRange() { return range_; } |
708 | | |
709 | 0 | void ReplaceRange(const MultiGetRange& other) { |
710 | 0 | assert(hit_file_ == nullptr); |
711 | 0 | range_ = other; |
712 | 0 | current_level_range_ = other; |
713 | 0 | } |
714 | | |
715 | | FilePickerMultiGet(FilePickerMultiGet&& other) |
716 | | : num_levels_(other.num_levels_), |
717 | | curr_level_(other.curr_level_), |
718 | | returned_file_level_(other.returned_file_level_), |
719 | | hit_file_level_(other.hit_file_level_), |
720 | | fp_ctx_array_(std::move(other.fp_ctx_array_)), |
721 | | range_(std::move(other.range_)), |
722 | | maybe_repeat_key_(other.maybe_repeat_key_), |
723 | | current_level_range_(std::move(other.current_level_range_)), |
724 | | current_file_range_(std::move(other.current_file_range_)), |
725 | | batch_iter_(other.batch_iter_, ¤t_level_range_), |
726 | | batch_iter_prev_(other.batch_iter_prev_, ¤t_level_range_), |
727 | | upper_key_(other.upper_key_, ¤t_level_range_), |
728 | | level_files_brief_(other.level_files_brief_), |
729 | | search_ended_(other.search_ended_), |
730 | | is_hit_file_last_in_level_(other.is_hit_file_last_in_level_), |
731 | | curr_file_level_(other.curr_file_level_), |
732 | | file_indexer_(other.file_indexer_), |
733 | | user_comparator_(other.user_comparator_), |
734 | | internal_comparator_(other.internal_comparator_), |
735 | 0 | hit_file_(other.hit_file_) {} |
736 | | FilePickerMultiGet& operator=(FilePickerMultiGet&&) = delete; |
737 | | ~FilePickerMultiGet() = default; |
738 | | |
739 | | private: |
740 | | unsigned int num_levels_; |
741 | | unsigned int curr_level_; |
742 | | unsigned int returned_file_level_; |
743 | | unsigned int hit_file_level_; |
744 | | |
745 | | struct FilePickerContext { |
746 | | int32_t search_left_bound; |
747 | | int32_t search_right_bound; |
748 | | unsigned int curr_index_in_curr_level; |
749 | | unsigned int start_index_in_curr_level; |
750 | | |
751 | | FilePickerContext(int32_t left, int32_t right) |
752 | 0 | : search_left_bound(left), |
753 | 0 | search_right_bound(right), |
754 | 0 | curr_index_in_curr_level(0), |
755 | 0 | start_index_in_curr_level(0) {} |
756 | | |
757 | | FilePickerContext() = default; |
758 | | }; |
759 | | std::array<FilePickerContext, MultiGetContext::MAX_BATCH_SIZE> fp_ctx_array_; |
760 | | MultiGetRange range_; |
761 | | bool maybe_repeat_key_; |
762 | | MultiGetRange current_level_range_; |
763 | | MultiGetRange current_file_range_; |
764 | | // Iterator to iterate through the keys in a MultiGet batch, that gets reset |
765 | | // at the beginning of each level. Each call to GetNextFile() will position |
766 | | // batch_iter_ at or right after the last key that was found in the returned |
767 | | // SST file |
768 | | MultiGetRange::Iterator batch_iter_; |
769 | | // An iterator that records the previous position of batch_iter_, i.e last |
770 | | // key found in the previous SST file, in order to serve as the start of |
771 | | // the batch key range for the next SST file |
772 | | MultiGetRange::Iterator batch_iter_prev_; |
773 | | MultiGetRange::Iterator upper_key_; |
774 | | autovector<LevelFilesBrief>* level_files_brief_; |
775 | | bool search_ended_; |
776 | | bool is_hit_file_last_in_level_; |
777 | | LevelFilesBrief* curr_file_level_; |
778 | | FileIndexer* file_indexer_; |
779 | | const Comparator* user_comparator_; |
780 | | const InternalKeyComparator* internal_comparator_; |
781 | | FdWithKeyRange* hit_file_; |
782 | | |
783 | | // Iterates through files in the current level until it finds a file that |
784 | | // contains at least one key from the MultiGet batch |
785 | | bool GetNextFileInLevelWithKeys(MultiGetRange* next_file_range, |
786 | | size_t* file_index, FdWithKeyRange** fd, |
787 | 0 | bool* is_last_key_in_file) { |
788 | 0 | size_t curr_file_index = *file_index; |
789 | 0 | FdWithKeyRange* f = nullptr; |
790 | 0 | bool file_hit = false; |
791 | 0 | int cmp_largest = -1; |
792 | 0 | int cmp_smallest = -1; |
793 | 0 | if (curr_file_index >= curr_file_level_->num_files) { |
794 | | // In the unlikely case the next key is a duplicate of the current key, |
795 | | // and the current key is the last in the level and the internal key |
796 | | // was not found, we need to skip lookup for the remaining keys and |
797 | | // reset the search bounds |
798 | 0 | if (batch_iter_ != current_level_range_.end()) { |
799 | | #ifndef NDEBUG |
800 | | if (curr_level_ < num_levels_ + 1) { |
801 | | if ((*level_files_brief_)[curr_level_].num_files == 0) { |
802 | | struct FilePickerContext& fp_ctx = |
803 | | fp_ctx_array_[batch_iter_.index()]; |
804 | | |
805 | | assert(fp_ctx.search_left_bound == 0); |
806 | | assert(fp_ctx.search_right_bound == -1 || |
807 | | fp_ctx.search_right_bound == FileIndexer::kLevelMaxIndex); |
808 | | } |
809 | | } |
810 | | #endif // NDBEUG |
811 | |
|
812 | 0 | ++batch_iter_; |
813 | 0 | for (; batch_iter_ != current_level_range_.end(); ++batch_iter_) { |
814 | 0 | struct FilePickerContext& fp_ctx = fp_ctx_array_[batch_iter_.index()]; |
815 | 0 | fp_ctx.search_left_bound = 0; |
816 | 0 | fp_ctx.search_right_bound = FileIndexer::kLevelMaxIndex; |
817 | 0 | } |
818 | 0 | } |
819 | 0 | return false; |
820 | 0 | } |
821 | | // Loops over keys in the MultiGet batch until it finds a file with |
822 | | // atleast one of the keys. Then it keeps moving forward until the |
823 | | // last key in the batch that falls in that file |
824 | 0 | while (batch_iter_ != current_level_range_.end() && |
825 | 0 | (fp_ctx_array_[batch_iter_.index()].curr_index_in_curr_level == |
826 | 0 | curr_file_index || |
827 | 0 | !file_hit)) { |
828 | 0 | struct FilePickerContext& fp_ctx = fp_ctx_array_[batch_iter_.index()]; |
829 | 0 | f = &curr_file_level_->files[fp_ctx.curr_index_in_curr_level]; |
830 | 0 | Slice& user_key = batch_iter_->ukey_without_ts; |
831 | | |
832 | | // Do key range filtering of files or/and fractional cascading if: |
833 | | // (1) not all the files are in level 0, or |
834 | | // (2) there are more than 3 current level files |
835 | | // If there are only 3 or less current level files in the system, we |
836 | | // skip the key range filtering. In this case, more likely, the system |
837 | | // is highly tuned to minimize number of tables queried by each query, |
838 | | // so it is unlikely that key range filtering is more efficient than |
839 | | // querying the files. |
840 | 0 | if (num_levels_ > 1 || curr_file_level_->num_files > 3) { |
841 | | // Check if key is within a file's range. If search left bound and |
842 | | // right bound point to the same find, we are sure key falls in |
843 | | // range. |
844 | 0 | cmp_smallest = user_comparator_->CompareWithoutTimestamp( |
845 | 0 | user_key, false, ExtractUserKey(f->smallest_key), true); |
846 | |
|
847 | 0 | assert(curr_level_ == 0 || |
848 | 0 | fp_ctx.curr_index_in_curr_level == |
849 | 0 | fp_ctx.start_index_in_curr_level || |
850 | 0 | cmp_smallest <= 0); |
851 | |
|
852 | 0 | if (cmp_smallest >= 0) { |
853 | 0 | cmp_largest = user_comparator_->CompareWithoutTimestamp( |
854 | 0 | user_key, false, ExtractUserKey(f->largest_key), true); |
855 | 0 | } else { |
856 | 0 | cmp_largest = -1; |
857 | 0 | } |
858 | | |
859 | | // Setup file search bound for the next level based on the |
860 | | // comparison results |
861 | 0 | if (curr_level_ > 0) { |
862 | 0 | file_indexer_->GetNextLevelIndex( |
863 | 0 | curr_level_, fp_ctx.curr_index_in_curr_level, cmp_smallest, |
864 | 0 | cmp_largest, &fp_ctx.search_left_bound, |
865 | 0 | &fp_ctx.search_right_bound); |
866 | 0 | } |
867 | | // Key falls out of current file's range |
868 | 0 | if (cmp_smallest < 0 || cmp_largest > 0) { |
869 | 0 | next_file_range->SkipKey(batch_iter_); |
870 | 0 | } else { |
871 | 0 | file_hit = true; |
872 | 0 | } |
873 | 0 | } else { |
874 | 0 | file_hit = true; |
875 | 0 | } |
876 | 0 | if (cmp_largest == 0) { |
877 | | // cmp_largest is 0, which means the next key will not be in this |
878 | | // file, so stop looking further. However, its possible there are |
879 | | // duplicates in the batch, so find the upper bound for the batch |
880 | | // in this file (upper_key_) by skipping past the duplicates. We |
881 | | // leave batch_iter_ as is since we may have to pick up from there |
882 | | // for the next file, if this file has a merge value rather than |
883 | | // final value |
884 | 0 | upper_key_ = batch_iter_; |
885 | 0 | ++upper_key_; |
886 | 0 | while (upper_key_ != current_level_range_.end() && |
887 | 0 | user_comparator_->CompareWithoutTimestamp( |
888 | 0 | batch_iter_->ukey_without_ts, false, |
889 | 0 | upper_key_->ukey_without_ts, false) == 0) { |
890 | 0 | if (curr_level_ > 0) { |
891 | 0 | struct FilePickerContext& ctx = fp_ctx_array_[upper_key_.index()]; |
892 | 0 | file_indexer_->GetNextLevelIndex( |
893 | 0 | curr_level_, ctx.curr_index_in_curr_level, cmp_smallest, |
894 | 0 | cmp_largest, &ctx.search_left_bound, &ctx.search_right_bound); |
895 | 0 | } |
896 | 0 | ++upper_key_; |
897 | 0 | } |
898 | 0 | break; |
899 | 0 | } else { |
900 | 0 | if (curr_level_ == 0) { |
901 | | // We need to look through all files in level 0 |
902 | 0 | ++fp_ctx.curr_index_in_curr_level; |
903 | 0 | } |
904 | 0 | ++batch_iter_; |
905 | 0 | } |
906 | 0 | if (!file_hit) { |
907 | 0 | curr_file_index = |
908 | 0 | (batch_iter_ != current_level_range_.end()) |
909 | 0 | ? fp_ctx_array_[batch_iter_.index()].curr_index_in_curr_level |
910 | 0 | : curr_file_level_->num_files; |
911 | 0 | } |
912 | 0 | } |
913 | |
|
914 | 0 | *fd = f; |
915 | 0 | *file_index = curr_file_index; |
916 | 0 | *is_last_key_in_file = cmp_largest == 0; |
917 | 0 | if (!*is_last_key_in_file) { |
918 | | // If the largest key in the batch overlapping the file is not the |
919 | | // largest key in the file, upper_ley_ would not have been updated so |
920 | | // update it here |
921 | 0 | upper_key_ = batch_iter_; |
922 | 0 | } |
923 | 0 | return file_hit; |
924 | 0 | } |
925 | | |
926 | | // Setup local variables to search next level. |
927 | | // Returns false if there are no more levels to search. |
928 | 0 | bool PrepareNextLevel() { |
929 | 0 | if (curr_level_ == 0) { |
930 | 0 | MultiGetRange::Iterator mget_iter = current_level_range_.begin(); |
931 | 0 | if (fp_ctx_array_[mget_iter.index()].curr_index_in_curr_level < |
932 | 0 | curr_file_level_->num_files) { |
933 | 0 | batch_iter_prev_ = current_level_range_.begin(); |
934 | 0 | upper_key_ = batch_iter_ = current_level_range_.begin(); |
935 | 0 | return true; |
936 | 0 | } |
937 | 0 | } |
938 | | |
939 | 0 | curr_level_++; |
940 | | // Reset key range to saved value |
941 | 0 | while (curr_level_ < num_levels_) { |
942 | 0 | bool level_contains_keys = false; |
943 | 0 | curr_file_level_ = &(*level_files_brief_)[curr_level_]; |
944 | 0 | if (curr_file_level_->num_files == 0) { |
945 | | // When current level is empty, the search bound generated from upper |
946 | | // level must be [0, -1] or [0, FileIndexer::kLevelMaxIndex] if it is |
947 | | // also empty. |
948 | |
|
949 | 0 | for (auto mget_iter = current_level_range_.begin(); |
950 | 0 | mget_iter != current_level_range_.end(); ++mget_iter) { |
951 | 0 | struct FilePickerContext& fp_ctx = fp_ctx_array_[mget_iter.index()]; |
952 | |
|
953 | 0 | assert(fp_ctx.search_left_bound == 0); |
954 | 0 | assert(fp_ctx.search_right_bound == -1 || |
955 | 0 | fp_ctx.search_right_bound == FileIndexer::kLevelMaxIndex); |
956 | | // Since current level is empty, it will need to search all files in |
957 | | // the next level |
958 | 0 | fp_ctx.search_left_bound = 0; |
959 | 0 | fp_ctx.search_right_bound = FileIndexer::kLevelMaxIndex; |
960 | 0 | } |
961 | | // Skip all subsequent empty levels |
962 | 0 | do { |
963 | 0 | ++curr_level_; |
964 | 0 | } while ((curr_level_ < num_levels_) && |
965 | 0 | (*level_files_brief_)[curr_level_].num_files == 0); |
966 | 0 | continue; |
967 | 0 | } |
968 | | |
969 | | // Some files may overlap each other. We find |
970 | | // all files that overlap user_key and process them in order from |
971 | | // newest to oldest. In the context of merge-operator, this can occur at |
972 | | // any level. Otherwise, it only occurs at Level-0 (since Put/Deletes |
973 | | // are always compacted into a single entry). |
974 | 0 | int32_t start_index = -1; |
975 | 0 | current_level_range_ = |
976 | 0 | MultiGetRange(range_, range_.begin(), range_.end()); |
977 | 0 | for (auto mget_iter = current_level_range_.begin(); |
978 | 0 | mget_iter != current_level_range_.end(); ++mget_iter) { |
979 | 0 | struct FilePickerContext& fp_ctx = fp_ctx_array_[mget_iter.index()]; |
980 | 0 | if (curr_level_ == 0) { |
981 | | // On Level-0, we read through all files to check for overlap. |
982 | 0 | start_index = 0; |
983 | 0 | level_contains_keys = true; |
984 | 0 | } else { |
985 | | // On Level-n (n>=1), files are sorted. Binary search to find the |
986 | | // earliest file whose largest key >= ikey. Search left bound and |
987 | | // right bound are used to narrow the range. |
988 | 0 | if (fp_ctx.search_left_bound <= fp_ctx.search_right_bound) { |
989 | 0 | if (fp_ctx.search_right_bound == FileIndexer::kLevelMaxIndex) { |
990 | 0 | fp_ctx.search_right_bound = |
991 | 0 | static_cast<int32_t>(curr_file_level_->num_files) - 1; |
992 | 0 | } |
993 | | // `search_right_bound_` is an inclusive upper-bound, but since it |
994 | | // was determined based on user key, it is still possible the lookup |
995 | | // key falls to the right of `search_right_bound_`'s corresponding |
996 | | // file. So, pass a limit one higher, which allows us to detect this |
997 | | // case. |
998 | 0 | Slice& ikey = mget_iter->ikey; |
999 | 0 | start_index = FindFileInRange( |
1000 | 0 | *internal_comparator_, *curr_file_level_, ikey, |
1001 | 0 | static_cast<uint32_t>(fp_ctx.search_left_bound), |
1002 | 0 | static_cast<uint32_t>(fp_ctx.search_right_bound) + 1); |
1003 | 0 | if (start_index == fp_ctx.search_right_bound + 1) { |
1004 | | // `ikey_` comes after `search_right_bound_`. The lookup key does |
1005 | | // not exist on this level, so let's skip this level and do a full |
1006 | | // binary search on the next level. |
1007 | 0 | fp_ctx.search_left_bound = 0; |
1008 | 0 | fp_ctx.search_right_bound = FileIndexer::kLevelMaxIndex; |
1009 | 0 | current_level_range_.SkipKey(mget_iter); |
1010 | 0 | continue; |
1011 | 0 | } else { |
1012 | 0 | level_contains_keys = true; |
1013 | 0 | } |
1014 | 0 | } else { |
1015 | | // search_left_bound > search_right_bound, key does not exist in |
1016 | | // this level. Since no comparison is done in this level, it will |
1017 | | // need to search all files in the next level. |
1018 | 0 | fp_ctx.search_left_bound = 0; |
1019 | 0 | fp_ctx.search_right_bound = FileIndexer::kLevelMaxIndex; |
1020 | 0 | current_level_range_.SkipKey(mget_iter); |
1021 | 0 | continue; |
1022 | 0 | } |
1023 | 0 | } |
1024 | 0 | assert(start_index >= 0); |
1025 | 0 | assert(start_index < static_cast<int32_t>(curr_file_level_->num_files)); |
1026 | 0 | fp_ctx.start_index_in_curr_level = start_index; |
1027 | 0 | fp_ctx.curr_index_in_curr_level = start_index; |
1028 | 0 | } |
1029 | 0 | if (level_contains_keys) { |
1030 | 0 | batch_iter_prev_ = current_level_range_.begin(); |
1031 | 0 | upper_key_ = batch_iter_ = current_level_range_.begin(); |
1032 | 0 | return true; |
1033 | 0 | } |
1034 | 0 | curr_level_++; |
1035 | 0 | } |
1036 | | // curr_level_ = num_levels_. So, no more levels to search. |
1037 | 0 | return false; |
1038 | 0 | } |
1039 | | }; |
1040 | | |
1041 | 303k | VersionStorageInfo::~VersionStorageInfo() { delete[] files_; } |
1042 | | |
1043 | 303k | Version::~Version() { |
1044 | 303k | assert(refs_ == 0); |
1045 | | |
1046 | | // Remove from linked list |
1047 | 303k | prev_->next_ = next_; |
1048 | 303k | next_->prev_ = prev_; |
1049 | | |
1050 | | // Drop references to files |
1051 | 1.81M | for (int level = 0; level < storage_info_.num_levels_; level++) { |
1052 | 1.68M | for (size_t i = 0; i < storage_info_.files_[level].size(); i++) { |
1053 | 173k | FileMetaData* f = storage_info_.files_[level][i]; |
1054 | 173k | assert(f->refs > 0); |
1055 | 173k | f->refs--; |
1056 | 173k | if (f->refs <= 0) { |
1057 | 93.9k | assert(cfd_ != nullptr); |
1058 | | // When not in the process of closing the DB, we'll have a superversion |
1059 | | // to get current mutable options from |
1060 | 93.9k | auto* sv = cfd_->GetSuperVersion(); |
1061 | 93.9k | uint32_t path_id = f->fd.GetPathId(); |
1062 | 93.9k | assert(path_id < cfd_->ioptions().cf_paths.size()); |
1063 | 93.9k | vset_->obsolete_files_.emplace_back( |
1064 | 93.9k | f, cfd_->ioptions().cf_paths[path_id].path, |
1065 | 93.9k | sv ? sv->mutable_cf_options.uncache_aggressiveness : 0, |
1066 | 93.9k | cfd_->GetFileMetadataCacheReservationManager()); |
1067 | 93.9k | } |
1068 | 173k | } |
1069 | 1.51M | } |
1070 | 303k | } |
1071 | | |
1072 | | int FindFile(const InternalKeyComparator& icmp, |
1073 | 2.65k | const LevelFilesBrief& file_level, const Slice& key) { |
1074 | 2.65k | return FindFileInRange(icmp, file_level, key, 0, |
1075 | 2.65k | static_cast<uint32_t>(file_level.num_files)); |
1076 | 2.65k | } |
1077 | | |
1078 | | void DoGenerateLevelFilesBrief(LevelFilesBrief* file_level, |
1079 | | const std::vector<FileMetaData*>& files, |
1080 | 223k | Arena* arena) { |
1081 | 223k | assert(file_level); |
1082 | 223k | assert(arena); |
1083 | | |
1084 | 223k | size_t num = files.size(); |
1085 | 223k | file_level->num_files = num; |
1086 | 223k | char* mem = arena->AllocateAligned(num * sizeof(FdWithKeyRange)); |
1087 | 223k | file_level->files = new (mem) FdWithKeyRange[num]; |
1088 | | |
1089 | 414k | for (size_t i = 0; i < num; i++) { |
1090 | 190k | Slice smallest_key = files[i]->smallest.Encode(); |
1091 | 190k | Slice largest_key = files[i]->largest.Encode(); |
1092 | | |
1093 | | // Copy key slice to sequential memory |
1094 | 190k | size_t smallest_size = smallest_key.size(); |
1095 | 190k | size_t largest_size = largest_key.size(); |
1096 | 190k | mem = arena->AllocateAligned(smallest_size + largest_size); |
1097 | 190k | memcpy(mem, smallest_key.data(), smallest_size); |
1098 | 190k | memcpy(mem + smallest_size, largest_key.data(), largest_size); |
1099 | | |
1100 | 190k | FdWithKeyRange& f = file_level->files[i]; |
1101 | 190k | f.fd = files[i]->fd; |
1102 | 190k | f.file_metadata = files[i]; |
1103 | 190k | f.smallest_key = Slice(mem, smallest_size); |
1104 | 190k | f.largest_key = Slice(mem + smallest_size, largest_size); |
1105 | 190k | } |
1106 | 223k | } |
1107 | | |
1108 | | static bool AfterFile(const Comparator* ucmp, const Slice* user_key, |
1109 | 3.00k | const FdWithKeyRange* f) { |
1110 | | // nullptr user_key occurs before all keys and is therefore never after *f |
1111 | 3.00k | return (user_key != nullptr && |
1112 | 3.00k | ucmp->CompareWithoutTimestamp(*user_key, |
1113 | 3.00k | ExtractUserKey(f->largest_key)) > 0); |
1114 | 3.00k | } |
1115 | | |
1116 | | static bool BeforeFile(const Comparator* ucmp, const Slice* user_key, |
1117 | 2.94k | const FdWithKeyRange* f) { |
1118 | | // nullptr user_key occurs after all keys and is therefore never before *f |
1119 | 2.94k | return (user_key != nullptr && |
1120 | 2.94k | ucmp->CompareWithoutTimestamp(*user_key, |
1121 | 2.94k | ExtractUserKey(f->smallest_key)) < 0); |
1122 | 2.94k | } |
1123 | | |
1124 | | bool SomeFileOverlapsRange(const InternalKeyComparator& icmp, |
1125 | | bool disjoint_sorted_files, |
1126 | | const LevelFilesBrief& file_level, |
1127 | | const Slice* smallest_user_key, |
1128 | 0 | const Slice* largest_user_key) { |
1129 | 0 | const Comparator* ucmp = icmp.user_comparator(); |
1130 | 0 | if (!disjoint_sorted_files) { |
1131 | | // Need to check against all files |
1132 | 0 | for (size_t i = 0; i < file_level.num_files; i++) { |
1133 | 0 | const FdWithKeyRange* f = &(file_level.files[i]); |
1134 | 0 | if (AfterFile(ucmp, smallest_user_key, f) || |
1135 | 0 | BeforeFile(ucmp, largest_user_key, f)) { |
1136 | | // No overlap |
1137 | 0 | } else { |
1138 | 0 | return true; // Overlap |
1139 | 0 | } |
1140 | 0 | } |
1141 | 0 | return false; |
1142 | 0 | } |
1143 | | |
1144 | | // Binary search over file list |
1145 | 0 | uint32_t index = 0; |
1146 | 0 | if (smallest_user_key != nullptr) { |
1147 | | // Find the leftmost possible internal key for smallest_user_key |
1148 | 0 | InternalKey small; |
1149 | 0 | small.SetMinPossibleForUserKey(*smallest_user_key); |
1150 | 0 | index = FindFile(icmp, file_level, small.Encode()); |
1151 | 0 | } |
1152 | |
|
1153 | 0 | if (index >= file_level.num_files) { |
1154 | | // beginning of range is after all files, so no overlap. |
1155 | 0 | return false; |
1156 | 0 | } |
1157 | | |
1158 | 0 | return !BeforeFile(ucmp, largest_user_key, &file_level.files[index]); |
1159 | 0 | } |
1160 | | |
1161 | | namespace { |
1162 | | |
1163 | | TableCacheOpenOptions GetCompactionTableCacheOpenOptions( |
1164 | 8.15k | bool open_ephemeral_table_reader) { |
1165 | 8.15k | TableCacheOpenOptions options; |
1166 | 8.15k | options.open_ephemeral_table_reader = open_ephemeral_table_reader; |
1167 | 8.15k | options.avoid_shared_metadata_cache = open_ephemeral_table_reader; |
1168 | 8.15k | options.skip_filters = open_ephemeral_table_reader; |
1169 | 8.15k | return options; |
1170 | 8.15k | } |
1171 | | |
1172 | | class LevelIterator final : public InternalIterator { |
1173 | | public: |
1174 | | // NOTE: many of the const& parameters are saved in this object (so |
1175 | | // must outlive this object) |
1176 | | LevelIterator( |
1177 | | TableCache* table_cache, const ReadOptions& read_options, |
1178 | | const FileOptions& file_options, const InternalKeyComparator& icomparator, |
1179 | | const LevelFilesBrief* flevel, const MutableCFOptions& mutable_cf_options, |
1180 | | HistogramImpl* file_read_hist, TableReaderCaller caller, |
1181 | | bool skip_filters, int level, RangeDelAggregator* range_del_agg, |
1182 | | const std::vector<AtomicCompactionUnitBoundary>* compaction_boundaries = |
1183 | | nullptr, |
1184 | | bool allow_unprepared_value = false, |
1185 | | std::unique_ptr<TruncatedRangeDelIterator>*** range_tombstone_iter_ptr_ = |
1186 | | nullptr, |
1187 | | SequenceNumber read_seq = kMaxSequenceNumber, |
1188 | | Statistics* db_statistics = nullptr, SystemClock* clock = nullptr, |
1189 | | bool open_ephemeral_table_reader = false) |
1190 | 5.38k | : table_cache_(table_cache), |
1191 | 5.38k | read_options_(read_options), |
1192 | 5.38k | file_options_(file_options), |
1193 | 5.38k | icomparator_(icomparator), |
1194 | 5.38k | user_comparator_(icomparator.user_comparator()), |
1195 | 5.38k | flevel_(flevel), |
1196 | 5.38k | mutable_cf_options_(mutable_cf_options), |
1197 | 5.38k | prefix_extractor_(mutable_cf_options.prefix_extractor.get()), |
1198 | 5.38k | file_read_hist_(file_read_hist), |
1199 | 5.38k | caller_(caller), |
1200 | 5.38k | file_index_(flevel_->num_files), |
1201 | 5.38k | range_del_agg_(range_del_agg), |
1202 | 5.38k | pinned_iters_mgr_(nullptr), |
1203 | 5.38k | compaction_boundaries_(compaction_boundaries), |
1204 | 5.38k | range_tombstone_iter_(nullptr), |
1205 | 5.38k | range_tombstone_iter_required_(range_tombstone_iter_ptr_ != nullptr), |
1206 | 5.38k | read_seq_(read_seq != kMaxSequenceNumber || |
1207 | 2.91k | read_options.snapshot == nullptr |
1208 | 5.38k | ? read_seq |
1209 | 5.38k | : read_options.snapshot->GetSequenceNumber()), |
1210 | 5.38k | level_(level), |
1211 | 5.38k | skip_filters_(skip_filters), |
1212 | 5.38k | allow_unprepared_value_(allow_unprepared_value), |
1213 | 5.38k | is_next_read_sequential_(false), |
1214 | 5.38k | to_return_sentinel_(false), |
1215 | 5.38k | scan_opts_(nullptr), |
1216 | 5.38k | db_statistics_(db_statistics), |
1217 | 5.38k | clock_(clock), |
1218 | | table_cache_open_options_( |
1219 | 5.38k | GetCompactionTableCacheOpenOptions(open_ephemeral_table_reader)), |
1220 | | // Same condition under which a block-based table iterator checks the |
1221 | | // prefix filter on Seek(). TableCache hands out no reference to an |
1222 | | // ephemeral table reader, which a stand-in would need. |
1223 | | files_filterable_on_seek_( |
1224 | 5.38k | !skip_filters_ && !table_cache_open_options_.skip_filters && |
1225 | 5.38k | !table_cache_open_options_.open_ephemeral_table_reader && |
1226 | 5.38k | GetIteratorPrefixMode(read_options, prefix_extractor_ != nullptr) != |
1227 | 5.38k | IteratorPrefixMode::kTotalOrder) { |
1228 | | // Empty level is not supported. |
1229 | 5.38k | assert(flevel_ != nullptr && flevel_->num_files > 0); |
1230 | 5.38k | if (range_tombstone_iter_ptr_) { |
1231 | 4.51k | *range_tombstone_iter_ptr_ = &range_tombstone_iter_; |
1232 | 4.51k | } |
1233 | 5.38k | } |
1234 | | |
1235 | | // No copying allowed |
1236 | | LevelIterator(const LevelIterator&) = delete; |
1237 | | LevelIterator& operator=(const LevelIterator&) = delete; |
1238 | | LevelIterator(LevelIterator&&) = delete; |
1239 | | LevelIterator& operator=(LevelIterator&&) = delete; |
1240 | | |
1241 | 5.38k | ~LevelIterator() override { |
1242 | 5.38k | delete file_iter_.Set(nullptr); |
1243 | | // Clean up any prepared iterators that weren't used |
1244 | 5.38k | for (auto& entry : prepared_iters_) { |
1245 | 0 | delete entry.second; |
1246 | 0 | } |
1247 | 5.38k | prepared_iters_.clear(); |
1248 | 5.38k | assert(prepared_iters_.size() == 0); |
1249 | 5.38k | } |
1250 | | |
1251 | | // Seek to the first file with a key >= target. |
1252 | | // If range_tombstone_iter_ is not nullptr, then we pretend that file |
1253 | | // boundaries are fake keys (sentinel keys). These keys are used to keep range |
1254 | | // tombstones alive even when all point keys in an SST file are exhausted. |
1255 | | // These sentinel keys will be skipped in merging iterator. |
1256 | | void Seek(const Slice& target) override; |
1257 | | void SeekForPrev(const Slice& target) override; |
1258 | | void SeekToFirst() override; |
1259 | | void SeekToLast() override; |
1260 | | void Next() final; |
1261 | | bool NextAndGetResult(IterateResult* result) override; |
1262 | | void Prev() override; |
1263 | | |
1264 | | // In addition to valid and invalid state (!file_iter.Valid() and |
1265 | | // status.ok()), a third state of the iterator is when !file_iter_.Valid() and |
1266 | | // to_return_sentinel_. This means we are at the end of a file, and a sentinel |
1267 | | // key (the file boundary that we pretend as a key) is to be returned next. |
1268 | | // file_iter_.Valid() and to_return_sentinel_ should not both be true. |
1269 | 24.7k | bool Valid() const override { |
1270 | 24.7k | assert(!(file_iter_.Valid() && to_return_sentinel_)); |
1271 | 24.7k | return file_iter_.Valid() || to_return_sentinel_; |
1272 | 24.7k | } |
1273 | 14.5k | Slice key() const override { |
1274 | 14.5k | assert(Valid()); |
1275 | 14.5k | if (to_return_sentinel_) { |
1276 | | // Sentinel should be returned after file_iter_ reaches the end of the |
1277 | | // file |
1278 | 2.62k | assert(!file_iter_.Valid()); |
1279 | 2.62k | return sentinel_; |
1280 | 2.62k | } |
1281 | 11.8k | return file_iter_.key(); |
1282 | 14.5k | } |
1283 | | |
1284 | 5.41k | Slice value() const override { |
1285 | 5.41k | assert(Valid()); |
1286 | 5.41k | assert(!to_return_sentinel_); |
1287 | 5.41k | return file_iter_.value(); |
1288 | 5.41k | } |
1289 | | |
1290 | 2.44k | uint64_t write_unix_time() const override { |
1291 | 2.44k | assert(Valid()); |
1292 | 2.44k | return file_iter_.write_unix_time(); |
1293 | 2.44k | } |
1294 | | |
1295 | 9.24k | Status status() const override { |
1296 | 9.24k | return file_iter_.iter() ? file_iter_.status() : Status::OK(); |
1297 | 9.24k | } |
1298 | | |
1299 | 3.13k | bool PrepareValue() override { return file_iter_.PrepareValue(); } |
1300 | | |
1301 | 0 | inline bool MayBeOutOfLowerBound() override { |
1302 | 0 | assert(Valid()); |
1303 | 0 | return may_be_out_of_lower_bound_ && file_iter_.MayBeOutOfLowerBound(); |
1304 | 0 | } |
1305 | | |
1306 | 0 | inline IterBoundCheck UpperBoundCheckResult() override { |
1307 | 0 | if (Valid()) { |
1308 | 0 | return file_iter_.UpperBoundCheckResult(); |
1309 | 0 | } else { |
1310 | 0 | return IterBoundCheck::kUnknown; |
1311 | 0 | } |
1312 | 0 | } |
1313 | | |
1314 | 2.46k | void SetPinnedItersMgr(PinnedIteratorsManager* pinned_iters_mgr) override { |
1315 | 2.46k | pinned_iters_mgr_ = pinned_iters_mgr; |
1316 | 2.46k | if (file_iter_.iter()) { |
1317 | 0 | file_iter_.SetPinnedItersMgr(pinned_iters_mgr); |
1318 | 0 | } |
1319 | 2.46k | } |
1320 | | |
1321 | 34 | bool IsKeyPinned() const override { |
1322 | 34 | return pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled() && |
1323 | 34 | file_iter_.iter() && file_iter_.IsKeyPinned(); |
1324 | 34 | } |
1325 | | |
1326 | 1.81k | bool IsValuePinned() const override { |
1327 | 1.81k | return pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled() && |
1328 | 1.81k | file_iter_.iter() && file_iter_.IsValuePinned(); |
1329 | 1.81k | } |
1330 | | |
1331 | 19.7k | bool IsDeleteRangeSentinelKey() const override { return to_return_sentinel_; } |
1332 | | |
1333 | 0 | void SetRangeDelReadSeqno(SequenceNumber read_seq) override { |
1334 | 0 | read_seq_ = read_seq; |
1335 | 0 | } |
1336 | | |
1337 | 8.88k | inline bool FileHasMultiScanArg(size_t file_index) { |
1338 | 8.88k | if (file_to_scan_opts_.get()) { |
1339 | 0 | auto it = file_to_scan_opts_->find(file_index); |
1340 | 0 | if (it != file_to_scan_opts_->end()) { |
1341 | 0 | return !it->second.empty(); |
1342 | 0 | } |
1343 | 0 | } |
1344 | 8.88k | return false; |
1345 | 8.88k | } |
1346 | | |
1347 | 0 | MultiScanArgs& GetMultiScanArgForFile(size_t file_index) { |
1348 | 0 | auto multi_scan_args_it = file_to_scan_opts_->find(file_index); |
1349 | 0 | if (multi_scan_args_it == file_to_scan_opts_->end()) { |
1350 | 0 | auto ret = file_to_scan_opts_->emplace( |
1351 | 0 | file_index, MultiScanArgs(user_comparator_.user_comparator())); |
1352 | 0 | multi_scan_args_it = ret.first; |
1353 | 0 | assert(ret.second); |
1354 | 0 | } |
1355 | 0 | return multi_scan_args_it->second; |
1356 | 0 | } |
1357 | | |
1358 | 0 | void Prepare(const MultiScanArgs* so) override { |
1359 | | // We assume here that scan_opts is sorted such that |
1360 | | // scan_opts[0].range.start < scan_opts[1].range.start, and non |
1361 | | // overlapping |
1362 | 0 | if (so == nullptr) { |
1363 | 0 | return; |
1364 | 0 | } |
1365 | | |
1366 | 0 | scan_opts_ = so; |
1367 | | |
1368 | | // Verify comparator is consistent |
1369 | 0 | assert(so->GetComparator() == user_comparator_.user_comparator()); |
1370 | |
|
1371 | 0 | file_to_scan_opts_ = std::make_unique<ScanOptionsMap>(); |
1372 | 0 | ForEachMultiScanOverlappingFile( |
1373 | 0 | icomparator_, user_comparator_, *flevel_, level_, *scan_opts_, |
1374 | 0 | [&](size_t file_index, const ScanOptions& scan_range) { |
1375 | 0 | auto const metadata = flevel_->files[file_index].file_metadata; |
1376 | 0 | if (metadata->FileIsStandAloneRangeTombstone()) { |
1377 | | // Skip stand alone range deletion files. |
1378 | 0 | return; |
1379 | 0 | } |
1380 | 0 | auto& args = GetMultiScanArgForFile(file_index); |
1381 | 0 | args.insert(scan_range.range.start.value(), |
1382 | 0 | scan_range.range.limit.value(), scan_range.property_bag); |
1383 | 0 | }); |
1384 | |
|
1385 | 0 | StopWatch timer(clock_, db_statistics_, MULTISCAN_PREPARE_ITERATORS); |
1386 | | |
1387 | | // Propagate multiscan configs |
1388 | 0 | for (auto& file_to_arg : *file_to_scan_opts_) { |
1389 | 0 | file_to_arg.second.CopyConfigFrom(*so); |
1390 | 0 | assert(OverlapRange(*file_to_arg.second.GetScanRanges().begin(), |
1391 | 0 | file_to_arg.first) && |
1392 | 0 | OverlapRange(*file_to_arg.second.GetScanRanges().rbegin(), |
1393 | 0 | file_to_arg.first)); |
1394 | 0 | } |
1395 | |
|
1396 | 0 | if (so->use_async_io && |
1397 | 0 | (!range_tombstone_iter_required_ || range_tombstone_iter_ != nullptr)) { |
1398 | 0 | auto before = file_index_; |
1399 | | // Pre-create and prepare only relevant file iterators |
1400 | 0 | for (auto& file_to_arg : *file_to_scan_opts_) { |
1401 | 0 | size_t file_index = file_to_arg.first; |
1402 | |
|
1403 | 0 | file_index_ = file_index; |
1404 | | // Create iterator for this file |
1405 | 0 | auto iter = NewFileIterator(/*seek_target=*/nullptr, |
1406 | 0 | /*prefix_filtered_table=*/nullptr); |
1407 | 0 | if (iter != nullptr) { |
1408 | | // If we have async enabled, lets prepare all our iterators. |
1409 | 0 | iter->Prepare(&file_to_arg.second); |
1410 | | // Store the prepared iterator |
1411 | 0 | prepared_iters_[file_index] = iter; |
1412 | 0 | } |
1413 | 0 | } |
1414 | 0 | file_index_ = before; |
1415 | 0 | } |
1416 | 0 | } |
1417 | | |
1418 | | private: |
1419 | | // Return true if at least one invalid file is seen and skipped. |
1420 | | bool SkipEmptyFileForward(); |
1421 | | void SkipEmptyFileBackward(); |
1422 | | void SetFileIterator(InternalIterator* iter); |
1423 | | void InitFileIterator(size_t new_file_index, const Slice* seek_target); |
1424 | | void RetryPrefixFilteredFile(const Slice* seek_target); |
1425 | | |
1426 | 9.10k | const Slice& file_smallest_key(size_t file_index) { |
1427 | 9.10k | assert(file_index < flevel_->num_files); |
1428 | 9.10k | return flevel_->files[file_index].smallest_key; |
1429 | 9.10k | } |
1430 | | |
1431 | 9.89k | const Slice& file_largest_key(size_t file_index) { |
1432 | 9.89k | assert(file_index < flevel_->num_files); |
1433 | 9.89k | return flevel_->files[file_index].largest_key; |
1434 | 9.89k | } |
1435 | | |
1436 | 2.02k | bool KeyReachedUpperBound(const Slice& internal_key) { |
1437 | 2.02k | return read_options_.iterate_upper_bound != nullptr && |
1438 | 0 | user_comparator_.CompareWithoutTimestamp( |
1439 | 0 | ExtractUserKey(internal_key), /*a_has_ts=*/true, |
1440 | 0 | *read_options_.iterate_upper_bound, /*b_has_ts=*/false) >= 0; |
1441 | 2.02k | } |
1442 | | |
1443 | | template <bool IsSeek> |
1444 | 20.4k | void SampleRead() { |
1445 | 20.4k | bool sampled = |
1446 | 20.4k | IsSeek ? should_sample_file_read() : should_sample_file_read_next(); |
1447 | 20.4k | if (!sampled) { |
1448 | 20.4k | return; |
1449 | 20.4k | } |
1450 | | |
1451 | 7 | if (file_index_ >= flevel_->num_files || !file_iter_.Valid()) { |
1452 | 0 | return; |
1453 | 0 | } |
1454 | 7 | const FileMetaData* meta = flevel_->files[file_index_].file_metadata; |
1455 | 7 | sample_file_read_inc(meta); |
1456 | 7 | ValueType type = ExtractValueType(file_iter_.key()); |
1457 | 7 | if (type == kTypeDeletion || type == kTypeSingleDeletion || |
1458 | 6 | type == kTypeDeletionWithTimestamp || type == kTypeMerge) { |
1459 | 1 | sample_collapsible_entry_file_read_inc(meta); |
1460 | 1 | } |
1461 | 7 | } version_set.cc:void rocksdb::(anonymous namespace)::LevelIterator::SampleRead<true>() Line | Count | Source | 1444 | 5.39k | void SampleRead() { | 1445 | 5.39k | bool sampled = | 1446 | 5.39k | IsSeek ? should_sample_file_read() : should_sample_file_read_next(); | 1447 | 5.39k | if (!sampled) { | 1448 | 5.38k | return; | 1449 | 5.38k | } | 1450 | | | 1451 | 7 | if (file_index_ >= flevel_->num_files || !file_iter_.Valid()) { | 1452 | 0 | return; | 1453 | 0 | } | 1454 | 7 | const FileMetaData* meta = flevel_->files[file_index_].file_metadata; | 1455 | 7 | sample_file_read_inc(meta); | 1456 | 7 | ValueType type = ExtractValueType(file_iter_.key()); | 1457 | 7 | if (type == kTypeDeletion || type == kTypeSingleDeletion || | 1458 | 6 | type == kTypeDeletionWithTimestamp || type == kTypeMerge) { | 1459 | 1 | sample_collapsible_entry_file_read_inc(meta); | 1460 | 1 | } | 1461 | 7 | } |
version_set.cc:void rocksdb::(anonymous namespace)::LevelIterator::SampleRead<false>() Line | Count | Source | 1444 | 15.0k | void SampleRead() { | 1445 | 15.0k | bool sampled = | 1446 | 15.0k | IsSeek ? should_sample_file_read() : should_sample_file_read_next(); | 1447 | 15.0k | if (!sampled) { | 1448 | 15.0k | return; | 1449 | 15.0k | } | 1450 | | | 1451 | 0 | if (file_index_ >= flevel_->num_files || !file_iter_.Valid()) { | 1452 | 0 | return; | 1453 | 0 | } | 1454 | 0 | const FileMetaData* meta = flevel_->files[file_index_].file_metadata; | 1455 | 0 | sample_file_read_inc(meta); | 1456 | 0 | ValueType type = ExtractValueType(file_iter_.key()); | 1457 | 0 | if (type == kTypeDeletion || type == kTypeSingleDeletion || | 1458 | 0 | type == kTypeDeletionWithTimestamp || type == kTypeMerge) { | 1459 | 0 | sample_collapsible_entry_file_read_inc(meta); | 1460 | 0 | } | 1461 | 0 | } |
|
1462 | | |
1463 | 13.1k | void ClearRangeTombstoneIter() { |
1464 | 13.1k | if (range_tombstone_iter_) { |
1465 | 12.3k | range_tombstone_iter_->reset(); |
1466 | 12.3k | } |
1467 | 13.1k | } |
1468 | | |
1469 | | // Move file_iter_ to the file at file_index_. |
1470 | | // range_tombstone_iter_ is updated with a range tombstone iterator |
1471 | | // into the new file. Old range tombstone iterator is cleared. |
1472 | | // If seek_target is non-null, the caller seeks the returned iterator to it |
1473 | | // next. When the file's prefix filter rules that seek out, the returned |
1474 | | // iterator is an empty stand-in and *prefix_filtered_table is set to the |
1475 | | // file's table reader, which the stand-in keeps alive. Otherwise |
1476 | | // *prefix_filtered_table is left unchanged. |
1477 | | InternalIterator* NewFileIterator(const Slice* seek_target, |
1478 | 8.88k | TableReader** prefix_filtered_table) { |
1479 | 8.88k | assert(file_index_ < flevel_->num_files); |
1480 | 8.88k | auto file_meta = flevel_->files[file_index_]; |
1481 | | |
1482 | 8.88k | const InternalKey* smallest_compaction_key = nullptr; |
1483 | 8.88k | const InternalKey* largest_compaction_key = nullptr; |
1484 | 8.88k | if (compaction_boundaries_ != nullptr) { |
1485 | 3.05k | smallest_compaction_key = (*compaction_boundaries_)[file_index_].smallest; |
1486 | 3.05k | largest_compaction_key = (*compaction_boundaries_)[file_index_].largest; |
1487 | 3.05k | } |
1488 | | // TableCache hands out no reference to an ephemeral table reader. |
1489 | 8.88k | assert(seek_target == nullptr || |
1490 | 8.88k | !table_cache_open_options_.open_ephemeral_table_reader); |
1491 | 8.88k | CheckMayBeOutOfLowerBound(); |
1492 | 8.88k | ClearRangeTombstoneIter(); |
1493 | 8.88k | return table_cache_->NewIterator( |
1494 | 8.88k | read_options_, file_options_, icomparator_, *file_meta.file_metadata, |
1495 | 8.88k | range_del_agg_, mutable_cf_options_, |
1496 | 8.88k | nullptr /* don't need reference to table */, file_read_hist_, caller_, |
1497 | 8.88k | /*arena=*/nullptr, skip_filters_, level_, |
1498 | 8.88k | /*max_file_size_for_l0_meta_pin=*/0, smallest_compaction_key, |
1499 | 8.88k | largest_compaction_key, allow_unprepared_value_, seek_target, |
1500 | 8.88k | prefix_filtered_table, &read_seq_, range_tombstone_iter_, |
1501 | | /*maybe_pin_table_handle=*/ |
1502 | 8.88k | !table_cache_open_options_.open_ephemeral_table_reader, |
1503 | 8.88k | /*file_open_metadata=*/nullptr, table_cache_open_options_); |
1504 | 8.88k | } |
1505 | | |
1506 | | // Check if current file being fully within iterate_lower_bound. |
1507 | | // |
1508 | | // Note MyRocks may update iterate bounds between seek. To workaround it, |
1509 | | // we need to check and update may_be_out_of_lower_bound_ accordingly. |
1510 | 14.6k | void CheckMayBeOutOfLowerBound() { |
1511 | 14.6k | if (read_options_.iterate_lower_bound != nullptr && |
1512 | 0 | file_index_ < flevel_->num_files) { |
1513 | 0 | may_be_out_of_lower_bound_ = |
1514 | 0 | user_comparator_.CompareWithoutTimestamp( |
1515 | 0 | ExtractUserKey(file_smallest_key(file_index_)), /*a_has_ts=*/true, |
1516 | 0 | *read_options_.iterate_lower_bound, /*b_has_ts=*/false) < 0; |
1517 | 0 | } |
1518 | 14.6k | } |
1519 | | |
1520 | | #ifndef NDEBUG |
1521 | | bool OverlapRange(const ScanOptions& opts, size_t file_index); |
1522 | | #endif |
1523 | | |
1524 | | TableCache* table_cache_; |
1525 | | const ReadOptions& read_options_; |
1526 | | const FileOptions& file_options_; |
1527 | | const InternalKeyComparator& icomparator_; |
1528 | | const UserComparatorWrapper user_comparator_; |
1529 | | const LevelFilesBrief* flevel_; |
1530 | | mutable FileDescriptor current_value_; |
1531 | | const MutableCFOptions& mutable_cf_options_; |
1532 | | const SliceTransform* prefix_extractor_; |
1533 | | |
1534 | | HistogramImpl* file_read_hist_; |
1535 | | TableReaderCaller caller_; |
1536 | | size_t file_index_; |
1537 | | RangeDelAggregator* range_del_agg_; |
1538 | | IteratorWrapper file_iter_; // May be nullptr |
1539 | | // Non-null while file_iter_ is an empty stand-in for the file at |
1540 | | // file_index_, because that file's prefix filter ruled out the last Seek() |
1541 | | // target. Points to the file's table reader so that later seeks into the |
1542 | | // same file can check the filter again without the table cache. |
1543 | | TableReader* prefix_filtered_table_ = nullptr; |
1544 | | PinnedIteratorsManager* pinned_iters_mgr_; |
1545 | | |
1546 | | // To be propagated to RangeDelAggregator in order to safely truncate range |
1547 | | // tombstones. |
1548 | | const std::vector<AtomicCompactionUnitBoundary>* compaction_boundaries_; |
1549 | | |
1550 | | // This is set when this level iterator is used under a merging iterator |
1551 | | // that processes range tombstones. range_tombstone_iter_ points to where the |
1552 | | // merging iterator stores the range tombstones iterator for this level. When |
1553 | | // this level iterator moves to a new SST file, it updates the range |
1554 | | // tombstones accordingly through this pointer. So the merging iterator always |
1555 | | // has access to the current SST file's range tombstones. |
1556 | | // |
1557 | | // The level iterator treats file boundary as fake keys (sentinel keys) to |
1558 | | // keep range tombstones alive if needed and make upper level, i.e. merging |
1559 | | // iterator, aware of file changes (when level iterator moves to a new SST |
1560 | | // file, there is some bookkeeping work that needs to be done at merging |
1561 | | // iterator end). |
1562 | | // |
1563 | | // *range_tombstone_iter_ points to range tombstones of the current SST file |
1564 | | std::unique_ptr<TruncatedRangeDelIterator>* range_tombstone_iter_; |
1565 | | bool range_tombstone_iter_required_; |
1566 | | |
1567 | | // The sentinel key to be returned |
1568 | | Slice sentinel_; |
1569 | | SequenceNumber read_seq_; |
1570 | | |
1571 | | int level_; |
1572 | | bool skip_filters_; |
1573 | | bool allow_unprepared_value_; |
1574 | | bool may_be_out_of_lower_bound_ = true; |
1575 | | bool is_next_read_sequential_; |
1576 | | // Set in Seek() when a prefix seek reaches end of the current file, |
1577 | | // and the next file has a different prefix. SkipEmptyFileForward() |
1578 | | // will not move to next file when this flag is set. |
1579 | | bool prefix_exhausted_ = false; |
1580 | | // Whether next/prev key is a sentinel key. |
1581 | | bool to_return_sentinel_ = false; |
1582 | | const MultiScanArgs* scan_opts_ = nullptr; |
1583 | | |
1584 | | Statistics* db_statistics_ = nullptr; |
1585 | | SystemClock* clock_ = nullptr; |
1586 | | TableCacheOpenOptions table_cache_open_options_; |
1587 | | // Whether Seek() lets a file's prefix filter rule the seek out before the |
1588 | | // file's iterator is built. |
1589 | | const bool files_filterable_on_seek_; |
1590 | | |
1591 | | // Our stored scan_opts for each prefix |
1592 | | std::unique_ptr<ScanOptionsMap> file_to_scan_opts_ = nullptr; |
1593 | | |
1594 | | // Map to store pre-created iterators by file index |
1595 | | std::unordered_map<size_t, InternalIterator*> prepared_iters_; |
1596 | | |
1597 | | // Sets flags for if we should return the sentinel key next. |
1598 | | // The condition for returning sentinel is reaching the end of current |
1599 | | // file_iter_: !Valid() && status.().ok(). |
1600 | | void TrySetDeleteRangeSentinel(const Slice& boundary_key); |
1601 | 12.9k | void ClearSentinel() { to_return_sentinel_ = false; } |
1602 | | }; |
1603 | | |
1604 | 15.1k | void LevelIterator::TrySetDeleteRangeSentinel(const Slice& boundary_key) { |
1605 | 15.1k | assert(range_tombstone_iter_); |
1606 | 15.1k | if (file_iter_.iter() != nullptr && !file_iter_.Valid() && |
1607 | 7.23k | file_iter_.status().ok()) { |
1608 | 7.23k | to_return_sentinel_ = true; |
1609 | 7.23k | sentinel_ = boundary_key; |
1610 | 7.23k | } |
1611 | 15.1k | } |
1612 | | |
1613 | 1.07k | void LevelIterator::Seek(const Slice& target) { |
1614 | 1.07k | prefix_exhausted_ = false; |
1615 | 1.07k | ClearSentinel(); |
1616 | | // A new file iterator is sought to target right away, so its file's prefix |
1617 | | // filter can rule the seek out before the iterator is built. MultiScan |
1618 | | // positions files through their prepared ranges instead. |
1619 | 1.07k | const Slice* seek_target = |
1620 | 1.07k | files_filterable_on_seek_ && scan_opts_ == nullptr ? &target : nullptr; |
1621 | | // Check whether the seek key fall under the same file |
1622 | 1.07k | bool need_to_reseek = true; |
1623 | 1.07k | if (file_iter_.iter() != nullptr && file_index_ < flevel_->num_files) { |
1624 | 72 | const FdWithKeyRange& cur_file = flevel_->files[file_index_]; |
1625 | 72 | if (icomparator_.InternalKeyComparator::Compare( |
1626 | 72 | target, cur_file.largest_key) <= 0 && |
1627 | 20 | icomparator_.InternalKeyComparator::Compare( |
1628 | 20 | target, cur_file.smallest_key) >= 0) { |
1629 | 10 | need_to_reseek = false; |
1630 | 10 | assert(static_cast<size_t>(FindFile(icomparator_, *flevel_, target)) == |
1631 | 10 | file_index_); |
1632 | 10 | } |
1633 | 72 | } |
1634 | 1.07k | if (need_to_reseek) { |
1635 | 1.06k | TEST_SYNC_POINT("LevelIterator::Seek:BeforeFindFile"); |
1636 | 1.06k | size_t new_file_index = FindFile(icomparator_, *flevel_, target); |
1637 | 1.06k | InitFileIterator(new_file_index, seek_target); |
1638 | 1.06k | } else if (prefix_filtered_table_ != nullptr) { |
1639 | 0 | InitFileIterator(file_index_, seek_target); |
1640 | 0 | } |
1641 | | |
1642 | 1.07k | if (file_iter_.iter() != nullptr) { |
1643 | 1.01k | if (scan_opts_) { |
1644 | | // At this point, we only know that the seek target is < largest_key |
1645 | | // in the file. We need to check whether there is actual overlap. |
1646 | 0 | const FdWithKeyRange& cur_file = flevel_->files[file_index_]; |
1647 | 0 | if (KeyReachedUpperBound(cur_file.smallest_key)) { |
1648 | 0 | return; |
1649 | 0 | } |
1650 | 0 | } |
1651 | 1.01k | file_iter_.Seek(target); |
1652 | | // Status::TryAgain indicates asynchronous request for retrieval of data |
1653 | | // blocks has been submitted. So it should return at this point and Seek |
1654 | | // should be called again to retrieve the requested block and execute the |
1655 | | // remaining code. |
1656 | 1.01k | if (file_iter_.status() == Status::TryAgain()) { |
1657 | 0 | return; |
1658 | 0 | } |
1659 | 1.01k | if (!file_iter_.Valid() && file_iter_.status().ok() && |
1660 | 0 | prefix_extractor_ != nullptr && !read_options_.total_order_seek && |
1661 | 0 | !read_options_.auto_prefix_mode && |
1662 | 0 | file_index_ < flevel_->num_files - 1) { |
1663 | 0 | size_t ts_sz = user_comparator_.user_comparator()->timestamp_size(); |
1664 | 0 | Slice target_user_key_without_ts = |
1665 | 0 | ExtractUserKeyAndStripTimestamp(target, ts_sz); |
1666 | 0 | Slice next_file_first_user_key_without_ts = |
1667 | 0 | ExtractUserKeyAndStripTimestamp(file_smallest_key(file_index_ + 1), |
1668 | 0 | ts_sz); |
1669 | 0 | if (prefix_extractor_->InDomain(target_user_key_without_ts) && |
1670 | 0 | (!prefix_extractor_->InDomain(next_file_first_user_key_without_ts) || |
1671 | 0 | prefix_extractor_->Transform(target_user_key_without_ts) |
1672 | 0 | .compare(prefix_extractor_->Transform( |
1673 | 0 | next_file_first_user_key_without_ts)) != 0)) { |
1674 | | // SkipEmptyFileForward() will not advance to next file when this flag |
1675 | | // is set for reason detailed below. |
1676 | | // |
1677 | | // The file we initially positioned to has no keys under the target |
1678 | | // prefix, and the next file's smallest key has a different prefix than |
1679 | | // target. When doing prefix iterator seek, when keys for one prefix |
1680 | | // have been exhausted, it can jump to any key that is larger. Here we |
1681 | | // are enforcing a stricter contract than that, in order to make it |
1682 | | // easier for higher layers (merging and DB iterator) to reason the |
1683 | | // correctness: |
1684 | | // 1. Within the prefix, the result should be accurate. |
1685 | | // 2. If keys for the prefix is exhausted, it is either positioned to |
1686 | | // the next key after the prefix, or make the iterator invalid. |
1687 | | // A side benefit will be that it invalidates the iterator earlier so |
1688 | | // that the upper level merging iterator can merge fewer child |
1689 | | // iterators. |
1690 | | // |
1691 | | // The flag is cleared in Seek*() calls. There is no need to clear the |
1692 | | // flag in Prev() since Prev() will not be called when the flag is set |
1693 | | // for reasons explained below. If range_tombstone_iter_ is nullptr, |
1694 | | // then there is no file boundary sentinel key. Since |
1695 | | // !file_iter_.Valid() from the if condition above, this level iterator |
1696 | | // is !Valid(), so Prev() will not be called. If range_tombstone_iter_ |
1697 | | // is not nullptr, there are two cases depending on if this level |
1698 | | // iterator reaches top of the heap in merging iterator (the upper |
1699 | | // layer). |
1700 | | // If so, merging iterator will see the sentinel key, call |
1701 | | // NextAndGetResult() and the call to NextAndGetResult() will skip the |
1702 | | // sentinel key and makes this level iterator invalid. If not, then it |
1703 | | // could be because the upper layer is done before any method of this |
1704 | | // level iterator is called or another Seek*() call is invoked. Either |
1705 | | // way, Prev() is never called before Seek*(). |
1706 | | // The flag should not be cleared at the beginning of |
1707 | | // Next/NextAndGetResult() since it is used in SkipEmptyFileForward() |
1708 | | // called in Next/NextAndGetResult(). |
1709 | 0 | prefix_exhausted_ = true; |
1710 | 0 | } |
1711 | 0 | } |
1712 | | |
1713 | 1.01k | if (range_tombstone_iter_) { |
1714 | 171 | TrySetDeleteRangeSentinel(file_largest_key(file_index_)); |
1715 | 171 | } |
1716 | 1.01k | } |
1717 | 1.07k | SkipEmptyFileForward(); |
1718 | 1.07k | CheckMayBeOutOfLowerBound(); |
1719 | 1.07k | SampleRead<true>(); |
1720 | 1.07k | } |
1721 | | |
1722 | 1.58k | void LevelIterator::SeekForPrev(const Slice& target) { |
1723 | 1.58k | prefix_exhausted_ = false; |
1724 | 1.58k | ClearSentinel(); |
1725 | 1.58k | size_t new_file_index = FindFile(icomparator_, *flevel_, target); |
1726 | | // Seek beyond this level's smallest key |
1727 | 1.58k | if (new_file_index == 0 && |
1728 | 584 | icomparator_.Compare(target, file_smallest_key(0)) < 0) { |
1729 | 348 | SetFileIterator(nullptr); |
1730 | 348 | ClearRangeTombstoneIter(); |
1731 | 348 | CheckMayBeOutOfLowerBound(); |
1732 | 348 | return; |
1733 | 348 | } |
1734 | 1.23k | if (new_file_index >= flevel_->num_files) { |
1735 | 729 | new_file_index = flevel_->num_files - 1; |
1736 | 729 | } |
1737 | | |
1738 | 1.23k | InitFileIterator(new_file_index, /*seek_target=*/nullptr); |
1739 | 1.23k | if (file_iter_.iter() != nullptr) { |
1740 | 1.23k | file_iter_.SeekForPrev(target); |
1741 | 1.23k | if (range_tombstone_iter_ && |
1742 | 1.23k | icomparator_.Compare(target, file_smallest_key(file_index_)) >= 0) { |
1743 | | // In SeekForPrev() case, it is possible that the target is less than |
1744 | | // file's lower boundary since largest key is used to determine file index |
1745 | | // (FindFile()). When target is less than file's lower boundary, sentinel |
1746 | | // key should not be set so that SeekForPrev() does not result in a key |
1747 | | // larger than target. This is correct in that there is no need to keep |
1748 | | // the range tombstones in this file alive as they only cover keys |
1749 | | // starting from the file's lower boundary, which is after `target`. |
1750 | 1.07k | TrySetDeleteRangeSentinel(file_smallest_key(file_index_)); |
1751 | 1.07k | } |
1752 | 1.23k | SkipEmptyFileBackward(); |
1753 | 1.23k | } |
1754 | 1.23k | CheckMayBeOutOfLowerBound(); |
1755 | 1.23k | SampleRead<true>(); |
1756 | 1.23k | } |
1757 | | |
1758 | 3.08k | void LevelIterator::SeekToFirst() { |
1759 | 3.08k | prefix_exhausted_ = false; |
1760 | 3.08k | ClearSentinel(); |
1761 | 3.08k | InitFileIterator(0, /*seek_target=*/nullptr); |
1762 | 3.08k | if (file_iter_.iter() != nullptr) { |
1763 | 3.08k | file_iter_.SeekToFirst(); |
1764 | 3.08k | if (range_tombstone_iter_) { |
1765 | | // We do this in SeekToFirst() and SeekToLast() since |
1766 | | // we could have an empty file with only range tombstones. |
1767 | 3.08k | TrySetDeleteRangeSentinel(file_largest_key(file_index_)); |
1768 | 3.08k | } |
1769 | 3.08k | } |
1770 | 3.08k | SkipEmptyFileForward(); |
1771 | 3.08k | CheckMayBeOutOfLowerBound(); |
1772 | 3.08k | SampleRead<true>(); |
1773 | 3.08k | } |
1774 | | |
1775 | 0 | void LevelIterator::SeekToLast() { |
1776 | 0 | prefix_exhausted_ = false; |
1777 | 0 | ClearSentinel(); |
1778 | 0 | InitFileIterator(flevel_->num_files - 1, /*seek_target=*/nullptr); |
1779 | 0 | if (file_iter_.iter() != nullptr) { |
1780 | 0 | file_iter_.SeekToLast(); |
1781 | 0 | if (range_tombstone_iter_) { |
1782 | 0 | TrySetDeleteRangeSentinel(file_smallest_key(file_index_)); |
1783 | 0 | } |
1784 | 0 | } |
1785 | 0 | SkipEmptyFileBackward(); |
1786 | 0 | CheckMayBeOutOfLowerBound(); |
1787 | 0 | SampleRead<true>(); |
1788 | 0 | } |
1789 | | |
1790 | 0 | void LevelIterator::Next() { |
1791 | 0 | assert(Valid()); |
1792 | 0 | if (to_return_sentinel_) { |
1793 | | // file_iter_ is at EOF already when to_return_sentinel_ |
1794 | 0 | ClearSentinel(); |
1795 | 0 | } else { |
1796 | 0 | file_iter_.Next(); |
1797 | 0 | if (range_tombstone_iter_) { |
1798 | 0 | TrySetDeleteRangeSentinel(file_largest_key(file_index_)); |
1799 | 0 | } |
1800 | 0 | } |
1801 | 0 | SkipEmptyFileForward(); |
1802 | 0 | SampleRead<false>(); |
1803 | 0 | } |
1804 | | |
1805 | 9.82k | bool LevelIterator::NextAndGetResult(IterateResult* result) { |
1806 | 9.82k | assert(Valid()); |
1807 | | // file_iter_ is at EOF already when to_return_sentinel_ |
1808 | 9.82k | bool is_valid = !to_return_sentinel_ && file_iter_.NextAndGetResult(result); |
1809 | 9.82k | if (!is_valid) { |
1810 | 9.23k | if (to_return_sentinel_) { |
1811 | 4.61k | ClearSentinel(); |
1812 | 4.61k | } else if (range_tombstone_iter_) { |
1813 | 4.61k | TrySetDeleteRangeSentinel(file_largest_key(file_index_)); |
1814 | 4.61k | } |
1815 | 9.23k | is_next_read_sequential_ = true; |
1816 | 9.23k | SkipEmptyFileForward(); |
1817 | 9.23k | is_next_read_sequential_ = false; |
1818 | 9.23k | is_valid = Valid(); |
1819 | 9.23k | if (is_valid) { |
1820 | | // This could be set in TrySetDeleteRangeSentinel() or |
1821 | | // SkipEmptyFileForward() above. |
1822 | 6.64k | if (to_return_sentinel_) { |
1823 | 4.61k | result->key = sentinel_; |
1824 | 4.61k | result->bound_check_result = IterBoundCheck::kUnknown; |
1825 | 4.61k | result->value_prepared = true; |
1826 | 4.61k | } else { |
1827 | 2.02k | result->key = key(); |
1828 | 2.02k | result->bound_check_result = file_iter_.UpperBoundCheckResult(); |
1829 | | // Ideally, we should return the real file_iter_.value_prepared but the |
1830 | | // information is not here. It would casue an extra PrepareValue() |
1831 | | // for the first key of a file. |
1832 | 2.02k | result->value_prepared = !allow_unprepared_value_; |
1833 | 2.02k | } |
1834 | 6.64k | } |
1835 | 9.23k | } |
1836 | 9.82k | SampleRead<false>(); |
1837 | 9.82k | return is_valid; |
1838 | 9.82k | } |
1839 | | |
1840 | 5.25k | void LevelIterator::Prev() { |
1841 | 5.25k | assert(Valid()); |
1842 | 5.25k | if (to_return_sentinel_) { |
1843 | 2.62k | ClearSentinel(); |
1844 | 2.63k | } else { |
1845 | 2.63k | file_iter_.Prev(); |
1846 | 2.63k | if (range_tombstone_iter_) { |
1847 | 2.63k | TrySetDeleteRangeSentinel(file_smallest_key(file_index_)); |
1848 | 2.63k | } |
1849 | 2.63k | } |
1850 | 5.25k | SkipEmptyFileBackward(); |
1851 | 5.25k | SampleRead<false>(); |
1852 | 5.25k | } |
1853 | | |
1854 | 13.3k | bool LevelIterator::SkipEmptyFileForward() { |
1855 | 13.3k | bool seen_empty_file = false; |
1856 | | // Pause at sentinel key |
1857 | 15.4k | while (!to_return_sentinel_ && |
1858 | 10.8k | (file_iter_.iter() == nullptr || |
1859 | 10.7k | (!file_iter_.Valid() && file_iter_.status().ok() && |
1860 | 4.61k | file_iter_.iter()->UpperBoundCheckResult() != |
1861 | 4.68k | IterBoundCheck::kOutOfBound))) { |
1862 | 4.68k | seen_empty_file = true; |
1863 | | // Move to next file |
1864 | 4.68k | if (file_index_ >= flevel_->num_files - 1 || |
1865 | 2.02k | KeyReachedUpperBound(file_smallest_key(file_index_ + 1)) || |
1866 | 2.65k | prefix_exhausted_) { |
1867 | 2.65k | SetFileIterator(nullptr); |
1868 | 2.65k | ClearRangeTombstoneIter(); |
1869 | 2.65k | break; |
1870 | 2.65k | } |
1871 | | // may init a new *range_tombstone_iter |
1872 | 2.02k | InitFileIterator(file_index_ + 1, /*seek_target=*/nullptr); |
1873 | | // We moved to a new SST file |
1874 | | // Seek range_tombstone_iter_ to reset its !Valid() default state. |
1875 | | // We do not need to call range_tombstone_iter_.Seek* in |
1876 | | // LevelIterator::Seek* since when the merging iterator calls |
1877 | | // LevelIterator::Seek*, it should also call Seek* into the corresponding |
1878 | | // range tombstone iterator. |
1879 | 2.02k | if (file_iter_.iter() != nullptr) { |
1880 | | // If we are doing prepared scan opts then we should seek to the values |
1881 | | // specified by the scan opts |
1882 | | |
1883 | 2.02k | if (scan_opts_ && FileHasMultiScanArg(file_index_)) { |
1884 | 0 | const ScanOptions& opts = |
1885 | 0 | GetMultiScanArgForFile(file_index_).GetScanRanges().front(); |
1886 | 0 | assert(opts.range.start.has_value()); |
1887 | 0 | InternalKey target; |
1888 | 0 | const size_t ts_size = |
1889 | 0 | user_comparator_.user_comparator()->timestamp_size(); |
1890 | 0 | if (ts_size == 0) { |
1891 | 0 | target = InternalKey(opts.range.start.value(), kMaxSequenceNumber, |
1892 | 0 | kValueTypeForSeek); |
1893 | 0 | } else { |
1894 | 0 | std::string seek_key; |
1895 | 0 | AppendKeyWithMaxTimestamp(&seek_key, opts.range.start.value(), |
1896 | 0 | ts_size); |
1897 | 0 | target = InternalKey(seek_key, kMaxSequenceNumber, kValueTypeForSeek); |
1898 | 0 | } |
1899 | 0 | file_iter_.Seek(target.Encode()); |
1900 | 2.02k | } else { |
1901 | 2.02k | file_iter_.SeekToFirst(); |
1902 | 2.02k | } |
1903 | 2.02k | if (range_tombstone_iter_) { |
1904 | 2.02k | if (*range_tombstone_iter_) { |
1905 | 0 | (*range_tombstone_iter_)->SeekToFirst(); |
1906 | 0 | } |
1907 | 2.02k | TrySetDeleteRangeSentinel(file_largest_key(file_index_)); |
1908 | 2.02k | } |
1909 | 2.02k | } |
1910 | 2.02k | } |
1911 | 13.3k | return seen_empty_file; |
1912 | 13.3k | } |
1913 | | |
1914 | 6.48k | void LevelIterator::SkipEmptyFileBackward() { |
1915 | | // Pause at sentinel key |
1916 | 8.03k | while (!to_return_sentinel_ && |
1917 | 5.41k | (file_iter_.iter() == nullptr || |
1918 | 5.41k | (!file_iter_.Valid() && file_iter_.status().ok()))) { |
1919 | | // Move to previous file |
1920 | 2.77k | if (file_index_ == 0) { |
1921 | | // Already the first file |
1922 | 1.22k | SetFileIterator(nullptr); |
1923 | 1.22k | ClearRangeTombstoneIter(); |
1924 | 1.22k | return; |
1925 | 1.22k | } |
1926 | 1.55k | InitFileIterator(file_index_ - 1, /*seek_target=*/nullptr); |
1927 | | // We moved to a new SST file |
1928 | | // Seek range_tombstone_iter_ to reset its !Valid() default state. |
1929 | 1.55k | if (file_iter_.iter() != nullptr) { |
1930 | 1.55k | file_iter_.SeekToLast(); |
1931 | 1.55k | if (range_tombstone_iter_) { |
1932 | 1.55k | if (*range_tombstone_iter_) { |
1933 | 0 | (*range_tombstone_iter_)->SeekToLast(); |
1934 | 0 | } |
1935 | 1.55k | TrySetDeleteRangeSentinel(file_smallest_key(file_index_)); |
1936 | 1.55k | if (to_return_sentinel_) { |
1937 | 0 | break; |
1938 | 0 | } |
1939 | 1.55k | } |
1940 | 1.55k | } |
1941 | 1.55k | } |
1942 | 6.48k | } |
1943 | | |
1944 | | #ifndef NDEBUG |
1945 | | bool LevelIterator::OverlapRange(const ScanOptions& opts, size_t file_index) { |
1946 | | return MultiScanRangeOverlapsFile(user_comparator_, opts, |
1947 | | flevel_->files[file_index]); |
1948 | | } |
1949 | | #endif |
1950 | | |
1951 | 13.1k | void LevelIterator::SetFileIterator(InternalIterator* iter) { |
1952 | 13.1k | prefix_filtered_table_ = nullptr; |
1953 | 13.1k | if (pinned_iters_mgr_ && iter) { |
1954 | 4.99k | iter->SetPinnedItersMgr(pinned_iters_mgr_); |
1955 | 4.99k | } |
1956 | | |
1957 | 13.1k | InternalIterator* old_iter = file_iter_.Set(iter); |
1958 | | // Update the read pattern for PrefetchBuffer. |
1959 | 13.1k | if (is_next_read_sequential_) { |
1960 | 4.61k | file_iter_.UpdateReadaheadState(old_iter); |
1961 | 4.61k | } |
1962 | | |
1963 | 13.1k | if (pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled() && |
1964 | 2.91k | old_iter != nullptr && !old_iter->IsSafeToDropWhilePinned()) { |
1965 | 2.76k | TEST_SYNC_POINT_CALLBACK("LevelIterator::SetFileIterator:PinIterator", |
1966 | 2.76k | old_iter); |
1967 | 2.76k | pinned_iters_mgr_->PinIterator(old_iter); |
1968 | 10.4k | } else { |
1969 | 10.4k | TEST_SYNC_POINT_CALLBACK("LevelIterator::SetFileIterator:DeleteIterator", |
1970 | 10.4k | old_iter); |
1971 | 10.4k | delete old_iter; |
1972 | 10.4k | } |
1973 | 13.1k | } |
1974 | | |
1975 | | void LevelIterator::InitFileIterator(size_t new_file_index, |
1976 | 8.96k | const Slice* seek_target) { |
1977 | 8.96k | if (new_file_index >= flevel_->num_files) { |
1978 | 68 | file_index_ = new_file_index; |
1979 | 68 | SetFileIterator(nullptr); |
1980 | 68 | ClearRangeTombstoneIter(); |
1981 | 68 | return; |
1982 | 8.89k | } else { |
1983 | | // If the file iterator shows incomplete, we try it again if users seek |
1984 | | // to the same file, as this time we may go to a different data block |
1985 | | // which is cached in block cache. |
1986 | | // |
1987 | 8.89k | const bool same_file = file_iter_.iter() != nullptr && |
1988 | 3.59k | !file_iter_.status().IsIncomplete() && |
1989 | 3.59k | new_file_index == file_index_; |
1990 | 8.89k | if (same_file && prefix_filtered_table_ == nullptr) { |
1991 | | // file_iter_ is already constructed with this iterator, so |
1992 | | // no need to change anything |
1993 | 8.88k | } else if (same_file && scan_opts_ == nullptr) { |
1994 | 0 | RetryPrefixFilteredFile(seek_target); |
1995 | 8.88k | } else { |
1996 | 8.88k | file_index_ = new_file_index; |
1997 | 8.88k | if (!prepared_iters_.empty()) { |
1998 | 0 | auto prepared_it = prepared_iters_.find(file_index_); |
1999 | 0 | if (prepared_it != prepared_iters_.end()) { |
2000 | 0 | InternalIterator* iter = prepared_it->second; |
2001 | 0 | prepared_iters_.erase(prepared_it); |
2002 | 0 | SetFileIterator(iter); |
2003 | 0 | return; |
2004 | 0 | } |
2005 | 0 | } |
2006 | | |
2007 | 8.88k | TableReader* prefix_filtered_table = nullptr; |
2008 | 8.88k | InternalIterator* iter = |
2009 | 8.88k | NewFileIterator(seek_target, &prefix_filtered_table); |
2010 | 8.88k | if (FileHasMultiScanArg(file_index_)) { |
2011 | 0 | auto& args = GetMultiScanArgForFile(file_index_); |
2012 | 0 | assert(OverlapRange(*args.GetScanRanges().begin(), file_index_) && |
2013 | 0 | OverlapRange(*args.GetScanRanges().rbegin(), file_index_)); |
2014 | 0 | iter->Prepare(&args); |
2015 | 0 | } |
2016 | | |
2017 | 8.88k | SetFileIterator(iter); |
2018 | 8.88k | prefix_filtered_table_ = prefix_filtered_table; |
2019 | 8.88k | } |
2020 | 8.89k | } |
2021 | 8.96k | } |
2022 | | |
2023 | | // file_iter_ stands in for the file at file_index_ because the file's prefix |
2024 | | // filter ruled out an earlier seek target. Replaces it with a real iterator |
2025 | | // for the file, unless the filter rules out seek_target too. Only the point |
2026 | | // iterator is replaced: TableCache::NewIterator() already set up the file's |
2027 | | // range tombstones, if any, along with the stand-in. |
2028 | 0 | void LevelIterator::RetryPrefixFilteredFile(const Slice* seek_target) { |
2029 | 0 | TEST_SYNC_POINT("LevelIterator::RetryPrefixFilteredFile"); |
2030 | 0 | PERF_TIMER_GUARD(new_table_iterator_nanos); |
2031 | 0 | const bool skip_filters = |
2032 | 0 | skip_filters_ || table_cache_open_options_.skip_filters; |
2033 | 0 | InternalIterator* iter = |
2034 | 0 | seek_target != nullptr |
2035 | 0 | ? prefix_filtered_table_->NewIteratorForSeek( |
2036 | 0 | read_options_, prefix_extractor_, /*arena=*/nullptr, |
2037 | 0 | skip_filters, caller_, file_options_.compaction_readahead_size, |
2038 | 0 | allow_unprepared_value_, *seek_target) |
2039 | 0 | : prefix_filtered_table_->NewIterator( |
2040 | 0 | read_options_, prefix_extractor_, /*arena=*/nullptr, |
2041 | 0 | skip_filters, caller_, file_options_.compaction_readahead_size, |
2042 | 0 | allow_unprepared_value_); |
2043 | 0 | if (iter == nullptr) { |
2044 | 0 | return; |
2045 | 0 | } |
2046 | | // The stand-in holds the table cache handle that keeps the reader alive. |
2047 | 0 | file_iter_.iter()->DelegateCleanupsTo(iter); |
2048 | 0 | SetFileIterator(iter); |
2049 | 0 | } |
2050 | | |
2051 | | } // anonymous namespace |
2052 | | |
2053 | | Status Version::GetTableProperties(const ReadOptions& read_options, |
2054 | | std::shared_ptr<const TableProperties>* tp, |
2055 | | const FileMetaData* file_meta, |
2056 | 104k | const std::string* fname) const { |
2057 | 104k | auto* table_cache = cfd_->table_cache(); |
2058 | 104k | const auto& ioptions = cfd_->ioptions(); |
2059 | 104k | Status s = table_cache->GetTableProperties( |
2060 | 104k | file_options_, read_options, cfd_->internal_comparator(), *file_meta, tp, |
2061 | 104k | mutable_cf_options_, true /* no io */); |
2062 | 104k | if (s.ok()) { |
2063 | 104k | return s; |
2064 | 104k | } |
2065 | | |
2066 | | // We only ignore error type `Incomplete` since it's by design that we |
2067 | | // disallow table when it's not in table cache. |
2068 | 0 | if (!s.IsIncomplete()) { |
2069 | 0 | return s; |
2070 | 0 | } |
2071 | | |
2072 | | // 2. Table is not present in table cache, we'll read the table properties |
2073 | | // directly from the properties block in the file. |
2074 | 0 | std::unique_ptr<FSRandomAccessFile> file; |
2075 | 0 | std::string file_name; |
2076 | 0 | if (fname != nullptr) { |
2077 | 0 | file_name = *fname; |
2078 | 0 | } else { |
2079 | 0 | file_name = TableFileName(ioptions.cf_paths, file_meta->fd.GetNumber(), |
2080 | 0 | file_meta->fd.GetPathId()); |
2081 | 0 | } |
2082 | 0 | FileOptions fopts = file_options_; |
2083 | 0 | fopts.file_checksum = file_meta->file_checksum; |
2084 | 0 | fopts.file_checksum_func_name = file_meta->file_checksum_func_name; |
2085 | 0 | s = ioptions.fs->NewRandomAccessFile(file_name, fopts, &file, nullptr); |
2086 | 0 | if (!s.ok()) { |
2087 | 0 | return s; |
2088 | 0 | } |
2089 | | |
2090 | | // By setting the magic number to kNullTableMagicNumber, we can bypass |
2091 | | // the magic number check in the footer. |
2092 | 0 | std::unique_ptr<RandomAccessFileReader> file_reader( |
2093 | 0 | new RandomAccessFileReader( |
2094 | 0 | std::move(file), file_name, ioptions.clock /* clock */, io_tracer_, |
2095 | 0 | ioptions.stats /* stats */, |
2096 | 0 | Histograms::SST_READ_MICROS /* hist_type */, |
2097 | 0 | nullptr /* file_read_hist */, nullptr /* rate_limiter */, |
2098 | 0 | ioptions.listeners)); |
2099 | 0 | std::unique_ptr<TableProperties> props; |
2100 | 0 | s = ReadTableProperties( |
2101 | 0 | file_reader.get(), file_meta->fd.GetFileSize(), |
2102 | 0 | Footer::kNullTableMagicNumber /* table's magic number */, ioptions, |
2103 | 0 | read_options, &props); |
2104 | 0 | if (!s.ok()) { |
2105 | 0 | return s; |
2106 | 0 | } |
2107 | 0 | *tp = std::move(props); |
2108 | 0 | RecordTick(ioptions.stats, NUMBER_DIRECT_LOAD_TABLE_PROPERTIES); |
2109 | 0 | return s; |
2110 | 0 | } |
2111 | | |
2112 | | Status Version::GetPropertiesOfAllTables( |
2113 | 0 | const ReadOptions& read_options, TablePropertiesCollection* props) const { |
2114 | 0 | Status s; |
2115 | 0 | for (int level = 0; level < storage_info_.num_levels_; level++) { |
2116 | 0 | s = GetPropertiesOfAllTables(read_options, props, level); |
2117 | 0 | if (!s.ok()) { |
2118 | 0 | return s; |
2119 | 0 | } |
2120 | 0 | } |
2121 | | |
2122 | 0 | return Status::OK(); |
2123 | 0 | } |
2124 | | |
2125 | | Status Version::TablesRangeTombstoneSummary(int max_entries_to_print, |
2126 | 0 | std::string* out_str) { |
2127 | 0 | if (max_entries_to_print <= 0) { |
2128 | 0 | return Status::OK(); |
2129 | 0 | } |
2130 | 0 | int num_entries_left = max_entries_to_print; |
2131 | |
|
2132 | 0 | std::stringstream ss; |
2133 | | |
2134 | | // TODO: plumb Env::IOActivity, Env::IOPriority |
2135 | 0 | const ReadOptions read_options; |
2136 | 0 | for (int level = 0; level < storage_info_.num_levels_; level++) { |
2137 | 0 | for (const auto& file_meta : storage_info_.files_[level]) { |
2138 | 0 | auto fname = |
2139 | 0 | TableFileName(cfd_->ioptions().cf_paths, file_meta->fd.GetNumber(), |
2140 | 0 | file_meta->fd.GetPathId()); |
2141 | |
|
2142 | 0 | ss << "=== file : " << fname << " ===\n"; |
2143 | |
|
2144 | 0 | TableCache* table_cache = cfd_->table_cache(); |
2145 | 0 | std::unique_ptr<FragmentedRangeTombstoneIterator> tombstone_iter; |
2146 | |
|
2147 | 0 | Status s = table_cache->GetRangeTombstoneIterator( |
2148 | 0 | read_options, cfd_->internal_comparator(), *file_meta, |
2149 | 0 | mutable_cf_options_, &tombstone_iter); |
2150 | 0 | if (!s.ok()) { |
2151 | 0 | return s; |
2152 | 0 | } |
2153 | 0 | if (tombstone_iter) { |
2154 | 0 | tombstone_iter->SeekToFirst(); |
2155 | | |
2156 | | // TODO: print timestamp |
2157 | 0 | while (tombstone_iter->Valid() && num_entries_left > 0) { |
2158 | 0 | ss << "start: " << tombstone_iter->start_key().ToString(true) |
2159 | 0 | << " end: " << tombstone_iter->end_key().ToString(true) |
2160 | 0 | << " seq: " << tombstone_iter->seq() << '\n'; |
2161 | 0 | tombstone_iter->Next(); |
2162 | 0 | num_entries_left--; |
2163 | 0 | } |
2164 | 0 | if (num_entries_left <= 0) { |
2165 | 0 | break; |
2166 | 0 | } |
2167 | 0 | } |
2168 | 0 | } |
2169 | 0 | if (num_entries_left <= 0) { |
2170 | 0 | break; |
2171 | 0 | } |
2172 | 0 | } |
2173 | 0 | assert(num_entries_left >= 0); |
2174 | 0 | if (num_entries_left <= 0) { |
2175 | 0 | ss << "(results may not be complete)\n"; |
2176 | 0 | } |
2177 | |
|
2178 | 0 | *out_str = ss.str(); |
2179 | 0 | return Status::OK(); |
2180 | 0 | } |
2181 | | |
2182 | | Status Version::GetPropertiesOfAllTables(const ReadOptions& read_options, |
2183 | | TablePropertiesCollection* props, |
2184 | 0 | int level) const { |
2185 | 0 | for (const auto& file_meta : storage_info_.files_[level]) { |
2186 | 0 | auto fname = |
2187 | 0 | TableFileName(cfd_->ioptions().cf_paths, file_meta->fd.GetNumber(), |
2188 | 0 | file_meta->fd.GetPathId()); |
2189 | | // 1. If the table is already present in table cache, load table |
2190 | | // properties from there. |
2191 | 0 | std::shared_ptr<const TableProperties> table_properties; |
2192 | 0 | Status s = |
2193 | 0 | GetTableProperties(read_options, &table_properties, file_meta, &fname); |
2194 | 0 | if (s.ok()) { |
2195 | 0 | props->insert({fname, table_properties}); |
2196 | 0 | } else { |
2197 | 0 | return s; |
2198 | 0 | } |
2199 | 0 | } |
2200 | | |
2201 | 0 | return Status::OK(); |
2202 | 0 | } |
2203 | | |
2204 | | Status Version::GetPropertiesOfTablesInRange( |
2205 | | const ReadOptions& read_options, const autovector<UserKeyRange>& ranges, |
2206 | 0 | TablePropertiesCollection* props) const { |
2207 | 0 | for (int level = 0; level < storage_info_.num_non_empty_levels(); level++) { |
2208 | 0 | for (const auto& range : ranges) { |
2209 | | // Convert user_key into a corresponding internal key. |
2210 | 0 | InternalKey k1(range.start, kMaxSequenceNumber, kValueTypeForSeek); |
2211 | 0 | InternalKey k2(range.limit, kMaxSequenceNumber, kValueTypeForSeek); |
2212 | 0 | std::vector<FileMetaData*> files; |
2213 | 0 | storage_info_.GetOverlappingInputs(level, &k1, &k2, &files, -1, nullptr, |
2214 | 0 | false); |
2215 | 0 | for (const auto& file_meta : files) { |
2216 | 0 | auto fname = |
2217 | 0 | TableFileName(cfd_->ioptions().cf_paths, file_meta->fd.GetNumber(), |
2218 | 0 | file_meta->fd.GetPathId()); |
2219 | 0 | if (props->count(fname) == 0) { |
2220 | | // 1. If the table is already present in table cache, load table |
2221 | | // properties from there. |
2222 | 0 | std::shared_ptr<const TableProperties> table_properties; |
2223 | 0 | Status s = GetTableProperties(read_options, &table_properties, |
2224 | 0 | file_meta, &fname); |
2225 | 0 | if (s.ok()) { |
2226 | 0 | props->insert({fname, table_properties}); |
2227 | 0 | } else { |
2228 | 0 | return s; |
2229 | 0 | } |
2230 | 0 | } |
2231 | 0 | } |
2232 | 0 | } |
2233 | 0 | } |
2234 | | |
2235 | 0 | return Status::OK(); |
2236 | 0 | } |
2237 | | |
2238 | | Status Version::GetPropertiesOfTablesByLevel( |
2239 | | const ReadOptions& read_options, |
2240 | | std::vector<std::unique_ptr<TablePropertiesCollection>>* props_by_level) |
2241 | 0 | const { |
2242 | 0 | Status s; |
2243 | |
|
2244 | 0 | props_by_level->reserve(storage_info_.num_levels_); |
2245 | 0 | for (int level = 0; level < storage_info_.num_levels_; level++) { |
2246 | 0 | props_by_level->push_back(std::make_unique<TablePropertiesCollection>()); |
2247 | 0 | s = GetPropertiesOfAllTables(read_options, props_by_level->back().get(), |
2248 | 0 | level); |
2249 | 0 | if (!s.ok()) { |
2250 | 0 | return s; |
2251 | 0 | } |
2252 | 0 | } |
2253 | 0 | return Status::OK(); |
2254 | 0 | } |
2255 | | |
2256 | | Status Version::GetAggregatedTableProperties( |
2257 | | const ReadOptions& read_options, std::shared_ptr<const TableProperties>* tp, |
2258 | 0 | int level) { |
2259 | 0 | TablePropertiesCollection props; |
2260 | 0 | Status s; |
2261 | 0 | if (level < 0) { |
2262 | 0 | s = GetPropertiesOfAllTables(read_options, &props); |
2263 | 0 | } else { |
2264 | 0 | s = GetPropertiesOfAllTables(read_options, &props, level); |
2265 | 0 | } |
2266 | 0 | if (!s.ok()) { |
2267 | 0 | return s; |
2268 | 0 | } |
2269 | | |
2270 | 0 | auto* new_tp = new TableProperties(); |
2271 | 0 | for (const auto& item : props) { |
2272 | 0 | new_tp->Add(*item.second); |
2273 | 0 | } |
2274 | 0 | tp->reset(new_tp); |
2275 | 0 | return Status::OK(); |
2276 | 0 | } |
2277 | | |
2278 | 0 | size_t Version::GetMemoryUsageByTableReaders(const ReadOptions& read_options) { |
2279 | 0 | size_t total_usage = 0; |
2280 | 0 | for (auto& file_level : storage_info_.level_files_brief_) { |
2281 | 0 | for (size_t i = 0; i < file_level.num_files; i++) { |
2282 | 0 | total_usage += cfd_->table_cache()->GetMemoryUsageByTableReader( |
2283 | 0 | file_options_, read_options, cfd_->internal_comparator(), |
2284 | 0 | *file_level.files[i].file_metadata, mutable_cf_options_); |
2285 | 0 | } |
2286 | 0 | } |
2287 | 0 | return total_usage; |
2288 | 0 | } |
2289 | | |
2290 | 60.5k | void Version::GetColumnFamilyMetaData(ColumnFamilyMetaData* cf_meta) { |
2291 | 60.5k | assert(cf_meta); |
2292 | 60.5k | assert(cfd_); |
2293 | | |
2294 | 60.5k | cf_meta->name = cfd_->GetName(); |
2295 | 60.5k | cf_meta->size = 0; |
2296 | 60.5k | cf_meta->file_count = 0; |
2297 | 60.5k | cf_meta->levels.clear(); |
2298 | | |
2299 | 60.5k | cf_meta->blob_file_size = 0; |
2300 | 60.5k | cf_meta->blob_file_count = 0; |
2301 | 60.5k | cf_meta->blob_files.clear(); |
2302 | | |
2303 | 60.5k | const auto& ioptions = cfd_->ioptions(); |
2304 | 60.5k | auto* vstorage = storage_info(); |
2305 | | |
2306 | 484k | for (int level = 0; level < cfd_->NumberLevels(); level++) { |
2307 | 423k | uint64_t level_size = 0; |
2308 | 423k | cf_meta->file_count += vstorage->LevelFiles(level).size(); |
2309 | 423k | std::vector<SstFileMetaData> files; |
2310 | 423k | for (const auto& file : vstorage->LevelFiles(level)) { |
2311 | 84.8k | uint32_t path_id = file->fd.GetPathId(); |
2312 | 84.8k | std::string file_path; |
2313 | 84.8k | if (path_id < ioptions.cf_paths.size()) { |
2314 | 84.8k | file_path = ioptions.cf_paths[path_id].path; |
2315 | 84.8k | } else { |
2316 | 0 | assert(!ioptions.cf_paths.empty()); |
2317 | 0 | file_path = ioptions.cf_paths.back().path; |
2318 | 0 | } |
2319 | 84.8k | const uint64_t file_number = file->fd.GetNumber(); |
2320 | 84.8k | files.emplace_back( |
2321 | 84.8k | MakeTableFileName("", file_number), file_number, file_path, |
2322 | 84.8k | file->fd.GetFileSize(), file->fd.smallest_seqno, |
2323 | 84.8k | file->fd.largest_seqno, file->smallest.user_key().ToString(), |
2324 | 84.8k | file->largest.user_key().ToString(), |
2325 | 84.8k | file->stats.num_reads_sampled.load(std::memory_order_relaxed), |
2326 | 84.8k | file->being_compacted, file->temperature, |
2327 | 84.8k | file->oldest_blob_file_number, file->TryGetOldestAncestorTime(), |
2328 | 84.8k | file->TryGetFileCreationTime(), file->epoch_number, |
2329 | 84.8k | file->file_checksum, file->file_checksum_func_name); |
2330 | 84.8k | files.back().num_entries = file->num_entries; |
2331 | 84.8k | files.back().num_deletions = file->num_deletions; |
2332 | 84.8k | files.back().db_ownership_start_time = file->db_ownership_start_time; |
2333 | 84.8k | files.back().data_file_provenance = file->data_file_provenance; |
2334 | 84.8k | files.back().smallest = file->smallest.Encode().ToString(); |
2335 | 84.8k | files.back().largest = file->largest.Encode().ToString(); |
2336 | 84.8k | level_size += file->fd.GetFileSize(); |
2337 | 84.8k | } |
2338 | 423k | cf_meta->levels.emplace_back(level, level_size, std::move(files)); |
2339 | 423k | cf_meta->size += level_size; |
2340 | 423k | } |
2341 | 60.5k | for (const auto& meta : vstorage->GetBlobFiles()) { |
2342 | 0 | assert(meta); |
2343 | |
|
2344 | 0 | cf_meta->blob_files.emplace_back( |
2345 | 0 | meta->GetBlobFileNumber(), BlobFileName("", meta->GetBlobFileNumber()), |
2346 | 0 | ioptions.cf_paths.front().path, meta->GetBlobFileSize(), |
2347 | 0 | meta->GetTotalBlobCount(), meta->GetTotalBlobBytes(), |
2348 | 0 | meta->GetGarbageBlobCount(), meta->GetGarbageBlobBytes(), |
2349 | 0 | meta->GetChecksumMethod(), meta->GetChecksumValue()); |
2350 | 0 | ++cf_meta->blob_file_count; |
2351 | 0 | cf_meta->blob_file_size += meta->GetBlobFileSize(); |
2352 | 0 | } |
2353 | 60.5k | } |
2354 | | |
2355 | | void Version::GetColumnFamilyMetaData( |
2356 | | const GetColumnFamilyMetaDataOptions& options, |
2357 | 0 | ColumnFamilyMetaData* cf_meta) { |
2358 | 0 | assert(cf_meta); |
2359 | 0 | assert(cfd_); |
2360 | |
|
2361 | 0 | cf_meta->name = cfd_->GetName(); |
2362 | 0 | cf_meta->size = 0; |
2363 | 0 | cf_meta->file_count = 0; |
2364 | 0 | cf_meta->levels.clear(); |
2365 | 0 | cf_meta->blob_file_size = 0; |
2366 | 0 | cf_meta->blob_file_count = 0; |
2367 | 0 | cf_meta->blob_files.clear(); |
2368 | |
|
2369 | 0 | const auto& ioptions = cfd_->ioptions(); |
2370 | 0 | auto* vstorage = storage_info(); |
2371 | |
|
2372 | 0 | int first_level = (options.level >= 0) ? options.level : 0; |
2373 | 0 | int last_level = |
2374 | 0 | (options.level >= 0) ? options.level + 1 : cfd_->NumberLevels(); |
2375 | |
|
2376 | 0 | InternalKey ikey_start; |
2377 | 0 | InternalKey ikey_end; |
2378 | 0 | const InternalKey* begin = nullptr; |
2379 | 0 | const InternalKey* end = nullptr; |
2380 | |
|
2381 | 0 | if (options.range.start.has_value()) { |
2382 | 0 | ikey_start = InternalKey(options.range.start.value(), kMaxSequenceNumber, |
2383 | 0 | kValueTypeForSeek); |
2384 | 0 | begin = &ikey_start; |
2385 | 0 | } |
2386 | |
|
2387 | 0 | if (options.range.limit.has_value()) { |
2388 | 0 | ikey_end = InternalKey(options.range.limit.value(), kMaxSequenceNumber, |
2389 | 0 | kValueTypeForSeek); |
2390 | 0 | end = &ikey_end; |
2391 | 0 | } |
2392 | |
|
2393 | 0 | for (int l = first_level; l < last_level; ++l) { |
2394 | 0 | uint64_t level_size = 0; |
2395 | 0 | std::vector<SstFileMetaData> files; |
2396 | 0 | std::vector<FileMetaData*> overlapping_files; |
2397 | 0 | vstorage->GetOverlappingInputs(l, begin, end, &overlapping_files); |
2398 | |
|
2399 | 0 | for (const auto& file : overlapping_files) { |
2400 | 0 | uint32_t path_id = file->fd.GetPathId(); |
2401 | 0 | const auto& file_path = (path_id < ioptions.cf_paths.size()) |
2402 | 0 | ? ioptions.cf_paths[path_id].path |
2403 | 0 | : ioptions.cf_paths.back().path; |
2404 | 0 | const uint64_t file_number = file->fd.GetNumber(); |
2405 | 0 | files.emplace_back( |
2406 | 0 | MakeTableFileName("", file_number), file_number, file_path, |
2407 | 0 | file->fd.GetFileSize(), file->fd.smallest_seqno, |
2408 | 0 | file->fd.largest_seqno, file->smallest.user_key().ToString(), |
2409 | 0 | file->largest.user_key().ToString(), |
2410 | 0 | file->stats.num_reads_sampled.load(std::memory_order_relaxed), |
2411 | 0 | file->being_compacted, file->temperature, |
2412 | 0 | file->oldest_blob_file_number, file->TryGetOldestAncestorTime(), |
2413 | 0 | file->TryGetFileCreationTime(), file->epoch_number, |
2414 | 0 | file->file_checksum, file->file_checksum_func_name); |
2415 | 0 | files.back().num_entries = file->num_entries; |
2416 | 0 | files.back().num_deletions = file->num_deletions; |
2417 | 0 | files.back().db_ownership_start_time = file->db_ownership_start_time; |
2418 | 0 | files.back().data_file_provenance = file->data_file_provenance; |
2419 | 0 | files.back().smallest = file->smallest.Encode().ToString(); |
2420 | 0 | files.back().largest = file->largest.Encode().ToString(); |
2421 | 0 | level_size += file->fd.GetFileSize(); |
2422 | 0 | cf_meta->file_count++; |
2423 | 0 | } |
2424 | 0 | if (!files.empty()) { |
2425 | 0 | cf_meta->levels.emplace_back(l, level_size, std::move(files)); |
2426 | 0 | cf_meta->size += level_size; |
2427 | 0 | } |
2428 | 0 | } |
2429 | 0 | } |
2430 | | |
2431 | 0 | uint64_t Version::GetSstFilesSize() { |
2432 | 0 | uint64_t sst_files_size = 0; |
2433 | 0 | for (int level = 0; level < storage_info_.num_levels_; level++) { |
2434 | 0 | for (const auto& file_meta : storage_info_.LevelFiles(level)) { |
2435 | 0 | sst_files_size += file_meta->fd.GetFileSize(); |
2436 | 0 | } |
2437 | 0 | } |
2438 | 0 | return sst_files_size; |
2439 | 0 | } |
2440 | | |
2441 | | void Version::GetSstFilesBoundaryKeys(Slice* smallest_user_key, |
2442 | 0 | Slice* largest_user_key) { |
2443 | 0 | smallest_user_key->clear(); |
2444 | 0 | largest_user_key->clear(); |
2445 | 0 | bool initialized = false; |
2446 | 0 | const Comparator* ucmp = storage_info_.user_comparator_; |
2447 | 0 | for (int level = 0; level < cfd_->NumberLevels(); level++) { |
2448 | 0 | if (storage_info_.LevelFiles(level).size() == 0) { |
2449 | 0 | continue; |
2450 | 0 | } |
2451 | 0 | if (level == 0) { |
2452 | | // we need to consider all files on level 0 |
2453 | 0 | for (const auto& file : storage_info_.LevelFiles(level)) { |
2454 | 0 | const Slice& start_user_key = file->smallest.user_key(); |
2455 | 0 | if (!initialized || |
2456 | 0 | ucmp->Compare(start_user_key, *smallest_user_key) < 0) { |
2457 | 0 | *smallest_user_key = start_user_key; |
2458 | 0 | } |
2459 | 0 | const Slice& end_user_key = file->largest.user_key(); |
2460 | 0 | if (!initialized || |
2461 | 0 | ucmp->Compare(end_user_key, *largest_user_key) > 0) { |
2462 | 0 | *largest_user_key = end_user_key; |
2463 | 0 | } |
2464 | 0 | initialized = true; |
2465 | 0 | } |
2466 | 0 | } else { |
2467 | | // we only need to consider the first and last file |
2468 | 0 | const Slice& start_user_key = |
2469 | 0 | storage_info_.LevelFiles(level)[0]->smallest.user_key(); |
2470 | 0 | if (!initialized || |
2471 | 0 | ucmp->Compare(start_user_key, *smallest_user_key) < 0) { |
2472 | 0 | *smallest_user_key = start_user_key; |
2473 | 0 | } |
2474 | 0 | const Slice& end_user_key = |
2475 | 0 | storage_info_.LevelFiles(level).back()->largest.user_key(); |
2476 | 0 | if (!initialized || ucmp->Compare(end_user_key, *largest_user_key) > 0) { |
2477 | 0 | *largest_user_key = end_user_key; |
2478 | 0 | } |
2479 | 0 | initialized = true; |
2480 | 0 | } |
2481 | 0 | } |
2482 | 0 | } |
2483 | | |
2484 | 0 | void Version::GetCreationTimeOfOldestFile(uint64_t* creation_time) { |
2485 | 0 | uint64_t oldest_time = std::numeric_limits<uint64_t>::max(); |
2486 | 0 | for (int level = 0; level < storage_info_.num_non_empty_levels_; level++) { |
2487 | 0 | for (FileMetaData* meta : storage_info_.LevelFiles(level)) { |
2488 | 0 | uint64_t file_creation_time = meta->TryGetFileCreationTime(); |
2489 | 0 | TEST_SYNC_POINT_CALLBACK( |
2490 | 0 | "Version::GetCreationTimeOfOldestFile::FileCreationTime", |
2491 | 0 | &file_creation_time); |
2492 | 0 | if (file_creation_time == kUnknownFileCreationTime) { |
2493 | 0 | *creation_time = 0; |
2494 | 0 | return; |
2495 | 0 | } |
2496 | 0 | if (file_creation_time < oldest_time) { |
2497 | 0 | oldest_time = file_creation_time; |
2498 | 0 | } |
2499 | 0 | } |
2500 | 0 | } |
2501 | 0 | *creation_time = oldest_time; |
2502 | 0 | } |
2503 | | |
2504 | | InternalIterator* Version::TEST_GetLevelIterator( |
2505 | | const ReadOptions& read_options, MergeIteratorBuilder* merge_iter_builder, |
2506 | 0 | int level, bool allow_unprepared_value) { |
2507 | 0 | auto* arena = merge_iter_builder->GetArena(); |
2508 | 0 | auto* mem = arena->AllocateAligned(sizeof(LevelIterator)); |
2509 | 0 | std::unique_ptr<TruncatedRangeDelIterator>** tombstone_iter_ptr = nullptr; |
2510 | 0 | auto level_iter = new (mem) LevelIterator( |
2511 | 0 | cfd_->table_cache(), read_options, file_options_, |
2512 | 0 | cfd_->internal_comparator(), &storage_info_.LevelFilesBrief(level), |
2513 | 0 | mutable_cf_options_, cfd_->internal_stats()->GetFileReadHist(level), |
2514 | 0 | TableReaderCaller::kUserIterator, IsFilterSkipped(level), level, |
2515 | 0 | nullptr /* range_del_agg */, nullptr /* compaction_boundaries */, |
2516 | 0 | allow_unprepared_value, |
2517 | 0 | read_options.ignore_range_deletions ? nullptr : &tombstone_iter_ptr, |
2518 | 0 | kMaxSequenceNumber, db_statistics_, clock_); |
2519 | 0 | if (read_options.ignore_range_deletions) { |
2520 | 0 | merge_iter_builder->AddIterator(level_iter); |
2521 | 0 | } else { |
2522 | 0 | merge_iter_builder->AddPointAndTombstoneIterator( |
2523 | 0 | level_iter, nullptr /* tombstone_iter */, tombstone_iter_ptr); |
2524 | 0 | } |
2525 | 0 | return level_iter; |
2526 | 0 | } |
2527 | | |
2528 | 0 | uint64_t VersionStorageInfo::GetEstimatedActiveKeys() const { |
2529 | | // Estimation will be inaccurate when: |
2530 | | // (1) there exist merge keys |
2531 | | // (2) keys are directly overwritten |
2532 | | // (3) deletion on non-existing keys |
2533 | | // (4) low number of samples |
2534 | 0 | if (current_num_samples_ == 0) { |
2535 | 0 | return 0; |
2536 | 0 | } |
2537 | | |
2538 | 0 | if (current_num_non_deletions_ <= current_num_deletions_) { |
2539 | 0 | return 0; |
2540 | 0 | } |
2541 | | |
2542 | 0 | uint64_t est = current_num_non_deletions_ - current_num_deletions_; |
2543 | |
|
2544 | 0 | uint64_t file_count = 0; |
2545 | 0 | for (int level = 0; level < num_levels_; ++level) { |
2546 | 0 | file_count += files_[level].size(); |
2547 | 0 | } |
2548 | |
|
2549 | 0 | if (current_num_samples_ < file_count) { |
2550 | 0 | assert(current_num_samples_ != 0); |
2551 | 0 | assert(est != 0); |
2552 | 0 | double multiplier = static_cast<double>(file_count) / current_num_samples_; |
2553 | 0 | double maximum_multiplier = |
2554 | 0 | static_cast<double>(std::numeric_limits<uint64_t>::max()) / est; |
2555 | | // If it can overflow, we return the maximum unsigned long. |
2556 | 0 | if (multiplier >= maximum_multiplier) { |
2557 | 0 | return std::numeric_limits<uint64_t>::max(); |
2558 | 0 | } |
2559 | 0 | return static_cast<uint64_t>(est * multiplier); |
2560 | 0 | } else { |
2561 | 0 | return est; |
2562 | 0 | } |
2563 | 0 | } |
2564 | | |
2565 | | double VersionStorageInfo::GetEstimatedCompressionRatioAtLevel( |
2566 | 0 | int level) const { |
2567 | 0 | assert(level < num_levels_); |
2568 | 0 | uint64_t sum_file_size_bytes = 0; |
2569 | 0 | uint64_t sum_data_size_bytes = 0; |
2570 | 0 | for (auto* file_meta : files_[level]) { |
2571 | 0 | auto raw_size = file_meta->raw_key_size + file_meta->raw_value_size; |
2572 | | // Check if the table property is properly initialized. It might not be |
2573 | | // because in `UpdateAccumulatedStats` we limit the maximum number of |
2574 | | // properties to read once. |
2575 | 0 | if (raw_size > 0) { |
2576 | 0 | sum_file_size_bytes += file_meta->fd.GetFileSize(); |
2577 | 0 | sum_data_size_bytes += raw_size; |
2578 | 0 | } |
2579 | 0 | } |
2580 | 0 | if (sum_file_size_bytes == 0) { |
2581 | 0 | return -1.0; |
2582 | 0 | } |
2583 | 0 | return static_cast<double>(sum_data_size_bytes) / sum_file_size_bytes; |
2584 | 0 | } |
2585 | | |
2586 | | void Version::AddIterators(const ReadOptions& read_options, |
2587 | | const FileOptions& soptions, |
2588 | | MergeIteratorBuilder* merge_iter_builder, |
2589 | | bool allow_unprepared_value, SequenceNumber read_seq, |
2590 | 9.76k | const MultiScanArgs* scan_opts) { |
2591 | 9.76k | assert(storage_info_.finalized_); |
2592 | | |
2593 | 33.0k | for (int level = 0; level < storage_info_.num_non_empty_levels(); level++) { |
2594 | 23.2k | AddIteratorsForLevel(read_options, soptions, merge_iter_builder, level, |
2595 | 23.2k | allow_unprepared_value, read_seq, scan_opts); |
2596 | 23.2k | } |
2597 | 9.76k | } |
2598 | | |
2599 | | void Version::AddIteratorsForLevel(const ReadOptions& read_options, |
2600 | | const FileOptions& soptions, |
2601 | | MergeIteratorBuilder* merge_iter_builder, |
2602 | | int level, bool allow_unprepared_value, |
2603 | | SequenceNumber read_seq, |
2604 | 23.2k | const MultiScanArgs* scan_opts) { |
2605 | 23.2k | assert(storage_info_.finalized_); |
2606 | 23.2k | if (level >= storage_info_.num_non_empty_levels()) { |
2607 | | // This is an empty level |
2608 | 0 | return; |
2609 | 23.2k | } else if (storage_info_.LevelFilesBrief(level).num_files == 0) { |
2610 | | // No files in this level |
2611 | 13.4k | return; |
2612 | 13.4k | } |
2613 | | |
2614 | 9.84k | auto* arena = merge_iter_builder->GetArena(); |
2615 | 9.84k | const LevelFilesBrief& file_level = storage_info_.LevelFilesBrief(level); |
2616 | 9.84k | const UserComparatorWrapper user_comparator( |
2617 | 9.84k | cfd_->internal_comparator().user_comparator()); |
2618 | | |
2619 | 9.84k | if (scan_opts != nullptr) { |
2620 | 0 | const std::vector<size_t> file_indexes = GetMultiScanOverlappingFiles( |
2621 | 0 | cfd_->internal_comparator(), user_comparator, file_level, level, |
2622 | 0 | *scan_opts, level == 0 ? std::numeric_limits<size_t>::max() : 2); |
2623 | 0 | if (file_indexes.empty()) { |
2624 | 0 | return; |
2625 | 0 | } |
2626 | | |
2627 | 0 | if (level == 0) { |
2628 | 0 | const bool should_sample = should_sample_file_read(); |
2629 | 0 | for (size_t file_index : file_indexes) { |
2630 | 0 | AddTableIteratorForLevel( |
2631 | 0 | cfd_, read_options, soptions, mutable_cf_options_, |
2632 | 0 | merge_iter_builder, arena, file_level, level, file_index, |
2633 | 0 | /*skip_filters=*/false, max_file_size_for_l0_meta_pin_, |
2634 | 0 | allow_unprepared_value, read_seq); |
2635 | 0 | if (should_sample) { |
2636 | 0 | sample_file_read_inc(file_level.files[file_index].file_metadata); |
2637 | 0 | } |
2638 | 0 | } |
2639 | 0 | return; |
2640 | 0 | } |
2641 | | |
2642 | 0 | if (file_indexes.size() == 1) { |
2643 | 0 | AddTableIteratorForLevel(cfd_, read_options, soptions, |
2644 | 0 | mutable_cf_options_, merge_iter_builder, arena, |
2645 | 0 | file_level, level, file_indexes.front(), |
2646 | 0 | IsFilterSkipped(level), |
2647 | 0 | /*max_file_size_for_l0_meta_pin=*/0, |
2648 | 0 | allow_unprepared_value, read_seq); |
2649 | 0 | return; |
2650 | 0 | } |
2651 | | |
2652 | | #ifndef NDEBUG |
2653 | | for (size_t file_index = 0; file_index < file_level.num_files; |
2654 | | ++file_index) { |
2655 | | TEST_SYNC_POINT_CALLBACK("Version::AddIteratorsForLevel:AddedFile", |
2656 | | file_level.files[file_index].file_metadata); |
2657 | | } |
2658 | | #endif |
2659 | 0 | auto* mem = arena->AllocateAligned(sizeof(LevelIterator)); |
2660 | 0 | std::unique_ptr<TruncatedRangeDelIterator>** tombstone_iter_ptr = nullptr; |
2661 | 0 | auto level_iter = new (mem) LevelIterator( |
2662 | 0 | cfd_->table_cache(), read_options, soptions, |
2663 | 0 | cfd_->internal_comparator(), &file_level, mutable_cf_options_, |
2664 | 0 | cfd_->internal_stats()->GetFileReadHist(level), |
2665 | 0 | TableReaderCaller::kUserIterator, IsFilterSkipped(level), level, |
2666 | 0 | /*range_del_agg=*/nullptr, |
2667 | 0 | /*compaction_boundaries=*/nullptr, allow_unprepared_value, |
2668 | 0 | read_options.ignore_range_deletions ? nullptr : &tombstone_iter_ptr, |
2669 | 0 | read_seq, db_statistics_, clock_); |
2670 | | #ifndef NDEBUG |
2671 | | std::pair<bool, bool> iterator_type( |
2672 | | false /* is_block_based_table_iterator */, |
2673 | | true /* is_level_iterator */); |
2674 | | TEST_SYNC_POINT_CALLBACK("Version::AddIteratorsForLevel:IteratorType", |
2675 | | &iterator_type); |
2676 | | #endif |
2677 | 0 | if (read_options.ignore_range_deletions) { |
2678 | 0 | merge_iter_builder->AddIterator(level_iter); |
2679 | 0 | } else { |
2680 | 0 | assert(tombstone_iter_ptr); |
2681 | 0 | merge_iter_builder->AddPointAndTombstoneIterator( |
2682 | 0 | level_iter, nullptr /* tombstone_iter */, tombstone_iter_ptr); |
2683 | 0 | } |
2684 | 0 | return; |
2685 | 0 | } |
2686 | | |
2687 | 9.84k | if (level == 0) { |
2688 | | // Merge all level zero files together since they may overlap |
2689 | 7.37k | const bool should_sample = should_sample_file_read(); |
2690 | 17.7k | for (size_t i = 0; i < file_level.num_files; i++) { |
2691 | 10.3k | AddTableIteratorForLevel( |
2692 | 10.3k | cfd_, read_options, soptions, mutable_cf_options_, merge_iter_builder, |
2693 | 10.3k | arena, file_level, level, i, |
2694 | 10.3k | /*skip_filters=*/false, max_file_size_for_l0_meta_pin_, |
2695 | 10.3k | allow_unprepared_value, read_seq); |
2696 | 10.3k | if (should_sample) { |
2697 | | // Count once for every L0 file. This is done per iterator creation |
2698 | | // rather than Seek(), while files in other levels are sampled on |
2699 | | // seek/next/prev. |
2700 | 8 | sample_file_read_inc(file_level.files[i].file_metadata); |
2701 | 8 | } |
2702 | 10.3k | } |
2703 | 7.37k | } else if (file_level.num_files > 0) { |
2704 | | // For levels > 0, we can use a concatenating iterator that sequentially |
2705 | | // walks through the non-overlapping files in the level, opening them |
2706 | | // lazily. |
2707 | 2.46k | auto* mem = arena->AllocateAligned(sizeof(LevelIterator)); |
2708 | 2.46k | std::unique_ptr<TruncatedRangeDelIterator>** tombstone_iter_ptr = nullptr; |
2709 | 2.46k | auto level_iter = new (mem) LevelIterator( |
2710 | 2.46k | cfd_->table_cache(), read_options, soptions, |
2711 | 2.46k | cfd_->internal_comparator(), &file_level, mutable_cf_options_, |
2712 | 2.46k | cfd_->internal_stats()->GetFileReadHist(level), |
2713 | 2.46k | TableReaderCaller::kUserIterator, IsFilterSkipped(level), level, |
2714 | 2.46k | /*range_del_agg=*/nullptr, |
2715 | 2.46k | /*compaction_boundaries=*/nullptr, allow_unprepared_value, |
2716 | 2.46k | read_options.ignore_range_deletions ? nullptr : &tombstone_iter_ptr, |
2717 | 2.46k | read_seq, db_statistics_, clock_); |
2718 | 2.46k | if (read_options.ignore_range_deletions) { |
2719 | 0 | merge_iter_builder->AddIterator(level_iter); |
2720 | 2.46k | } else { |
2721 | 2.46k | merge_iter_builder->AddPointAndTombstoneIterator( |
2722 | 2.46k | level_iter, nullptr /* tombstone_iter */, tombstone_iter_ptr); |
2723 | 2.46k | } |
2724 | 2.46k | } |
2725 | 9.84k | } |
2726 | | |
2727 | | Status Version::OverlapWithLevelIterator(const ReadOptions& read_options, |
2728 | | const FileOptions& file_options, |
2729 | | const Slice& smallest_user_key, |
2730 | | const Slice& largest_user_key, |
2731 | 8.51k | int level, bool* overlap) { |
2732 | 8.51k | assert(storage_info_.finalized_); |
2733 | | |
2734 | 8.51k | auto icmp = cfd_->internal_comparator(); |
2735 | 8.51k | auto ucmp = icmp.user_comparator(); |
2736 | | |
2737 | 8.51k | Arena arena; |
2738 | 8.51k | Status status; |
2739 | 8.51k | ReadRangeDelAggregator range_del_agg(&icmp, |
2740 | 8.51k | kMaxSequenceNumber /* upper_bound */); |
2741 | | |
2742 | 8.51k | *overlap = false; |
2743 | | |
2744 | 8.51k | if (level == 0) { |
2745 | 4.09k | for (size_t i = 0; i < storage_info_.LevelFilesBrief(0).num_files; i++) { |
2746 | 3.00k | const auto file = &storage_info_.LevelFilesBrief(0).files[i]; |
2747 | 3.00k | if (AfterFile(ucmp, &smallest_user_key, file) || |
2748 | 2.94k | BeforeFile(ucmp, &largest_user_key, file)) { |
2749 | 742 | continue; |
2750 | 742 | } |
2751 | 2.26k | ScopedArenaPtr<InternalIterator> iter(cfd_->table_cache()->NewIterator( |
2752 | 2.26k | read_options, file_options, cfd_->internal_comparator(), |
2753 | 2.26k | *file->file_metadata, &range_del_agg, mutable_cf_options_, nullptr, |
2754 | 2.26k | cfd_->internal_stats()->GetFileReadHist(0), |
2755 | 2.26k | TableReaderCaller::kUserIterator, &arena, |
2756 | 2.26k | /*skip_filters=*/false, /*level=*/0, max_file_size_for_l0_meta_pin_, |
2757 | 2.26k | /*smallest_compaction_key=*/nullptr, |
2758 | 2.26k | /*largest_compaction_key=*/nullptr, |
2759 | 2.26k | /*allow_unprepared_value=*/false, /*seek_target=*/nullptr, |
2760 | 2.26k | /*prefix_filtered_table=*/nullptr)); |
2761 | 2.26k | status = OverlapWithIterator(ucmp, smallest_user_key, largest_user_key, |
2762 | 2.26k | iter.get(), overlap); |
2763 | 2.26k | if (!status.ok() || *overlap) { |
2764 | 2.25k | break; |
2765 | 2.25k | } |
2766 | 2.26k | } |
2767 | 5.16k | } else if (storage_info_.LevelFilesBrief(level).num_files > 0) { |
2768 | 861 | auto mem = arena.AllocateAligned(sizeof(LevelIterator)); |
2769 | 861 | ScopedArenaPtr<InternalIterator> iter(new (mem) LevelIterator( |
2770 | 861 | cfd_->table_cache(), read_options, file_options, |
2771 | 861 | cfd_->internal_comparator(), &storage_info_.LevelFilesBrief(level), |
2772 | 861 | mutable_cf_options_, cfd_->internal_stats()->GetFileReadHist(level), |
2773 | 861 | TableReaderCaller::kUserIterator, IsFilterSkipped(level), level, |
2774 | 861 | &range_del_agg, nullptr, false, nullptr, kMaxSequenceNumber, |
2775 | 861 | db_statistics_, clock_)); |
2776 | 861 | status = OverlapWithIterator(ucmp, smallest_user_key, largest_user_key, |
2777 | 861 | iter.get(), overlap); |
2778 | 861 | } |
2779 | | |
2780 | 8.51k | if (status.ok() && *overlap == false && |
2781 | 5.49k | range_del_agg.IsRangeOverlapped(smallest_user_key, largest_user_key, |
2782 | 5.49k | /*end_exclusive=*/false)) { |
2783 | 0 | *overlap = true; |
2784 | 0 | } |
2785 | 8.51k | return status; |
2786 | 8.51k | } |
2787 | | |
2788 | | VersionStorageInfo::VersionStorageInfo( |
2789 | | const InternalKeyComparator* internal_comparator, |
2790 | | const Comparator* user_comparator, int levels, |
2791 | | CompactionStyle compaction_style, VersionStorageInfo* ref_vstorage, |
2792 | | bool _force_consistency_checks, |
2793 | | EpochNumberRequirement epoch_number_requirement, SystemClock* clock, |
2794 | | uint32_t bottommost_file_compaction_delay, |
2795 | | OffpeakTimeOption offpeak_time_option, |
2796 | | PeriodicCompactionPhaseParams periodic_compaction_phase_params) |
2797 | 303k | : internal_comparator_(internal_comparator), |
2798 | 303k | user_comparator_(user_comparator), |
2799 | | // cfd is nullptr if Version is dummy |
2800 | 303k | num_levels_(levels), |
2801 | 303k | num_non_empty_levels_(0), |
2802 | 303k | file_indexer_(user_comparator), |
2803 | 303k | compaction_style_(compaction_style), |
2804 | 303k | files_(new std::vector<FileMetaData*>[num_levels_]), |
2805 | 303k | base_level_(num_levels_ == 1 ? -1 : 1), |
2806 | 303k | lowest_unnecessary_level_(-1), |
2807 | 303k | level_multiplier_(0.0), |
2808 | 303k | files_by_compaction_pri_(num_levels_), |
2809 | 303k | level0_non_overlapping_(false), |
2810 | 303k | next_file_to_compact_by_size_(num_levels_), |
2811 | 303k | compaction_score_(num_levels_), |
2812 | 303k | compaction_level_(num_levels_), |
2813 | 303k | l0_delay_trigger_count_(0), |
2814 | 303k | compact_cursor_(num_levels_), |
2815 | 303k | accumulated_file_size_(0), |
2816 | 303k | accumulated_raw_key_size_(0), |
2817 | 303k | accumulated_raw_value_size_(0), |
2818 | 303k | accumulated_num_non_deletions_(0), |
2819 | 303k | accumulated_num_deletions_(0), |
2820 | 303k | current_num_non_deletions_(0), |
2821 | 303k | current_num_deletions_(0), |
2822 | 303k | current_num_samples_(0), |
2823 | 303k | estimated_compaction_needed_bytes_(0), |
2824 | 303k | clock_(clock), |
2825 | 303k | bottommost_file_compaction_delay_(bottommost_file_compaction_delay), |
2826 | 303k | finalized_(false), |
2827 | 303k | force_consistency_checks_(_force_consistency_checks), |
2828 | 303k | epoch_number_requirement_(epoch_number_requirement), |
2829 | 303k | offpeak_time_option_(std::move(offpeak_time_option)), |
2830 | 303k | periodic_compaction_phase_params_(periodic_compaction_phase_params) { |
2831 | 303k | if (ref_vstorage != nullptr) { |
2832 | 128k | accumulated_file_size_ = ref_vstorage->accumulated_file_size_; |
2833 | 128k | accumulated_raw_key_size_ = ref_vstorage->accumulated_raw_key_size_; |
2834 | 128k | accumulated_raw_value_size_ = ref_vstorage->accumulated_raw_value_size_; |
2835 | 128k | accumulated_num_non_deletions_ = |
2836 | 128k | ref_vstorage->accumulated_num_non_deletions_; |
2837 | 128k | accumulated_num_deletions_ = ref_vstorage->accumulated_num_deletions_; |
2838 | 128k | current_num_non_deletions_ = ref_vstorage->current_num_non_deletions_; |
2839 | 128k | current_num_deletions_ = ref_vstorage->current_num_deletions_; |
2840 | 128k | current_num_samples_ = ref_vstorage->current_num_samples_; |
2841 | 128k | oldest_snapshot_seqnum_ = ref_vstorage->oldest_snapshot_seqnum_; |
2842 | 128k | preserve_time_min_seqno_ = ref_vstorage->preserve_time_min_seqno_; |
2843 | 128k | compact_cursor_ = ref_vstorage->compact_cursor_; |
2844 | 128k | compact_cursor_.resize(num_levels_); |
2845 | 128k | } |
2846 | 303k | } |
2847 | | |
2848 | | Version::Version(ColumnFamilyData* column_family_data, VersionSet* vset, |
2849 | | const FileOptions& file_opt, |
2850 | | const MutableCFOptions& mutable_cf_options, |
2851 | | const std::shared_ptr<IOTracer>& io_tracer, |
2852 | | uint64_t version_number, |
2853 | | EpochNumberRequirement epoch_number_requirement) |
2854 | 303k | : env_(vset->env_), |
2855 | 303k | clock_(vset->clock_), |
2856 | 303k | cfd_(column_family_data), |
2857 | 303k | info_log_((cfd_ == nullptr) ? nullptr : cfd_->ioptions().logger), |
2858 | 303k | db_statistics_((cfd_ == nullptr) ? nullptr : cfd_->ioptions().stats), |
2859 | 303k | table_cache_((cfd_ == nullptr) ? nullptr : cfd_->table_cache()), |
2860 | 303k | blob_source_(cfd_ ? cfd_->blob_source() : nullptr), |
2861 | | merge_operator_( |
2862 | 303k | (cfd_ == nullptr) ? nullptr : cfd_->ioptions().merge_operator.get()), |
2863 | 303k | storage_info_( |
2864 | 303k | (cfd_ == nullptr) ? nullptr : &cfd_->internal_comparator(), |
2865 | 303k | (cfd_ == nullptr) ? nullptr : cfd_->user_comparator(), |
2866 | 303k | cfd_ == nullptr ? 0 : cfd_->NumberLevels(), |
2867 | 303k | cfd_ == nullptr ? kCompactionStyleLevel |
2868 | 303k | : cfd_->ioptions().compaction_style, |
2869 | 303k | (cfd_ == nullptr || cfd_->current() == nullptr) |
2870 | 303k | ? nullptr |
2871 | 303k | : cfd_->current()->storage_info(), |
2872 | 303k | cfd_ == nullptr ? false : cfd_->ioptions().force_consistency_checks, |
2873 | 303k | epoch_number_requirement, |
2874 | 303k | cfd_ == nullptr ? nullptr : cfd_->ioptions().clock, |
2875 | 303k | cfd_ == nullptr ? 0 |
2876 | 303k | : mutable_cf_options.bottommost_file_compaction_delay, |
2877 | 303k | vset->offpeak_time_option(), |
2878 | 303k | cfd_ == nullptr |
2879 | 303k | ? PeriodicCompactionPhaseParams{} |
2880 | 303k | : vset->GetPeriodicCompactionPhaseParams(cfd_->GetID())), |
2881 | 303k | vset_(vset), |
2882 | 303k | next_(this), |
2883 | 303k | prev_(this), |
2884 | 303k | refs_(0), |
2885 | 303k | file_options_(file_opt), |
2886 | 303k | mutable_cf_options_(mutable_cf_options), |
2887 | | max_file_size_for_l0_meta_pin_( |
2888 | 303k | MaxFileSizeForL0MetaPin(mutable_cf_options_)), |
2889 | 303k | version_number_(version_number), |
2890 | 303k | io_tracer_(io_tracer) {} |
2891 | | |
2892 | | Status Version::GetBlob(const ReadOptions& read_options, const Slice& user_key, |
2893 | | const BlobIndex& blob_index, |
2894 | | FilePrefetchBuffer* prefetch_buffer, |
2895 | 0 | PinnableSlice* value, uint64_t* bytes_read) const { |
2896 | 0 | assert(value); |
2897 | |
|
2898 | 0 | if (blob_index.HasTTL() || blob_index.IsInlined()) { |
2899 | 0 | return Status::Corruption("Unexpected TTL/inlined blob index"); |
2900 | 0 | } |
2901 | | |
2902 | 0 | const uint64_t blob_file_number = blob_index.file_number(); |
2903 | |
|
2904 | 0 | auto blob_file_meta = storage_info_.GetBlobFileMetaData(blob_file_number); |
2905 | 0 | if (!blob_file_meta) { |
2906 | | // INTEGRITY CHECK -- do not weaken. The blob index must reference a known |
2907 | | // external blob file. No metadata (including file_number 0, the |
2908 | | // kInvalidBlobFileNumber / same-file "embedded" sentinel) means a corrupt |
2909 | | // index, or a same-file reference that should have been resolved by |
2910 | | // EmbeddedBlobResolvingIterator before reaching the integrated-BlobDB read |
2911 | | // path. See FileMetaData::UpdateBoundaries for the write-side tripwire. |
2912 | 0 | return UnknownBlobFileStatus(storage_info_, blob_file_number); |
2913 | 0 | } |
2914 | | |
2915 | 0 | assert(blob_source_); |
2916 | 0 | value->Reset(); |
2917 | 0 | const BlobFileOpenInfo blob_file{blob_file_number, |
2918 | 0 | blob_file_meta->GetChecksumValue(), |
2919 | 0 | blob_file_meta->GetChecksumMethod()}; |
2920 | 0 | const Status s = blob_source_->GetBlob( |
2921 | 0 | read_options, user_key, blob_file, blob_index.offset(), |
2922 | 0 | blob_file_meta->GetBlobFileSize(), blob_index.size(), |
2923 | 0 | blob_index.compression(), prefetch_buffer, value, bytes_read); |
2924 | |
|
2925 | 0 | return s; |
2926 | 0 | } |
2927 | | |
2928 | | Status Version::GetBlobRange(const ReadOptions& read_options, |
2929 | | const Slice& user_key, const BlobIndex& blob_index, |
2930 | | uint64_t range_offset, size_t range_length, |
2931 | 0 | PinnableSlice* value, uint64_t* bytes_read) const { |
2932 | 0 | assert(value); |
2933 | |
|
2934 | 0 | if (blob_index.HasTTL() || blob_index.IsInlined()) { |
2935 | 0 | return Status::Corruption("Unexpected TTL/inlined blob index"); |
2936 | 0 | } |
2937 | | |
2938 | | // A strict sub-range of a compressed blob cannot be decompressed in |
2939 | | // isolation; the caller resolves such columns whole and slices instead. |
2940 | 0 | if (blob_index.compression() != kNoCompression) { |
2941 | 0 | return Status::Corruption("Cannot range-read a compressed blob"); |
2942 | 0 | } |
2943 | | |
2944 | 0 | const uint64_t blob_file_number = blob_index.file_number(); |
2945 | |
|
2946 | 0 | auto blob_file_meta = storage_info_.GetBlobFileMetaData(blob_file_number); |
2947 | 0 | if (!blob_file_meta) { |
2948 | | // INTEGRITY CHECK -- see Version::GetBlob. No metadata (including |
2949 | | // file_number 0, the same-file "embedded" sentinel) means a corrupt index |
2950 | | // or a same-file reference that the lazy caller must not route here. |
2951 | 0 | return UnknownBlobFileStatus(storage_info_, blob_file_number); |
2952 | 0 | } |
2953 | | |
2954 | 0 | assert(blob_source_); |
2955 | 0 | value->Reset(); |
2956 | 0 | const BlobFileOpenInfo blob_file{blob_file_number, |
2957 | 0 | blob_file_meta->GetChecksumValue(), |
2958 | 0 | blob_file_meta->GetChecksumMethod()}; |
2959 | 0 | return blob_source_->GetBlobRange( |
2960 | 0 | read_options, user_key, blob_file, blob_index.offset(), |
2961 | 0 | blob_file_meta->GetBlobFileSize(), blob_index.size(), |
2962 | 0 | blob_index.compression(), range_offset, range_length, value, bytes_read); |
2963 | 0 | } |
2964 | | |
2965 | | void Version::MultiGetBlobLazy(const ReadOptions& read_options, |
2966 | 0 | autovector<LazyBlobReadRequest>& reqs) const { |
2967 | 0 | assert(blob_source_); |
2968 | | |
2969 | | // Turn the flat list of separate-file blob references into per-blob-file |
2970 | | // batches that BlobSource can each resolve with a single coalesced MultiRead. |
2971 | | // Whole-value and sub-range requests use different BlobSource entry points, |
2972 | | // so they are grouped independently: |
2973 | | // whole_reqs -- batches of whole-value reads (BlobReadRequest) |
2974 | | // range_reqs -- batches of sub-range reads (BlobRangeReadRequest) |
2975 | | // Each batch holds at most MultiGetContext::MAX_BATCH_SIZE requests (see the |
2976 | | // loop below), so one file may contribute several batches. whole_idx / |
2977 | | // range_idx map a file number to the index, within whole_reqs / range_reqs, |
2978 | | // of that file's current (most recently started, still fillable) batch. |
2979 | 0 | autovector<BlobFileReadRequests> whole_reqs; |
2980 | 0 | autovector<BlobFileRangeReadRequests> range_reqs; |
2981 | 0 | std::unordered_map<uint64_t, size_t> whole_idx; |
2982 | 0 | std::unordered_map<uint64_t, size_t> range_idx; |
2983 | |
|
2984 | 0 | for (auto& req : reqs) { |
2985 | 0 | assert(req.user_key); |
2986 | 0 | assert(req.blob_index); |
2987 | 0 | assert(req.result); |
2988 | 0 | assert(req.status); |
2989 | 0 | const BlobIndex& blob_index = *req.blob_index; |
2990 | |
|
2991 | 0 | if (blob_index.HasTTL() || blob_index.IsInlined()) { |
2992 | 0 | *req.status = Status::Corruption("Unexpected TTL/inlined blob index"); |
2993 | 0 | continue; |
2994 | 0 | } |
2995 | | |
2996 | 0 | const uint64_t file_number = blob_index.file_number(); |
2997 | 0 | auto blob_file_meta = storage_info_.GetBlobFileMetaData(file_number); |
2998 | 0 | if (!blob_file_meta) { |
2999 | | // INTEGRITY CHECK -- see Version::GetBlob. A same-file/embedded reference |
3000 | | // (file_number 0) must be resolved via SameFileBlobReader, not here. |
3001 | 0 | *req.status = UnknownBlobFileStatus(storage_info_, file_number); |
3002 | 0 | continue; |
3003 | 0 | } |
3004 | 0 | const uint64_t file_size = blob_file_meta->GetBlobFileSize(); |
3005 | 0 | const BlobFileOpenInfo blob_file{file_number, |
3006 | 0 | blob_file_meta->GetChecksumValue(), |
3007 | 0 | blob_file_meta->GetChecksumMethod()}; |
3008 | |
|
3009 | 0 | if (req.range_length == kWholeBlobLength) { |
3010 | | // Append to this file's current batch, or start a new batch when the file |
3011 | | // has none yet or its current batch is full. Capping each batch at |
3012 | | // MAX_BATCH_SIZE is required, not just an optimization: |
3013 | | // BlobFileReader::MultiGetBlob[Range] asserts a batch is at most |
3014 | | // MAX_BATCH_SIZE, and BlobSource tracks a batch's cache hits in a 64-bit |
3015 | | // mask -- while MultiGetEntityLazy does not otherwise bound how many keys |
3016 | | // can reference a single blob file. |
3017 | 0 | size_t idx; |
3018 | 0 | auto it = whole_idx.find(file_number); |
3019 | 0 | if (it != whole_idx.end() && std::get<2>(whole_reqs[it->second]).size() < |
3020 | 0 | MultiGetContext::MAX_BATCH_SIZE) { |
3021 | 0 | idx = it->second; // this file's current batch still has room |
3022 | 0 | } else { |
3023 | 0 | idx = whole_reqs.size(); // no batch yet for this file, or it is full |
3024 | 0 | whole_idx[file_number] = idx; |
3025 | 0 | whole_reqs.emplace_back(blob_file, file_size, |
3026 | 0 | autovector<BlobReadRequest>()); |
3027 | 0 | } |
3028 | 0 | std::get<2>(whole_reqs[idx]) |
3029 | 0 | .emplace_back(*req.user_key, blob_index.offset(), blob_index.size(), |
3030 | 0 | blob_index.compression(), req.result, req.status); |
3031 | 0 | } else { |
3032 | | // A strict sub-range of a compressed blob cannot be decompressed in |
3033 | | // isolation; the caller resolves such columns whole and slices instead. |
3034 | 0 | if (blob_index.compression() != kNoCompression) { |
3035 | 0 | *req.status = Status::Corruption("Cannot range-read a compressed blob"); |
3036 | 0 | continue; |
3037 | 0 | } |
3038 | | // Same per-file batching and MAX_BATCH_SIZE cap as the whole-value path |
3039 | | // above. |
3040 | 0 | size_t idx; |
3041 | 0 | auto it = range_idx.find(file_number); |
3042 | 0 | if (it != range_idx.end() && std::get<2>(range_reqs[it->second]).size() < |
3043 | 0 | MultiGetContext::MAX_BATCH_SIZE) { |
3044 | 0 | idx = it->second; // this file's current batch still has room |
3045 | 0 | } else { |
3046 | 0 | idx = range_reqs.size(); // no batch yet for this file, or it is full |
3047 | 0 | range_idx[file_number] = idx; |
3048 | 0 | range_reqs.emplace_back(blob_file, file_size, |
3049 | 0 | autovector<BlobRangeReadRequest>()); |
3050 | 0 | } |
3051 | 0 | std::get<2>(range_reqs[idx]) |
3052 | 0 | .emplace_back(*req.user_key, blob_index.offset(), blob_index.size(), |
3053 | 0 | req.range_offset, req.range_length, req.result, |
3054 | 0 | req.status); |
3055 | 0 | } |
3056 | 0 | } |
3057 | | |
3058 | | // Resolve every batch; BlobSource issues one coalesced MultiRead per batch. |
3059 | 0 | if (!whole_reqs.empty()) { |
3060 | 0 | blob_source_->MultiGetBlob(read_options, whole_reqs, |
3061 | 0 | /*bytes_read=*/nullptr); |
3062 | 0 | } |
3063 | 0 | if (!range_reqs.empty()) { |
3064 | 0 | blob_source_->MultiGetBlobRange(read_options, range_reqs, |
3065 | 0 | /*bytes_read=*/nullptr); |
3066 | 0 | } |
3067 | 0 | } |
3068 | | |
3069 | | void Version::MultiGetBlob( |
3070 | | const ReadOptions& read_options, MultiGetRange& range, |
3071 | 0 | std::unordered_map<uint64_t, BlobReadContexts>& blob_ctxs) { |
3072 | 0 | assert(!blob_ctxs.empty()); |
3073 | |
|
3074 | 0 | autovector<BlobFileReadRequests> blob_reqs; |
3075 | |
|
3076 | 0 | for (auto& ctx : blob_ctxs) { |
3077 | 0 | const auto file_number = ctx.first; |
3078 | 0 | const auto blob_file_meta = storage_info_.GetBlobFileMetaData(file_number); |
3079 | |
|
3080 | 0 | autovector<BlobReadRequest> blob_reqs_in_file; |
3081 | 0 | BlobReadContexts& blobs_in_file = ctx.second; |
3082 | 0 | for (auto& blob : blobs_in_file) { |
3083 | 0 | const BlobIndex& blob_index = blob.blob_index; |
3084 | 0 | KeyContext* const key_context = blob.key_context; |
3085 | 0 | assert(key_context); |
3086 | 0 | assert(key_context->get_context); |
3087 | 0 | assert(key_context->s); |
3088 | |
|
3089 | 0 | if (key_context->value) { |
3090 | 0 | key_context->value->Reset(); |
3091 | 0 | } else { |
3092 | 0 | assert(key_context->columns); |
3093 | 0 | key_context->columns->Reset(); |
3094 | 0 | } |
3095 | |
|
3096 | 0 | if (!blob_file_meta) { |
3097 | | // INTEGRITY CHECK -- do not weaken; see Version::GetBlob and |
3098 | | // FileMetaData::UpdateBoundaries. A same-file/embedded reference must |
3099 | | // be resolved before reaching the integrated-BlobDB read path. |
3100 | 0 | *key_context->s = UnknownBlobFileStatus(storage_info_, file_number); |
3101 | 0 | continue; |
3102 | 0 | } |
3103 | | |
3104 | 0 | if (blob_index.HasTTL() || blob_index.IsInlined()) { |
3105 | 0 | *key_context->s = |
3106 | 0 | Status::Corruption("Unexpected TTL/inlined blob index"); |
3107 | 0 | continue; |
3108 | 0 | } |
3109 | | |
3110 | 0 | blob_reqs_in_file.emplace_back( |
3111 | 0 | key_context->get_context->ukey_to_get_blob_value(), |
3112 | 0 | blob_index.offset(), blob_index.size(), blob_index.compression(), |
3113 | 0 | &blob.result, key_context->s); |
3114 | 0 | } |
3115 | 0 | if (blob_reqs_in_file.size() > 0) { |
3116 | 0 | const auto file_size = blob_file_meta->GetBlobFileSize(); |
3117 | 0 | const BlobFileOpenInfo blob_file{file_number, |
3118 | 0 | blob_file_meta->GetChecksumValue(), |
3119 | 0 | blob_file_meta->GetChecksumMethod()}; |
3120 | 0 | blob_reqs.emplace_back(blob_file, file_size, blob_reqs_in_file); |
3121 | 0 | } |
3122 | 0 | } |
3123 | |
|
3124 | 0 | if (blob_reqs.size() > 0) { |
3125 | 0 | blob_source_->MultiGetBlob(read_options, blob_reqs, |
3126 | 0 | /*bytes_read=*/nullptr); |
3127 | 0 | } |
3128 | |
|
3129 | 0 | for (auto& ctx : blob_ctxs) { |
3130 | 0 | BlobReadContexts& blobs_in_file = ctx.second; |
3131 | 0 | for (auto& blob : blobs_in_file) { |
3132 | 0 | KeyContext* const key_context = blob.key_context; |
3133 | 0 | assert(key_context); |
3134 | 0 | assert(key_context->get_context); |
3135 | 0 | assert(key_context->s); |
3136 | |
|
3137 | 0 | if (key_context->s->ok()) { |
3138 | 0 | if (key_context->value) { |
3139 | 0 | *key_context->value = std::move(blob.result); |
3140 | 0 | range.AddValueSize(key_context->value->size()); |
3141 | 0 | } else { |
3142 | 0 | assert(key_context->columns); |
3143 | 0 | key_context->columns->SetPlainValue(std::move(blob.result)); |
3144 | 0 | range.AddValueSize(key_context->columns->payload_size()); |
3145 | 0 | } |
3146 | | // MultiGetBlob() has already materialized the blob reference into the |
3147 | | // user-visible value, so clear this flag before later MultiGet |
3148 | | // post-processing checks whether further blob resolution is needed. |
3149 | 0 | key_context->is_blob_index = false; |
3150 | |
|
3151 | 0 | if (range.GetValueSize() > read_options.value_size_soft_limit) { |
3152 | 0 | *key_context->s = Status::Aborted(); |
3153 | 0 | } |
3154 | 0 | } else if (key_context->s->IsIncomplete()) { |
3155 | | // read_options.read_tier == kBlockCacheTier |
3156 | | // Cannot read blob(s): no disk I/O allowed |
3157 | 0 | auto& get_context = *(key_context->get_context); |
3158 | 0 | get_context.MarkKeyMayExist(); |
3159 | 0 | } |
3160 | 0 | } |
3161 | 0 | } |
3162 | 0 | } |
3163 | | |
3164 | | #ifdef USE_COROUTINES |
3165 | | Status Version::ProcessBatch( |
3166 | | const ReadOptions& read_options, FilePickerMultiGet* batch, |
3167 | | std::vector<folly::coro::Task<Status>>& mget_tasks, |
3168 | | std::unordered_map<uint64_t, BlobReadContexts>* blob_ctxs, |
3169 | | autovector<FilePickerMultiGet, 4>& batches, std::deque<size_t>& waiting, |
3170 | | std::deque<size_t>& to_process, unsigned int& num_tasks_queued, |
3171 | | std::unordered_map<int, std::tuple<uint64_t, uint64_t, uint64_t>>& |
3172 | | mget_stats) { |
3173 | | FilePickerMultiGet& fp = *batch; |
3174 | | MultiGetRange range = fp.GetRange(); |
3175 | | // Initialize a new empty range. Any keys that are not in this level will |
3176 | | // eventually become part of the new range. |
3177 | | MultiGetRange leftover(range, range.begin(), range.begin()); |
3178 | | FdWithKeyRange* f = nullptr; |
3179 | | Status s; |
3180 | | |
3181 | | f = fp.GetNextFileInLevel(); |
3182 | | while (!f) { |
3183 | | fp.PrepareNextLevelForSearch(); |
3184 | | if (!fp.IsSearchEnded()) { |
3185 | | f = fp.GetNextFileInLevel(); |
3186 | | } else { |
3187 | | break; |
3188 | | } |
3189 | | } |
3190 | | while (f) { |
3191 | | MultiGetRange file_range = fp.CurrentFileRange(); |
3192 | | TableCache::TypedHandle* table_handle = nullptr; |
3193 | | bool skip_filters = IsFilterSkipped(static_cast<int>(fp.GetHitFileLevel()), |
3194 | | fp.IsHitFileLastInLevel()); |
3195 | | bool skip_range_deletions = false; |
3196 | | if (!skip_filters) { |
3197 | | Status status = table_cache_->MultiGetFilter( |
3198 | | read_options, *internal_comparator(), *f->file_metadata, |
3199 | | mutable_cf_options_, |
3200 | | cfd_->internal_stats()->GetFileReadHist(fp.GetHitFileLevel()), |
3201 | | fp.GetHitFileLevel(), &file_range, &table_handle); |
3202 | | if (status.ok()) { |
3203 | | skip_filters = true; |
3204 | | skip_range_deletions = true; |
3205 | | } else if (!status.IsNotSupported()) { |
3206 | | s = status; |
3207 | | } |
3208 | | } |
3209 | | if (!s.ok()) { |
3210 | | break; |
3211 | | } |
3212 | | // At this point, file_range contains any keys that are likely in this |
3213 | | // file. It may have false positives, but that's ok since higher level |
3214 | | // lookups for the key are dependent on this lookup anyway. |
3215 | | // Add the complement of file_range to leftover. That's the set of keys |
3216 | | // definitely not in this level. |
3217 | | // Subtract the complement of file_range from range, since they will be |
3218 | | // processed in a separate batch in parallel. |
3219 | | leftover += ~file_range; |
3220 | | range -= ~file_range; |
3221 | | if (!file_range.empty()) { |
3222 | | int level = fp.GetHitFileLevel(); |
3223 | | auto stat = mget_stats.find(level); |
3224 | | if (stat == mget_stats.end()) { |
3225 | | auto entry = mget_stats.insert({level, {0, 0, 0}}); |
3226 | | assert(entry.second); |
3227 | | stat = entry.first; |
3228 | | } |
3229 | | |
3230 | | if (waiting.empty() && to_process.empty() && |
3231 | | !fp.RemainingOverlapInLevel() && leftover.empty() && |
3232 | | mget_tasks.empty()) { |
3233 | | // All keys are in one SST file, so take the fast path |
3234 | | s = MultiGetFromSST(read_options, file_range, fp.GetHitFileLevel(), |
3235 | | skip_filters, skip_range_deletions, f, *blob_ctxs, |
3236 | | table_handle, std::get<0>(stat->second), |
3237 | | std::get<1>(stat->second), |
3238 | | std::get<2>(stat->second)); |
3239 | | } else { |
3240 | | mget_tasks.emplace_back(MultiGetFromSSTCoroutine( |
3241 | | read_options, file_range, fp.GetHitFileLevel(), skip_filters, |
3242 | | skip_range_deletions, f, *blob_ctxs, table_handle, |
3243 | | std::get<0>(stat->second), std::get<1>(stat->second), |
3244 | | std::get<2>(stat->second))); |
3245 | | ++num_tasks_queued; |
3246 | | } |
3247 | | } |
3248 | | if (fp.KeyMaySpanNextFile() && !file_range.empty()) { |
3249 | | break; |
3250 | | } |
3251 | | f = fp.GetNextFileInLevel(); |
3252 | | } |
3253 | | // Split the current batch only if some keys are likely in this level and |
3254 | | // some are not. Only split if we're done with this level, i.e f is null. |
3255 | | // Otherwise, it means there are more files in this level to look at. |
3256 | | if (s.ok() && !f && !leftover.empty() && !range.empty()) { |
3257 | | fp.ReplaceRange(range); |
3258 | | batches.emplace_back(&leftover, fp); |
3259 | | to_process.emplace_back(batches.size() - 1); |
3260 | | } |
3261 | | // 1. If f is non-null, that means we might not be done with this level. |
3262 | | // This can happen if one of the keys is the last key in the file, i.e |
3263 | | // fp.KeyMaySpanNextFile() is true. |
3264 | | // 2. If range is empty, then we're done with this range and no need to |
3265 | | // prepare the next level |
3266 | | // 3. If some tasks were queued for this range, then the next level will be |
3267 | | // prepared after executing those tasks |
3268 | | if (!f && !range.empty() && !num_tasks_queued) { |
3269 | | fp.PrepareNextLevelForSearch(); |
3270 | | } |
3271 | | return s; |
3272 | | } |
3273 | | |
3274 | | Status Version::MultiGetAsync( |
3275 | | const ReadOptions& options, MultiGetRange* range, |
3276 | | std::unordered_map<uint64_t, BlobReadContexts>* blob_ctxs) { |
3277 | | autovector<FilePickerMultiGet, 4> batches; |
3278 | | std::deque<size_t> waiting; |
3279 | | std::deque<size_t> to_process; |
3280 | | Status s; |
3281 | | std::vector<folly::coro::Task<Status>> mget_tasks; |
3282 | | std::unordered_map<int, std::tuple<uint64_t, uint64_t, uint64_t>> mget_stats; |
3283 | | |
3284 | | // Create the initial batch with the input range |
3285 | | batches.emplace_back(range, &storage_info_.level_files_brief_, |
3286 | | storage_info_.num_non_empty_levels_, |
3287 | | &storage_info_.file_indexer_, user_comparator(), |
3288 | | internal_comparator()); |
3289 | | to_process.emplace_back(0); |
3290 | | |
3291 | | while (!to_process.empty()) { |
3292 | | // As we process a batch, it may get split into two. So reserve space for |
3293 | | // an additional batch in the autovector in order to prevent later moves |
3294 | | // of elements in ProcessBatch(). |
3295 | | batches.reserve(batches.size() + 1); |
3296 | | |
3297 | | size_t idx = to_process.front(); |
3298 | | FilePickerMultiGet* batch = &batches.at(idx); |
3299 | | unsigned int num_tasks_queued = 0; |
3300 | | to_process.pop_front(); |
3301 | | if (batch->IsSearchEnded() || batch->GetRange().empty()) { |
3302 | | // If to_process is empty, i.e no more batches to look at, then we need |
3303 | | // schedule the enqueued coroutines and wait for them. Otherwise, we |
3304 | | // skip this batch and move to the next one in to_process. |
3305 | | if (!to_process.empty()) { |
3306 | | continue; |
3307 | | } |
3308 | | } else { |
3309 | | // Look through one level. This may split the batch and enqueue it to |
3310 | | // to_process |
3311 | | s = ProcessBatch(options, batch, mget_tasks, blob_ctxs, batches, waiting, |
3312 | | to_process, num_tasks_queued, mget_stats); |
3313 | | // If ProcessBatch didn't enqueue any coroutine tasks, it means all |
3314 | | // keys were filtered out. So put the batch back in to_process to |
3315 | | // lookup in the next level |
3316 | | if (!num_tasks_queued && !batch->IsSearchEnded()) { |
3317 | | // Put this back in the processing queue |
3318 | | to_process.emplace_back(idx); |
3319 | | } else if (num_tasks_queued) { |
3320 | | waiting.emplace_back(idx); |
3321 | | } |
3322 | | } |
3323 | | // If ProcessBatch() returned an error, then schedule the enqueued |
3324 | | // coroutines and wait for them, then abort the MultiGet. |
3325 | | if (to_process.empty() || !s.ok()) { |
3326 | | if (mget_tasks.size() > 0) { |
3327 | | assert(waiting.size()); |
3328 | | RecordTick(db_statistics_, MULTIGET_COROUTINE_COUNT, mget_tasks.size()); |
3329 | | // Collect all results so far |
3330 | | std::vector<Status> statuses = |
3331 | | folly::coro::blockingWait(co_withExecutor( |
3332 | | &range->context()->executor(), |
3333 | | folly::coro::collectAllRange(std::move(mget_tasks)))); |
3334 | | mget_tasks.clear(); |
3335 | | if (s.ok()) { |
3336 | | for (Status stat : statuses) { |
3337 | | if (!stat.ok()) { |
3338 | | s = std::move(stat); |
3339 | | break; |
3340 | | } |
3341 | | } |
3342 | | } |
3343 | | |
3344 | | if (!s.ok()) { |
3345 | | break; |
3346 | | } |
3347 | | |
3348 | | for (size_t wait_idx : waiting) { |
3349 | | FilePickerMultiGet& fp = batches.at(wait_idx); |
3350 | | // 1. If fp.GetHitFile() is non-null, then there could be more |
3351 | | // overlap in this level. So skip preparing next level. |
3352 | | // 2. If fp.GetRange() is empty, then this batch is completed |
3353 | | // and no need to prepare the next level. |
3354 | | if (!fp.GetHitFile() && !fp.GetRange().empty()) { |
3355 | | fp.PrepareNextLevelForSearch(); |
3356 | | } |
3357 | | } |
3358 | | to_process.swap(waiting); |
3359 | | } else { |
3360 | | assert(!s.ok() || waiting.size() == 0); |
3361 | | } |
3362 | | } |
3363 | | if (!s.ok()) { |
3364 | | break; |
3365 | | } |
3366 | | } |
3367 | | |
3368 | | uint64_t num_levels = 0; |
3369 | | for (auto& stat : mget_stats) { |
3370 | | if (stat.first == 0) { |
3371 | | num_levels += std::get<2>(stat.second); |
3372 | | } else { |
3373 | | num_levels++; |
3374 | | } |
3375 | | |
3376 | | uint64_t num_meta_reads = |
3377 | | std::get<0>(stat.second) + std::get<1>(stat.second); |
3378 | | uint64_t num_sst_reads = std::get<2>(stat.second); |
3379 | | if (num_meta_reads > 0) { |
3380 | | RecordInHistogram(db_statistics_, |
3381 | | NUM_INDEX_AND_FILTER_BLOCKS_READ_PER_LEVEL, |
3382 | | num_meta_reads); |
3383 | | } |
3384 | | if (num_sst_reads > 0) { |
3385 | | RecordInHistogram(db_statistics_, NUM_SST_READ_PER_LEVEL, num_sst_reads); |
3386 | | } |
3387 | | } |
3388 | | if (num_levels > 0) { |
3389 | | RecordInHistogram(db_statistics_, NUM_LEVEL_READ_PER_MULTIGET, num_levels); |
3390 | | } |
3391 | | |
3392 | | return s; |
3393 | | } |
3394 | | #endif |
3395 | | |
3396 | 4.42k | bool Version::IsFilterSkipped(int level, bool is_file_last_in_level) { |
3397 | | // Reaching the bottom level implies misses at all upper levels, so we'll |
3398 | | // skip checking the filters when we predict a hit. |
3399 | 4.42k | return cfd_->ioptions().optimize_filters_for_hits && |
3400 | 0 | (level > 0 || is_file_last_in_level) && |
3401 | 0 | level == storage_info_.num_non_empty_levels() - 1; |
3402 | 4.42k | } |
3403 | | |
3404 | 215k | void VersionStorageInfo::GenerateLevelFilesBrief() { |
3405 | 215k | level_files_brief_.resize(num_non_empty_levels_); |
3406 | 432k | for (int level = 0; level < num_non_empty_levels_; level++) { |
3407 | 216k | DoGenerateLevelFilesBrief(&level_files_brief_[level], files_[level], |
3408 | 216k | &arena_); |
3409 | 216k | } |
3410 | 215k | } |
3411 | | |
3412 | | void VersionStorageInfo::PrepareForVersionAppend( |
3413 | | const ImmutableOptions& immutable_options, |
3414 | 215k | const MutableCFOptions& mutable_cf_options) { |
3415 | 215k | ComputeCompensatedSizes(); |
3416 | 215k | UpdateNumNonEmptyLevels(); |
3417 | 215k | CalculateBaseBytes(immutable_options, mutable_cf_options); |
3418 | 215k | UpdateFilesByCompactionPri(immutable_options, mutable_cf_options); |
3419 | 215k | GenerateFileIndexer(); |
3420 | 215k | GenerateLevelFilesBrief(); |
3421 | 215k | GenerateLevel0NonOverlapping(); |
3422 | 215k | GenerateBottommostFiles(); |
3423 | 215k | GenerateFileLocationIndex(); |
3424 | 215k | } |
3425 | | |
3426 | | void Version::PrepareAppend(const ReadOptions& read_options, |
3427 | 215k | bool update_stats) { |
3428 | 215k | TEST_SYNC_POINT_CALLBACK( |
3429 | 215k | "Version::PrepareAppend:forced_check", |
3430 | 215k | static_cast<void*>(&storage_info_.force_consistency_checks_)); |
3431 | | |
3432 | 215k | if (update_stats) { |
3433 | 128k | UpdateAccumulatedStats(read_options); |
3434 | 128k | } |
3435 | | |
3436 | 215k | storage_info_.PrepareForVersionAppend(cfd_->ioptions(), mutable_cf_options_); |
3437 | 215k | } |
3438 | | |
3439 | | bool Version::MaybeInitializeFileMetaData(const ReadOptions& read_options, |
3440 | | FileMetaData* file_meta, |
3441 | 303k | bool populate_referenced_blob_bytes) { |
3442 | 303k | if (file_meta->init_stats_from_file || file_meta->compensated_file_size > 0) { |
3443 | 209k | return false; |
3444 | 209k | } |
3445 | 93.9k | std::shared_ptr<const TableProperties> tp; |
3446 | 93.9k | Status s = GetTableProperties(read_options, &tp, file_meta); |
3447 | 93.9k | file_meta->init_stats_from_file = true; |
3448 | 93.9k | if (!s.ok()) { |
3449 | 0 | ROCKS_LOG_ERROR(vset_->db_options_->info_log, |
3450 | 0 | "Unable to load table properties for file %" PRIu64 |
3451 | 0 | " --- %s\n", |
3452 | 0 | file_meta->fd.GetNumber(), s.ToString().c_str()); |
3453 | 0 | return false; |
3454 | 0 | } |
3455 | 93.9k | if (tp.get() == nullptr) { |
3456 | 0 | return false; |
3457 | 0 | } |
3458 | 93.9k | file_meta->num_entries = tp->num_entries; |
3459 | 93.9k | file_meta->num_deletions = tp->num_deletions; |
3460 | 93.9k | file_meta->raw_value_size = tp->raw_value_size; |
3461 | 93.9k | file_meta->raw_key_size = tp->raw_key_size; |
3462 | 93.9k | if (populate_referenced_blob_bytes) { |
3463 | 93.9k | file_meta->referenced_blob_bytes = tp->referenced_blob_bytes; |
3464 | 93.9k | } |
3465 | 93.9k | file_meta->num_range_deletions = tp->num_range_deletions; |
3466 | | // Ensure new invariants on old files |
3467 | 93.9k | file_meta->num_deletions = |
3468 | 93.9k | std::max(tp->num_deletions, tp->num_range_deletions); |
3469 | 93.9k | file_meta->num_entries = std::max(tp->num_entries, file_meta->num_deletions); |
3470 | 93.9k | return true; |
3471 | 93.9k | } |
3472 | | |
3473 | 93.9k | void VersionStorageInfo::UpdateAccumulatedStats(FileMetaData* file_meta) { |
3474 | 93.9k | TEST_SYNC_POINT_CALLBACK("VersionStorageInfo::UpdateAccumulatedStats", |
3475 | 93.9k | nullptr); |
3476 | | |
3477 | 93.9k | assert(file_meta->init_stats_from_file); |
3478 | 93.9k | accumulated_file_size_ += file_meta->fd.GetFileSize(); |
3479 | 93.9k | accumulated_raw_key_size_ += file_meta->raw_key_size; |
3480 | 93.9k | accumulated_raw_value_size_ += file_meta->raw_value_size; |
3481 | 93.9k | assert(file_meta->num_entries >= file_meta->num_deletions); |
3482 | 93.9k | accumulated_num_non_deletions_ += |
3483 | 93.9k | file_meta->num_entries - file_meta->num_deletions; |
3484 | 93.9k | accumulated_num_deletions_ += file_meta->num_deletions; |
3485 | | |
3486 | 93.9k | current_num_non_deletions_ += |
3487 | 93.9k | file_meta->num_entries - file_meta->num_deletions; |
3488 | 93.9k | current_num_deletions_ += file_meta->num_deletions; |
3489 | 93.9k | current_num_samples_++; |
3490 | 93.9k | } |
3491 | | |
3492 | 11.9k | void VersionStorageInfo::RemoveCurrentStats(FileMetaData* file_meta) { |
3493 | 11.9k | if (file_meta->init_stats_from_file) { |
3494 | 11.9k | current_num_non_deletions_ -= |
3495 | 11.9k | file_meta->num_entries - file_meta->num_deletions; |
3496 | 11.9k | current_num_deletions_ -= file_meta->num_deletions; |
3497 | 11.9k | current_num_samples_--; |
3498 | 11.9k | } |
3499 | 11.9k | } |
3500 | | |
3501 | 128k | void Version::UpdateAccumulatedStats(const ReadOptions& read_options) { |
3502 | | // maximum number of table properties loaded from files. |
3503 | 128k | const int kMaxInitCount = 20; |
3504 | 128k | int init_count = 0; |
3505 | | // here only the first kMaxInitCount files which haven't been |
3506 | | // initialized from file will be updated with num_deletions. |
3507 | | // The motivation here is to cap the maximum I/O per Version creation. |
3508 | | // The reason for choosing files from lower-level instead of higher-level |
3509 | | // is that such design is able to propagate the initialization from |
3510 | | // lower-level to higher-level: When the num_deletions of lower-level |
3511 | | // files are updated, it will make the lower-level files have accurate |
3512 | | // compensated_file_size, making lower-level to higher-level compaction |
3513 | | // will be triggered, which creates higher-level files whose num_deletions |
3514 | | // will be updated here. |
3515 | 128k | for (int level = 0; |
3516 | 1.02M | level < storage_info_.num_levels_ && init_count < kMaxInitCount; |
3517 | 897k | ++level) { |
3518 | 897k | for (auto* file_meta : storage_info_.files_[level]) { |
3519 | 173k | if (MaybeInitializeFileMetaData( |
3520 | 173k | read_options, file_meta, |
3521 | 173k | /*populate_referenced_blob_bytes=*/true)) { |
3522 | | // each FileMeta will be initialized only once. |
3523 | 93.9k | storage_info_.UpdateAccumulatedStats(file_meta); |
3524 | | // when option "max_open_files" is -1, all the file metadata has |
3525 | | // already been read, so MaybeInitializeFileMetaData() won't incur |
3526 | | // any I/O cost. "max_open_files=-1" means that the table cache passed |
3527 | | // to the VersionSet and then to the ColumnFamilySet has a size of |
3528 | | // TableCache::kInfiniteCapacity |
3529 | 93.9k | if (vset_->GetColumnFamilySet()->get_table_cache()->GetCapacity() == |
3530 | 93.9k | TableCache::kInfiniteCapacity) { |
3531 | 93.9k | continue; |
3532 | 93.9k | } |
3533 | 0 | if (++init_count >= kMaxInitCount) { |
3534 | 0 | break; |
3535 | 0 | } |
3536 | 0 | } |
3537 | 173k | } |
3538 | 897k | } |
3539 | | // In case all sampled-files contain only deletion entries, then we |
3540 | | // load the table-property of a file in higher-level to initialize |
3541 | | // that value. |
3542 | 128k | for (int level = storage_info_.num_levels_ - 1; |
3543 | 931k | storage_info_.accumulated_raw_value_size_ == 0 && level >= 0; --level) { |
3544 | 803k | for (int i = static_cast<int>(storage_info_.files_[level].size()) - 1; |
3545 | 932k | storage_info_.accumulated_raw_value_size_ == 0 && i >= 0; --i) { |
3546 | 129k | if (MaybeInitializeFileMetaData( |
3547 | 129k | read_options, storage_info_.files_[level][i], |
3548 | 129k | /*populate_referenced_blob_bytes=*/true)) { |
3549 | 0 | storage_info_.UpdateAccumulatedStats(storage_info_.files_[level][i]); |
3550 | 0 | } |
3551 | 129k | } |
3552 | 803k | } |
3553 | 128k | } |
3554 | | |
3555 | 215k | void VersionStorageInfo::ComputeCompensatedSizes() { |
3556 | 215k | static const int kDeletionWeightOnCompaction = 2; |
3557 | 215k | uint64_t average_value_size = GetAverageValueSize(); |
3558 | | |
3559 | | // compute the compensated size |
3560 | 1.72M | for (int level = 0; level < num_levels_; level++) { |
3561 | 1.51M | for (auto* file_meta : files_[level]) { |
3562 | | // Here we only compute compensated_file_size for those file_meta |
3563 | | // which compensated_file_size is uninitialized (== 0). This is true only |
3564 | | // for files that have been created right now and no other thread has |
3565 | | // access to them. That's why we can safely mutate compensated_file_size. |
3566 | 173k | if (file_meta->compensated_file_size == 0) { |
3567 | 93.9k | file_meta->compensated_file_size = file_meta->fd.GetFileSize(); |
3568 | | // Here we only boost the size of deletion entries of a file only |
3569 | | // when the number of deletion entries is greater than the number of |
3570 | | // non-deletion entries in the file. The motivation here is that in |
3571 | | // a stable workload, the number of deletion entries should be roughly |
3572 | | // equal to the number of non-deletion entries. If we compensate the |
3573 | | // size of deletion entries in a stable workload, the deletion |
3574 | | // compensation logic might introduce unwanted effet which changes the |
3575 | | // shape of LSM tree. |
3576 | 93.9k | if ((file_meta->num_deletions - file_meta->num_range_deletions) * 2 >= |
3577 | 93.9k | file_meta->num_entries) { |
3578 | 48.9k | file_meta->compensated_file_size += |
3579 | 48.9k | ((file_meta->num_deletions - file_meta->num_range_deletions) * 2 - |
3580 | 48.9k | file_meta->num_entries) * |
3581 | 48.9k | average_value_size * kDeletionWeightOnCompaction; |
3582 | 48.9k | } |
3583 | 93.9k | file_meta->compensated_file_size += |
3584 | 93.9k | file_meta->compensated_range_deletion_size; |
3585 | 93.9k | } |
3586 | 173k | } |
3587 | 1.51M | } |
3588 | 215k | } |
3589 | | |
3590 | 2.37M | int VersionStorageInfo::MaxInputLevel() const { |
3591 | 2.37M | if (compaction_style_ == kCompactionStyleLevel) { |
3592 | 2.37M | return num_levels() - 2; |
3593 | 2.37M | } |
3594 | 0 | return 0; |
3595 | 2.37M | } |
3596 | | |
3597 | 223k | int VersionStorageInfo::MaxOutputLevel(bool allow_ingest_behind) const { |
3598 | 223k | if (allow_ingest_behind) { |
3599 | 0 | assert(num_levels() > 1); |
3600 | 0 | return num_levels() - 2; |
3601 | 0 | } |
3602 | 223k | return num_levels() - 1; |
3603 | 223k | } |
3604 | | |
3605 | | void VersionStorageInfo::EstimateCompactionBytesNeeded( |
3606 | 221k | const MutableCFOptions& mutable_cf_options) { |
3607 | | // Only implemented for level-based compaction |
3608 | 221k | if (compaction_style_ != kCompactionStyleLevel) { |
3609 | 0 | estimated_compaction_needed_bytes_ = 0; |
3610 | 0 | return; |
3611 | 0 | } |
3612 | | |
3613 | | // Start from Level 0, if level 0 qualifies compaction to level 1, |
3614 | | // we estimate the size of compaction. |
3615 | | // Then we move on to the next level and see whether it qualifies compaction |
3616 | | // to the next level. The size of the level is estimated as the actual size |
3617 | | // on the level plus the input bytes from the previous level if there is any. |
3618 | | // If it exceeds, take the exceeded bytes as compaction input and add the size |
3619 | | // of the compaction size to tatal size. |
3620 | | // We keep doing it to Level 2, 3, etc, until the last level and return the |
3621 | | // accumulated bytes. |
3622 | | |
3623 | 221k | uint64_t bytes_compact_to_next_level = 0; |
3624 | 221k | uint64_t level_size = 0; |
3625 | 221k | for (auto* f : files_[0]) { |
3626 | 127k | level_size += f->fd.GetFileSize(); |
3627 | 127k | } |
3628 | | // Level 0 |
3629 | 221k | bool level0_compact_triggered = false; |
3630 | 221k | if (static_cast<int>(files_[0].size()) >= |
3631 | 221k | mutable_cf_options.level0_file_num_compaction_trigger || |
3632 | 214k | level_size >= mutable_cf_options.max_bytes_for_level_base) { |
3633 | 6.85k | level0_compact_triggered = true; |
3634 | 6.85k | estimated_compaction_needed_bytes_ = level_size; |
3635 | 6.85k | bytes_compact_to_next_level = level_size; |
3636 | 214k | } else { |
3637 | 214k | estimated_compaction_needed_bytes_ = 0; |
3638 | 214k | } |
3639 | | |
3640 | | // Level 1 and up. |
3641 | 221k | uint64_t bytes_next_level = 0; |
3642 | 221k | for (int level = base_level(); level <= MaxInputLevel(); level++) { |
3643 | 0 | level_size = 0; |
3644 | 0 | if (bytes_next_level > 0) { |
3645 | | #ifndef NDEBUG |
3646 | | uint64_t level_size2 = 0; |
3647 | | for (auto* f : files_[level]) { |
3648 | | level_size2 += f->fd.GetFileSize(); |
3649 | | } |
3650 | | assert(level_size2 == bytes_next_level); |
3651 | | #endif |
3652 | 0 | level_size = bytes_next_level; |
3653 | 0 | bytes_next_level = 0; |
3654 | 0 | } else { |
3655 | 0 | for (auto* f : files_[level]) { |
3656 | 0 | level_size += f->fd.GetFileSize(); |
3657 | 0 | } |
3658 | 0 | } |
3659 | 0 | if (level == base_level() && level0_compact_triggered) { |
3660 | | // Add base level size to compaction if level0 compaction triggered. |
3661 | 0 | estimated_compaction_needed_bytes_ += level_size; |
3662 | 0 | } |
3663 | | // Add size added by previous compaction |
3664 | 0 | level_size += bytes_compact_to_next_level; |
3665 | 0 | bytes_compact_to_next_level = 0; |
3666 | 0 | uint64_t level_target = MaxBytesForLevel(level); |
3667 | 0 | if (level_size > level_target) { |
3668 | 0 | bytes_compact_to_next_level = level_size - level_target; |
3669 | | // Estimate the actual compaction fan-out ratio as size ratio between |
3670 | | // the two levels. |
3671 | |
|
3672 | 0 | assert(bytes_next_level == 0); |
3673 | 0 | if (level + 1 < num_levels_) { |
3674 | 0 | for (auto* f : files_[level + 1]) { |
3675 | 0 | bytes_next_level += f->fd.GetFileSize(); |
3676 | 0 | } |
3677 | 0 | } |
3678 | 0 | if (bytes_next_level > 0) { |
3679 | 0 | assert(level_size > 0); |
3680 | 0 | estimated_compaction_needed_bytes_ += static_cast<uint64_t>( |
3681 | 0 | static_cast<double>(bytes_compact_to_next_level) * |
3682 | 0 | (static_cast<double>(bytes_next_level) / |
3683 | 0 | static_cast<double>(level_size) + |
3684 | 0 | 1)); |
3685 | 0 | } |
3686 | 0 | } |
3687 | 0 | } |
3688 | 221k | } |
3689 | | |
3690 | | namespace { |
3691 | | uint32_t GetExpiredTtlFilesCount(const ImmutableOptions& ioptions, |
3692 | | const MutableCFOptions& mutable_cf_options, |
3693 | 0 | const std::vector<FileMetaData*>& files) { |
3694 | 0 | uint32_t ttl_expired_files_count = 0; |
3695 | |
|
3696 | 0 | int64_t _current_time; |
3697 | 0 | auto status = ioptions.clock->GetCurrentTime(&_current_time); |
3698 | 0 | if (status.ok()) { |
3699 | 0 | const uint64_t current_time = static_cast<uint64_t>(_current_time); |
3700 | 0 | for (FileMetaData* f : files) { |
3701 | 0 | if (!f->being_compacted) { |
3702 | 0 | uint64_t oldest_ancestor_time = f->TryGetOldestAncestorTime(); |
3703 | 0 | if (oldest_ancestor_time != 0 && |
3704 | 0 | oldest_ancestor_time < (current_time - mutable_cf_options.ttl)) { |
3705 | 0 | ttl_expired_files_count++; |
3706 | 0 | } |
3707 | 0 | } |
3708 | 0 | } |
3709 | 0 | } |
3710 | 0 | return ttl_expired_files_count; |
3711 | 0 | } |
3712 | | |
3713 | | bool ShouldChangeFileTemperature(const ImmutableOptions& ioptions, |
3714 | | const MutableCFOptions& mutable_cf_options, |
3715 | 0 | const std::vector<FileMetaData*>& files) { |
3716 | 0 | const std::vector<FileTemperatureAge>& ages = |
3717 | 0 | mutable_cf_options.compaction_options_fifo |
3718 | 0 | .file_temperature_age_thresholds; |
3719 | 0 | if (ages.empty()) { |
3720 | 0 | return false; |
3721 | 0 | } |
3722 | 0 | if (files.empty()) { |
3723 | 0 | return false; |
3724 | 0 | } |
3725 | 0 | int64_t _current_time; |
3726 | 0 | auto status = ioptions.clock->GetCurrentTime(&_current_time); |
3727 | 0 | const uint64_t current_time = static_cast<uint64_t>(_current_time); |
3728 | | // This is the same logic used in |
3729 | | // FIFOCompactionPicker::PickTemperatureChangeCompaction(). |
3730 | 0 | if (status.ok() && current_time >= ages[0].age) { |
3731 | 0 | uint64_t create_time_threshold = current_time - ages[0].age; |
3732 | 0 | Temperature target_temp; |
3733 | 0 | assert(files.size() >= 1); |
3734 | 0 | for (size_t index = files.size(); index >= 1; --index) { |
3735 | 0 | FileMetaData* cur_file = files[index - 1]; |
3736 | 0 | FileMetaData* prev_file = index < 2 ? nullptr : files[index - 2]; |
3737 | 0 | if (!cur_file->being_compacted) { |
3738 | 0 | uint64_t est_newest_key_time = cur_file->TryGetNewestKeyTime(prev_file); |
3739 | | // Newer file could have newest_key_time populated |
3740 | 0 | if (est_newest_key_time == kUnknownNewestKeyTime) { |
3741 | 0 | continue; |
3742 | 0 | } |
3743 | 0 | if (est_newest_key_time > create_time_threshold) { |
3744 | 0 | return false; |
3745 | 0 | } |
3746 | 0 | target_temp = ages[0].temperature; |
3747 | 0 | for (size_t i = 1; i < ages.size(); ++i) { |
3748 | 0 | if (current_time >= ages[i].age && |
3749 | 0 | est_newest_key_time <= current_time - ages[i].age) { |
3750 | 0 | target_temp = ages[i].temperature; |
3751 | 0 | } |
3752 | 0 | } |
3753 | 0 | if (cur_file->temperature != target_temp) { |
3754 | 0 | return true; |
3755 | 0 | } |
3756 | 0 | } |
3757 | 0 | } |
3758 | 0 | } |
3759 | 0 | return false; |
3760 | 0 | } |
3761 | | } // anonymous namespace |
3762 | | |
3763 | | void VersionStorageInfo::ComputeCompactionScore( |
3764 | | const ImmutableOptions& immutable_options, |
3765 | | const MutableCFOptions& mutable_cf_options, |
3766 | 221k | const std::string& full_history_ts_low) { |
3767 | 221k | double total_downcompact_bytes = 0.0; |
3768 | | // Historically, score is defined as actual bytes in a level divided by |
3769 | | // the level's target size, and 1.0 is the threshold for triggering |
3770 | | // compaction. Higher score means higher prioritization. |
3771 | | // Now we keep the compaction triggering condition, but consider more |
3772 | | // factors for prioritization, while still keeping the 1.0 threshold. |
3773 | | // In order to provide flexibility for reducing score while still |
3774 | | // maintaining it to be over 1.0, we scale the original score by 10x |
3775 | | // if it is larger than 1.0. |
3776 | 221k | const double kScoreScale = 10.0; |
3777 | 221k | int max_output_level = |
3778 | 221k | MaxOutputLevel(immutable_options.cf_allow_ingest_behind || |
3779 | 221k | immutable_options.allow_ingest_behind); |
3780 | 1.55M | for (int level = 0; level <= MaxInputLevel(); level++) { |
3781 | 1.33M | double score; |
3782 | 1.33M | if (level == 0) { |
3783 | | // We treat level-0 specially by bounding the number of files |
3784 | | // instead of number of bytes for two reasons: |
3785 | | // |
3786 | | // (1) With larger write-buffer sizes, it is nice not to do too |
3787 | | // many level-0 compactions. |
3788 | | // |
3789 | | // (2) The files in level-0 are merged on every read and |
3790 | | // therefore we wish to avoid too many files when the individual |
3791 | | // file size is small (perhaps because of a small write-buffer |
3792 | | // setting, or very high compression ratios, or lots of |
3793 | | // overwrites/deletions). |
3794 | 221k | int num_sorted_runs = 0; |
3795 | 221k | uint64_t total_size = 0; |
3796 | 221k | for (auto* f : files_[level]) { |
3797 | 127k | total_downcompact_bytes += static_cast<double>(f->fd.GetFileSize()); |
3798 | 127k | if (!f->being_compacted) { |
3799 | 114k | total_size += f->compensated_file_size; |
3800 | 114k | num_sorted_runs++; |
3801 | 114k | } |
3802 | 127k | } |
3803 | 221k | if (compaction_style_ == kCompactionStyleUniversal) { |
3804 | | // For universal compaction, we use level0 score to indicate |
3805 | | // compaction score for the whole DB. Adding other levels as if |
3806 | | // they are L0 files. |
3807 | 0 | for (int i = 1; i <= max_output_level; i++) { |
3808 | | // It's possible that a subset of the files in a level may be in a |
3809 | | // compaction, due to delete triggered compaction or trivial move. |
3810 | | // In that case, the below check may not catch a level being |
3811 | | // compacted as it only checks the first file. The worst that can |
3812 | | // happen is a scheduled compaction thread will find nothing to do. |
3813 | 0 | if (!files_[i].empty() && !files_[i][0]->being_compacted) { |
3814 | 0 | num_sorted_runs++; |
3815 | 0 | } |
3816 | 0 | } |
3817 | 0 | } |
3818 | | |
3819 | 221k | if (compaction_style_ == kCompactionStyleFIFO) { |
3820 | 0 | const auto& fifo_opts = mutable_cf_options.compaction_options_fifo; |
3821 | 0 | uint64_t effective_size = total_size; |
3822 | 0 | uint64_t effective_max = fifo_opts.max_table_files_size; |
3823 | 0 | if (fifo_opts.max_data_files_size > 0) { |
3824 | | // Blob-aware: include blob file sizes in the total |
3825 | 0 | effective_size += GetBlobStats().total_file_size; |
3826 | 0 | effective_max = fifo_opts.max_data_files_size; |
3827 | 0 | } |
3828 | 0 | if (effective_max == 0) { |
3829 | | // avoid divide 0 |
3830 | 0 | effective_max = 1; |
3831 | 0 | } |
3832 | 0 | score = static_cast<double>(effective_size) / effective_max; |
3833 | 0 | if (score < 1 && fifo_opts.allow_compaction) { |
3834 | 0 | score = std::max( |
3835 | 0 | static_cast<double>(num_sorted_runs) / |
3836 | 0 | mutable_cf_options.level0_file_num_compaction_trigger, |
3837 | 0 | score); |
3838 | 0 | } |
3839 | 0 | if (score < 1 && mutable_cf_options.ttl > 0) { |
3840 | 0 | score = |
3841 | 0 | std::max(static_cast<double>(GetExpiredTtlFilesCount( |
3842 | 0 | immutable_options, mutable_cf_options, files_[0])), |
3843 | 0 | score); |
3844 | 0 | } |
3845 | 0 | if (score < 1 && |
3846 | 0 | ShouldChangeFileTemperature(immutable_options, mutable_cf_options, |
3847 | 0 | files_[0])) { |
3848 | | // For FIFO, just need a large enough score to trigger compaction. |
3849 | 0 | const double kScoreForNeedCompaction = 1.1; |
3850 | 0 | score = kScoreForNeedCompaction; |
3851 | 0 | } |
3852 | 221k | } else { |
3853 | | // For universal compaction, if a user configures `max_read_amp`, then |
3854 | | // the score may be a false positive signal. |
3855 | | // `level0_file_num_compaction_trigger` is used as a trigger to check |
3856 | | // if there is any compaction work to do. |
3857 | 221k | score = static_cast<double>(num_sorted_runs) / |
3858 | 221k | mutable_cf_options.level0_file_num_compaction_trigger; |
3859 | 221k | if (compaction_style_ == kCompactionStyleLevel && num_levels() > 1) { |
3860 | | // Level-based involves L0->L0 compactions that can lead to oversized |
3861 | | // L0 files. Take into account size as well to avoid later giant |
3862 | | // compactions to the base level. |
3863 | | // If score in L0 is always too high, L0->LBase will always be |
3864 | | // prioritized over LBase->LBase+1 compaction and LBase will |
3865 | | // accumulate to too large. But if L0 score isn't high enough, L0 will |
3866 | | // accumulate and data is not moved to LBase fast enough. The score |
3867 | | // calculation below takes into account L0 size vs LBase size. |
3868 | 221k | if (immutable_options.level_compaction_dynamic_level_bytes) { |
3869 | 221k | if (total_size >= mutable_cf_options.max_bytes_for_level_base) { |
3870 | | // When calculating estimated_compaction_needed_bytes, we assume |
3871 | | // L0 is qualified as pending compactions. We will need to make |
3872 | | // sure that it qualifies for compaction. |
3873 | | // It might be guaranteed by logic below anyway, but we are |
3874 | | // explicit here to make sure we don't stop writes with no |
3875 | | // compaction scheduled. |
3876 | 0 | score = std::max(score, 1.01); |
3877 | 0 | } |
3878 | 221k | if (total_size > level_max_bytes_[base_level_]) { |
3879 | | // In this case, we compare L0 size with actual LBase size and |
3880 | | // make sure score is more than 1.0 (10.0 after scaled) if L0 is |
3881 | | // larger than LBase. Since LBase score = LBase size / |
3882 | | // (target size + total_downcompact_bytes) where |
3883 | | // total_downcompact_bytes = total_size > LBase size, |
3884 | | // LBase score is lower than 10.0. So L0->LBase is prioritized |
3885 | | // over LBase -> LBase+1. |
3886 | 0 | uint64_t base_level_size = 0; |
3887 | 0 | for (auto f : files_[base_level_]) { |
3888 | 0 | base_level_size += f->compensated_file_size; |
3889 | 0 | } |
3890 | 0 | score = std::max(score, static_cast<double>(total_size) / |
3891 | 0 | static_cast<double>(std::max( |
3892 | 0 | base_level_size, |
3893 | 0 | level_max_bytes_[base_level_]))); |
3894 | 0 | } |
3895 | 221k | if (score > 1.0) { |
3896 | 831 | score *= kScoreScale; |
3897 | 831 | } |
3898 | 221k | } else { |
3899 | 0 | score = std::max(score, |
3900 | 0 | static_cast<double>(total_size) / |
3901 | 0 | mutable_cf_options.max_bytes_for_level_base); |
3902 | 0 | } |
3903 | 221k | } |
3904 | 221k | } |
3905 | 1.10M | } else { // level > 0 |
3906 | | // Compute the ratio of current size to size limit. |
3907 | 1.10M | uint64_t level_bytes_no_compacting = 0; |
3908 | 1.10M | uint64_t level_total_bytes = 0; |
3909 | 1.10M | for (auto f : files_[level]) { |
3910 | 0 | level_total_bytes += f->fd.GetFileSize(); |
3911 | 0 | if (!f->being_compacted) { |
3912 | 0 | level_bytes_no_compacting += f->compensated_file_size; |
3913 | 0 | } |
3914 | 0 | } |
3915 | 1.10M | if (!immutable_options.level_compaction_dynamic_level_bytes) { |
3916 | 0 | score = static_cast<double>(level_bytes_no_compacting) / |
3917 | 0 | MaxBytesForLevel(level); |
3918 | 1.10M | } else { |
3919 | 1.10M | if (level_bytes_no_compacting < MaxBytesForLevel(level)) { |
3920 | 1.10M | score = static_cast<double>(level_bytes_no_compacting) / |
3921 | 1.10M | MaxBytesForLevel(level); |
3922 | 1.10M | } else { |
3923 | | // If there are a large mount of data being compacted down to the |
3924 | | // current level soon, we would de-prioritize compaction from |
3925 | | // a level where the incoming data would be a large ratio. We do |
3926 | | // it by dividing level size not by target level size, but |
3927 | | // the target size and the incoming compaction bytes. |
3928 | 0 | score = static_cast<double>(level_bytes_no_compacting) / |
3929 | 0 | (MaxBytesForLevel(level) + total_downcompact_bytes) * |
3930 | 0 | kScoreScale; |
3931 | 0 | } |
3932 | | // Drain unnecessary levels, but with lower priority compared to |
3933 | | // when L0 is eligible. Only non-empty levels can be unnecessary. |
3934 | | // If there is no unnecessary levels, lowest_unnecessary_level_ = -1. |
3935 | 1.10M | if (level_bytes_no_compacting > 0 && |
3936 | 0 | level <= lowest_unnecessary_level_) { |
3937 | 0 | score = std::max( |
3938 | 0 | score, kScoreScale * |
3939 | 0 | (1.001 + 0.001 * (lowest_unnecessary_level_ - level))); |
3940 | 0 | } |
3941 | 1.10M | } |
3942 | 1.10M | if (level <= lowest_unnecessary_level_) { |
3943 | 0 | total_downcompact_bytes += level_total_bytes; |
3944 | 1.10M | } else if (level_total_bytes > MaxBytesForLevel(level)) { |
3945 | 0 | total_downcompact_bytes += |
3946 | 0 | static_cast<double>(level_total_bytes - MaxBytesForLevel(level)); |
3947 | 0 | } |
3948 | 1.10M | } |
3949 | 1.33M | compaction_level_[level] = level; |
3950 | 1.33M | compaction_score_[level] = score; |
3951 | 1.33M | } |
3952 | | |
3953 | | // sort all the levels based on their score. Higher scores get listed |
3954 | | // first. Use bubble sort because the number of entries are small. |
3955 | 1.33M | for (int i = 0; i < num_levels() - 2; i++) { |
3956 | 4.43M | for (int j = i + 1; j < num_levels() - 1; j++) { |
3957 | 3.32M | if (compaction_score_[i] < compaction_score_[j]) { |
3958 | 0 | double score = compaction_score_[i]; |
3959 | 0 | int level = compaction_level_[i]; |
3960 | 0 | compaction_score_[i] = compaction_score_[j]; |
3961 | 0 | compaction_level_[i] = compaction_level_[j]; |
3962 | 0 | compaction_score_[j] = score; |
3963 | 0 | compaction_level_[j] = level; |
3964 | 0 | } |
3965 | 3.32M | } |
3966 | 1.10M | } |
3967 | 221k | ComputeFilesMarkedForCompaction(max_output_level); |
3968 | 221k | ComputeBottommostFilesMarkedForCompaction( |
3969 | 221k | immutable_options.cf_allow_ingest_behind || |
3970 | 221k | immutable_options.allow_ingest_behind, |
3971 | 221k | immutable_options.user_comparator, full_history_ts_low); |
3972 | 221k | ComputeExpiredTtlFiles(immutable_options, mutable_cf_options.ttl); |
3973 | 221k | ComputeFilesMarkedForPeriodicCompaction( |
3974 | 221k | immutable_options, mutable_cf_options.periodic_compaction_seconds, |
3975 | 221k | max_output_level); |
3976 | 221k | ComputeFilesMarkedForForcedBlobGC( |
3977 | 221k | mutable_cf_options.blob_garbage_collection_age_cutoff, |
3978 | 221k | mutable_cf_options.blob_garbage_collection_force_threshold, |
3979 | 221k | mutable_cf_options.enable_blob_garbage_collection); |
3980 | 221k | ComputeFilesMarkedForReadTriggeredCompaction( |
3981 | 221k | mutable_cf_options.read_triggered_compaction_threshold, |
3982 | 221k | immutable_options.compaction_style); |
3983 | | |
3984 | 221k | EstimateCompactionBytesNeeded(mutable_cf_options); |
3985 | 221k | } |
3986 | | |
3987 | 0 | void VersionStorageInfo::RemoveFilesBeingCompactedFromCompactionCandidates() { |
3988 | 0 | for (auto* candidates : |
3989 | 0 | {&files_marked_for_compaction_, &bottommost_files_marked_for_compaction_, |
3990 | 0 | &expired_ttl_files_, &files_marked_for_periodic_compaction_, |
3991 | 0 | &files_marked_for_forced_blob_gc_, &read_triggered_compaction_files_}) { |
3992 | 0 | const size_t remaining = |
3993 | 0 | std::remove_if(candidates->begin(), candidates->end(), |
3994 | 0 | [](const auto& level_file) { |
3995 | 0 | return level_file.second->being_compacted; |
3996 | 0 | }) - |
3997 | 0 | candidates->begin(); |
3998 | 0 | candidates->resize(remaining); |
3999 | 0 | } |
4000 | 0 | standalone_range_tombstone_files_mark_threshold_ = kMaxSequenceNumber; |
4001 | 0 | for (const auto& level_file : files_marked_for_compaction_) { |
4002 | 0 | if (level_file.second->FileIsStandAloneRangeTombstone()) { |
4003 | 0 | standalone_range_tombstone_files_mark_threshold_ = |
4004 | 0 | std::min(standalone_range_tombstone_files_mark_threshold_, |
4005 | 0 | level_file.second->fd.smallest_seqno); |
4006 | 0 | } |
4007 | 0 | } |
4008 | 0 | } |
4009 | | |
4010 | 221k | void VersionStorageInfo::ComputeFilesMarkedForCompaction(int last_level) { |
4011 | 221k | files_marked_for_compaction_.clear(); |
4012 | 221k | int last_qualify_level = 0; |
4013 | 221k | standalone_range_tombstone_files_mark_threshold_ = kMaxSequenceNumber; |
4014 | | |
4015 | | // Do not include files from the last level with data |
4016 | | // If table properties collector suggests a file on the last level, |
4017 | | // we should not move it to a new level. |
4018 | 1.37M | for (int level = last_level; level >= 1; level--) { |
4019 | 1.18M | if (!files_[level].empty()) { |
4020 | 28.7k | last_qualify_level = level - 1; |
4021 | 28.7k | break; |
4022 | 28.7k | } |
4023 | 1.18M | } |
4024 | | |
4025 | 587k | for (int level = 0; level <= last_qualify_level; level++) { |
4026 | 365k | for (auto* f : files_[level]) { |
4027 | 127k | if (!f->being_compacted && f->marked_for_compaction) { |
4028 | 0 | files_marked_for_compaction_.emplace_back(level, f); |
4029 | 0 | if (f->FileIsStandAloneRangeTombstone()) { |
4030 | 0 | standalone_range_tombstone_files_mark_threshold_ = |
4031 | 0 | std::min(standalone_range_tombstone_files_mark_threshold_, |
4032 | 0 | f->fd.smallest_seqno); |
4033 | 0 | } |
4034 | 0 | } |
4035 | 127k | } |
4036 | 365k | } |
4037 | 221k | } |
4038 | | |
4039 | | void VersionStorageInfo::ComputeExpiredTtlFiles( |
4040 | 221k | const ImmutableOptions& ioptions, const uint64_t ttl) { |
4041 | 221k | expired_ttl_files_.clear(); |
4042 | 221k | if (ttl == 0 || compaction_style_ != CompactionStyle::kCompactionStyleLevel) { |
4043 | 0 | return; |
4044 | 0 | } |
4045 | | |
4046 | 221k | int64_t _current_time; |
4047 | 221k | auto status = ioptions.clock->GetCurrentTime(&_current_time); |
4048 | 221k | if (!status.ok()) { |
4049 | 0 | return; |
4050 | 0 | } |
4051 | 221k | const uint64_t current_time = static_cast<uint64_t>(_current_time); |
4052 | | |
4053 | 1.55M | for (int level = 0; level < num_levels() - 1; level++) { |
4054 | 1.33M | for (FileMetaData* f : files_[level]) { |
4055 | 127k | if (!f->being_compacted) { |
4056 | 114k | uint64_t oldest_ancestor_time = f->TryGetOldestAncestorTime(); |
4057 | 114k | if (oldest_ancestor_time > 0 && |
4058 | 114k | oldest_ancestor_time < (current_time - ttl)) { |
4059 | 0 | expired_ttl_files_.emplace_back(level, f); |
4060 | 0 | } |
4061 | 114k | } |
4062 | 127k | } |
4063 | 1.33M | } |
4064 | 221k | } |
4065 | | |
4066 | | void VersionStorageInfo::ComputeFilesMarkedForPeriodicCompaction( |
4067 | | const ImmutableOptions& ioptions, |
4068 | 221k | const uint64_t periodic_compaction_seconds, int last_level) { |
4069 | 221k | files_marked_for_periodic_compaction_.clear(); |
4070 | 221k | if (periodic_compaction_seconds == 0) { |
4071 | 221k | return; |
4072 | 221k | } |
4073 | | |
4074 | 0 | int64_t temp_current_time; |
4075 | 0 | auto status = ioptions.clock->GetCurrentTime(&temp_current_time); |
4076 | 0 | if (!status.ok()) { |
4077 | 0 | return; |
4078 | 0 | } |
4079 | 0 | const uint64_t current_time = static_cast<uint64_t>(temp_current_time); |
4080 | | |
4081 | | // If periodic_compaction_seconds is larger than current time, periodic |
4082 | | // compaction can't possibly be triggered. |
4083 | 0 | if (periodic_compaction_seconds > current_time) { |
4084 | 0 | return; |
4085 | 0 | } |
4086 | | |
4087 | | // Existing offpeak behavior pulls a file's deadline earlier by the time until |
4088 | | // the next daily offpeak window (so a whole TTL's worth can be marked at the |
4089 | | // start of offpeak). |
4090 | 0 | const OffpeakTimeInfo offpeak_time_info = |
4091 | 0 | offpeak_time_option_.GetOffpeakTimeInfo(current_time); |
4092 | 0 | const uint64_t offpeak_pull = |
4093 | 0 | offpeak_time_info.is_now_offpeak |
4094 | 0 | ? offpeak_time_info.seconds_till_next_offpeak_start |
4095 | 0 | : 0; |
4096 | | |
4097 | | // Preferred-phase scheduling (see |
4098 | | // DBOptions::periodic_compaction_phase_recovery_percent). When |
4099 | | // recovery_percent == 0 this is disabled and marking matches the classic |
4100 | | // "file age >= periodic_compaction_seconds" behavior (adjusted for offpeak). |
4101 | 0 | const PeriodicCompactionPhaseParams& phase_params = |
4102 | 0 | periodic_compaction_phase_params_; |
4103 | |
|
4104 | 0 | for (int level = 0; level <= last_level; level++) { |
4105 | 0 | for (auto f : files_[level]) { |
4106 | 0 | if (!f->being_compacted) { |
4107 | | // Compute a file's modification time in the following order: |
4108 | | // 1. Use file_creation_time table property if it is > 0. |
4109 | | // 2. Use oldest_ancestor_time table property if it is > 0. |
4110 | | // 3. Use file's mtime metadata if the above two table properties are 0. |
4111 | | // Don't consider the file at all if the modification time cannot be |
4112 | | // correctly determined based on the above conditions. |
4113 | 0 | uint64_t file_modification_time = f->TryGetFileCreationTime(); |
4114 | 0 | if (file_modification_time == kUnknownFileCreationTime) { |
4115 | 0 | file_modification_time = f->TryGetOldestAncestorTime(); |
4116 | 0 | } |
4117 | 0 | if (file_modification_time == kUnknownOldestAncestorTime) { |
4118 | 0 | auto file_path = TableFileName(ioptions.cf_paths, f->fd.GetNumber(), |
4119 | 0 | f->fd.GetPathId()); |
4120 | 0 | status = ioptions.env->GetFileModificationTime( |
4121 | 0 | file_path, &file_modification_time); |
4122 | 0 | if (!status.ok()) { |
4123 | 0 | ROCKS_LOG_WARN(ioptions.logger, |
4124 | 0 | "Can't get file modification time: %s: %s", |
4125 | 0 | file_path.c_str(), status.ToString().c_str()); |
4126 | 0 | continue; |
4127 | 0 | } |
4128 | 0 | } |
4129 | 0 | if (file_modification_time == 0) { |
4130 | 0 | continue; |
4131 | 0 | } |
4132 | | |
4133 | 0 | const uint64_t trigger_time = PeriodicCompactionPhaser::TriggerTime( |
4134 | 0 | file_modification_time, periodic_compaction_seconds, phase_params); |
4135 | 0 | if (current_time + offpeak_pull > trigger_time) { |
4136 | 0 | files_marked_for_periodic_compaction_.emplace_back(level, f); |
4137 | 0 | } |
4138 | 0 | } |
4139 | 0 | } |
4140 | 0 | } |
4141 | 0 | } |
4142 | | |
4143 | | void VersionStorageInfo::ComputeFilesMarkedForForcedBlobGC( |
4144 | | double blob_garbage_collection_age_cutoff, |
4145 | | double blob_garbage_collection_force_threshold, |
4146 | 221k | bool enable_blob_garbage_collection) { |
4147 | 221k | files_marked_for_forced_blob_gc_.clear(); |
4148 | 221k | if (!(enable_blob_garbage_collection && |
4149 | 0 | blob_garbage_collection_age_cutoff > 0.0 && |
4150 | 221k | blob_garbage_collection_force_threshold < 1.0)) { |
4151 | 221k | return; |
4152 | 221k | } |
4153 | | |
4154 | 0 | if (blob_files_.empty()) { |
4155 | 0 | return; |
4156 | 0 | } |
4157 | | |
4158 | | // Number of blob files eligible for GC based on age |
4159 | 0 | const size_t cutoff_count = static_cast<size_t>( |
4160 | 0 | blob_garbage_collection_age_cutoff * blob_files_.size()); |
4161 | 0 | if (!cutoff_count) { |
4162 | 0 | return; |
4163 | 0 | } |
4164 | | |
4165 | | // Compute the sum of total and garbage bytes over the batch of blob files |
4166 | | // currently eligible for garbage collection based on |
4167 | | // blob_garbage_collection_age_cutoff, and if the garbage ratio exceeds |
4168 | | // blob_garbage_collection_force_threshold, schedule compaction for the |
4169 | | // SST files that reference the oldest batch of blob files. Here is a toy |
4170 | | // example. Let's assume we have three SSTs 1, 2, and 3, and four blob files |
4171 | | // 10, 11, 12, and 13, which correspond to the range that is eligible for GC |
4172 | | // and satisfy the garbage ratio threshold. Also, let's say SSTs 1 and 2 both |
4173 | | // rely on blob file 10 and potentially some higher-numbered ones, while SST 3 |
4174 | | // relies on blob file 12 and potentially some higher-numbered ones. Then, the |
4175 | | // SST to oldest blob file mapping is as follows: |
4176 | | // |
4177 | | // SST file number Oldest blob file number |
4178 | | // 1 10 |
4179 | | // 2 10 |
4180 | | // 3 12 |
4181 | | // |
4182 | | // This is what the same thing looks like from the blob files' POV. (Note that |
4183 | | // the linked SSTs simply denote the inverse mapping of the above.) |
4184 | | // |
4185 | | // Blob file number Linked SST set |
4186 | | // 10 {1, 2} |
4187 | | // 11 {} |
4188 | | // 12 {3} |
4189 | | // 13 {} |
4190 | | // |
4191 | | // Then, the oldest batch of blob files consists of blob files 10 and 11, |
4192 | | // and we can get rid of them by forcing the compaction of SSTs 1 and 2. |
4193 | 0 | const auto& oldest_meta = blob_files_.front(); |
4194 | 0 | assert(oldest_meta); |
4195 | |
|
4196 | 0 | const auto& linked_ssts = oldest_meta->GetLinkedSsts(); |
4197 | 0 | if (linked_ssts.empty()) { |
4198 | | // SST files that reference the oldest blob file are linked to an older, |
4199 | | // dropped blob file instead, so the SST files to compact are unknown. |
4200 | 0 | assert(!dropped_blob_files_.empty()); |
4201 | 0 | return; |
4202 | 0 | } |
4203 | | |
4204 | 0 | size_t count = 1; |
4205 | 0 | uint64_t sum_total_blob_bytes = oldest_meta->GetTotalBlobBytes(); |
4206 | 0 | uint64_t sum_garbage_blob_bytes = oldest_meta->GetGarbageBlobBytes(); |
4207 | |
|
4208 | 0 | assert(cutoff_count <= blob_files_.size()); |
4209 | |
|
4210 | 0 | for (; count < cutoff_count; ++count) { |
4211 | 0 | const auto& meta = blob_files_[count]; |
4212 | 0 | assert(meta); |
4213 | |
|
4214 | 0 | sum_total_blob_bytes += meta->GetTotalBlobBytes(); |
4215 | 0 | sum_garbage_blob_bytes += meta->GetGarbageBlobBytes(); |
4216 | 0 | } |
4217 | |
|
4218 | 0 | if (sum_garbage_blob_bytes < |
4219 | 0 | blob_garbage_collection_force_threshold * sum_total_blob_bytes) { |
4220 | 0 | return; |
4221 | 0 | } |
4222 | | |
4223 | 0 | for (uint64_t sst_file_number : linked_ssts) { |
4224 | 0 | const FileLocation location = GetFileLocation(sst_file_number); |
4225 | 0 | assert(location.IsValid()); |
4226 | |
|
4227 | 0 | const int level = location.GetLevel(); |
4228 | 0 | assert(level >= 0); |
4229 | |
|
4230 | 0 | const size_t pos = location.GetPosition(); |
4231 | |
|
4232 | 0 | FileMetaData* const sst_meta = files_[level][pos]; |
4233 | 0 | assert(sst_meta); |
4234 | |
|
4235 | 0 | if (sst_meta->being_compacted) { |
4236 | 0 | continue; |
4237 | 0 | } |
4238 | | |
4239 | 0 | files_marked_for_forced_blob_gc_.emplace_back(level, sst_meta); |
4240 | 0 | } |
4241 | 0 | } |
4242 | | |
4243 | | void VersionStorageInfo::ComputeFilesMarkedForReadTriggeredCompaction( |
4244 | 221k | double threshold, CompactionStyle compaction_style) { |
4245 | 221k | read_triggered_compaction_files_.clear(); |
4246 | 221k | if (threshold <= 0.0 || compaction_style == kCompactionStyleFIFO) { |
4247 | 221k | return; |
4248 | 221k | } |
4249 | | |
4250 | | // Skip files at the last non-empty level -- there is no lower level to |
4251 | | // compact into. Exception: L0 files are allowed even when L0 is the last |
4252 | | // non-empty level, because in single-level universal or FIFO compaction, L0 |
4253 | | // files can be compacted together (L0 -> L0). |
4254 | 0 | int last_non_empty = num_non_empty_levels_ - 1; |
4255 | |
|
4256 | 0 | for (int level = 0; level < num_levels(); level++) { |
4257 | 0 | if (level >= last_non_empty && level != 0) { |
4258 | 0 | continue; |
4259 | 0 | } |
4260 | 0 | for (auto* f : files_[level]) { |
4261 | 0 | if (f->being_compacted) { |
4262 | 0 | continue; |
4263 | 0 | } |
4264 | 0 | uint64_t file_size = f->fd.GetFileSize(); |
4265 | 0 | if (file_size == 0) { |
4266 | 0 | continue; |
4267 | 0 | } |
4268 | 0 | double reads_per_byte = |
4269 | 0 | static_cast<double>(f->stats.num_collapsible_entry_reads_sampled.load( |
4270 | 0 | std::memory_order_relaxed)) / |
4271 | 0 | static_cast<double>(file_size); |
4272 | 0 | if (reads_per_byte > threshold) { |
4273 | 0 | read_triggered_compaction_files_.emplace_back(level, f); |
4274 | 0 | } |
4275 | 0 | } |
4276 | 0 | } |
4277 | | |
4278 | | // Snapshot reads_per_byte for each file so the sort comparator sees stable |
4279 | | // values. |
4280 | 0 | std::unordered_map<const FileMetaData*, double> reads_per_byte_snapshot; |
4281 | 0 | reads_per_byte_snapshot.reserve(read_triggered_compaction_files_.size()); |
4282 | 0 | for (const auto& entry : read_triggered_compaction_files_) { |
4283 | 0 | const auto* f = entry.second; |
4284 | 0 | uint64_t file_size = f->fd.GetFileSize(); |
4285 | 0 | reads_per_byte_snapshot[f] = |
4286 | 0 | file_size > 0 ? static_cast<double>( |
4287 | 0 | f->stats.num_collapsible_entry_reads_sampled.load( |
4288 | 0 | std::memory_order_relaxed)) / |
4289 | 0 | static_cast<double>(file_size) |
4290 | 0 | : 0.0; |
4291 | 0 | } |
4292 | | |
4293 | | // Sort by reads_per_byte descending so the hottest files are picked first |
4294 | 0 | std::sort(read_triggered_compaction_files_.begin(), |
4295 | 0 | read_triggered_compaction_files_.end(), |
4296 | 0 | [&reads_per_byte_snapshot](const auto& a, const auto& b) { |
4297 | 0 | return reads_per_byte_snapshot.at(a.second) > |
4298 | 0 | reads_per_byte_snapshot.at(b.second); |
4299 | 0 | }); |
4300 | 0 | } |
4301 | | |
4302 | | namespace { |
4303 | | |
4304 | | // used to sort files by size |
4305 | | struct Fsize { |
4306 | | size_t index; |
4307 | | FileMetaData* file; |
4308 | | }; |
4309 | | |
4310 | | // Comparator that is used to sort files based on their size |
4311 | | // In normal mode: descending size |
4312 | 0 | bool CompareCompensatedSizeDescending(const Fsize& first, const Fsize& second) { |
4313 | 0 | return (first.file->compensated_file_size > |
4314 | 0 | second.file->compensated_file_size); |
4315 | 0 | } |
4316 | | } // anonymous namespace |
4317 | | |
4318 | 173k | void VersionStorageInfo::AddFile(int level, FileMetaData* f) { |
4319 | 173k | auto& level_files = files_[level]; |
4320 | 173k | level_files.push_back(f); |
4321 | | |
4322 | 173k | f->refs++; |
4323 | 173k | } |
4324 | | |
4325 | | void VersionStorageInfo::AddBlobFile( |
4326 | 0 | std::shared_ptr<BlobFileMetaData> blob_file_meta) { |
4327 | 0 | assert(blob_file_meta); |
4328 | |
|
4329 | 0 | assert(blob_files_.empty() || |
4330 | 0 | (blob_files_.back() && blob_files_.back()->GetBlobFileNumber() < |
4331 | 0 | blob_file_meta->GetBlobFileNumber())); |
4332 | |
|
4333 | 0 | blob_files_.emplace_back(std::move(blob_file_meta)); |
4334 | 0 | } |
4335 | | |
4336 | | VersionStorageInfo::BlobFiles::const_iterator |
4337 | 0 | VersionStorageInfo::GetBlobFileMetaDataLB(uint64_t blob_file_number) const { |
4338 | 0 | return std::lower_bound( |
4339 | 0 | blob_files_.begin(), blob_files_.end(), blob_file_number, |
4340 | 0 | [](const std::shared_ptr<BlobFileMetaData>& lhs, uint64_t rhs) { |
4341 | 0 | assert(lhs); |
4342 | 0 | return lhs->GetBlobFileNumber() < rhs; |
4343 | 0 | }); |
4344 | 0 | } |
4345 | | |
4346 | 215k | void VersionStorageInfo::SetFinalized() { |
4347 | 215k | finalized_ = true; |
4348 | | |
4349 | | #ifndef NDEBUG |
4350 | | if (compaction_style_ != kCompactionStyleLevel) { |
4351 | | // Not level based compaction. |
4352 | | return; |
4353 | | } |
4354 | | assert(base_level_ < 0 || num_levels() == 1 || |
4355 | | (base_level_ >= 1 && base_level_ < num_levels())); |
4356 | | // Verify all levels newer than base_level are empty except L0 |
4357 | | for (int level = 1; level < base_level(); level++) { |
4358 | | assert(NumLevelBytes(level) == 0); |
4359 | | } |
4360 | | uint64_t max_bytes_prev_level = 0; |
4361 | | for (int level = base_level(); level < num_levels() - 1; level++) { |
4362 | | if (LevelFiles(level).size() == 0) { |
4363 | | continue; |
4364 | | } |
4365 | | assert(MaxBytesForLevel(level) >= max_bytes_prev_level); |
4366 | | max_bytes_prev_level = MaxBytesForLevel(level); |
4367 | | } |
4368 | | for (int level = 0; level < num_levels(); level++) { |
4369 | | assert(LevelFiles(level).size() == 0 || |
4370 | | LevelFiles(level).size() == LevelFilesBrief(level).num_files); |
4371 | | if (LevelFiles(level).size() > 0) { |
4372 | | assert(level < num_non_empty_levels()); |
4373 | | } |
4374 | | } |
4375 | | assert(compaction_level_.size() > 0); |
4376 | | assert(compaction_level_.size() == compaction_score_.size()); |
4377 | | #endif |
4378 | 215k | } |
4379 | | |
4380 | 215k | void VersionStorageInfo::UpdateNumNonEmptyLevels() { |
4381 | 215k | num_non_empty_levels_ = num_levels_; |
4382 | 1.50M | for (int i = num_levels_ - 1; i >= 0; i--) { |
4383 | 1.35M | if (files_[i].size() != 0) { |
4384 | 62.4k | return; |
4385 | 1.29M | } else { |
4386 | 1.29M | num_non_empty_levels_ = i; |
4387 | 1.29M | } |
4388 | 1.35M | } |
4389 | 215k | } |
4390 | | |
4391 | | namespace { |
4392 | | // Sort `temp` based on ratio of overlapping size over file size |
4393 | | void SortFileByOverlappingRatio( |
4394 | | const InternalKeyComparator& icmp, const std::vector<FileMetaData*>& files, |
4395 | | const std::vector<FileMetaData*>& next_level_files, SystemClock* clock, |
4396 | | int level, int num_non_empty_levels, uint64_t ttl, |
4397 | 1.29M | std::vector<Fsize>* temp) { |
4398 | 1.29M | std::unordered_map<uint64_t, uint64_t> file_to_order; |
4399 | 1.29M | auto next_level_it = next_level_files.begin(); |
4400 | | |
4401 | 1.29M | int64_t curr_time; |
4402 | 1.29M | Status status = clock->GetCurrentTime(&curr_time); |
4403 | 1.29M | if (!status.ok()) { |
4404 | | // If we can't get time, disable TTL. |
4405 | 0 | ttl = 0; |
4406 | 0 | } |
4407 | | |
4408 | 1.29M | FileTtlBooster ttl_booster(static_cast<uint64_t>(curr_time), ttl, |
4409 | 1.29M | num_non_empty_levels, level); |
4410 | | |
4411 | 1.29M | for (auto& file : files) { |
4412 | 109k | uint64_t overlapping_bytes = 0; |
4413 | | // Skip files in next level that is smaller than current file |
4414 | 109k | while (next_level_it != next_level_files.end() && |
4415 | 0 | icmp.Compare((*next_level_it)->largest, file->smallest) < 0) { |
4416 | 0 | next_level_it++; |
4417 | 0 | } |
4418 | | |
4419 | 109k | while (next_level_it != next_level_files.end() && |
4420 | 0 | icmp.Compare((*next_level_it)->smallest, file->largest) < 0) { |
4421 | 0 | overlapping_bytes += (*next_level_it)->fd.file_size; |
4422 | |
|
4423 | 0 | if (icmp.Compare((*next_level_it)->largest, file->largest) > 0) { |
4424 | | // next level file cross large boundary of current file. |
4425 | 0 | break; |
4426 | 0 | } |
4427 | 0 | next_level_it++; |
4428 | 0 | } |
4429 | | |
4430 | 109k | uint64_t ttl_boost_score = (ttl > 0) ? ttl_booster.GetBoostScore(file) : 1; |
4431 | 109k | assert(ttl_boost_score > 0); |
4432 | 109k | assert(file->compensated_file_size != 0); |
4433 | 109k | file_to_order[file->fd.GetNumber()] = overlapping_bytes * 1024U / |
4434 | 109k | file->compensated_file_size / |
4435 | 109k | ttl_boost_score; |
4436 | 109k | } |
4437 | | |
4438 | 1.29M | size_t num_to_sort = temp->size() > VersionStorageInfo::kNumberFilesToSort |
4439 | 1.29M | ? VersionStorageInfo::kNumberFilesToSort |
4440 | 1.29M | : temp->size(); |
4441 | | |
4442 | 1.29M | std::partial_sort( |
4443 | 1.29M | temp->begin(), temp->begin() + num_to_sort, temp->end(), |
4444 | 1.29M | [&](const Fsize& f1, const Fsize& f2) -> bool { |
4445 | | // If score is the same, pick file with smaller keys. |
4446 | | // This makes the algorithm more deterministic, and also |
4447 | | // help the trivial move case to have more files to |
4448 | | // extend. |
4449 | 99.9k | if (f1.file->marked_for_compaction == f2.file->marked_for_compaction) { |
4450 | 99.9k | if (file_to_order[f1.file->fd.GetNumber()] == |
4451 | 99.9k | file_to_order[f2.file->fd.GetNumber()]) { |
4452 | 99.9k | return icmp.Compare(f1.file->smallest, f2.file->smallest) < 0; |
4453 | 99.9k | } |
4454 | 0 | return file_to_order[f1.file->fd.GetNumber()] < |
4455 | 0 | file_to_order[f2.file->fd.GetNumber()]; |
4456 | 99.9k | } else { |
4457 | 0 | return f1.file->marked_for_compaction > |
4458 | 0 | f2.file->marked_for_compaction; |
4459 | 0 | } |
4460 | 99.9k | }); |
4461 | 1.29M | } |
4462 | | |
4463 | | void SortFileByRoundRobin(const InternalKeyComparator& icmp, |
4464 | | std::vector<InternalKey>* compact_cursor, |
4465 | | bool level0_non_overlapping, int level, |
4466 | 0 | std::vector<Fsize>* temp) { |
4467 | 0 | if (level == 0 && !level0_non_overlapping) { |
4468 | | // Using kOldestSmallestSeqFirst when level === 0, since the |
4469 | | // files may overlap (not fully sorted) |
4470 | 0 | std::sort(temp->begin(), temp->end(), |
4471 | 0 | [](const Fsize& f1, const Fsize& f2) -> bool { |
4472 | 0 | return f1.file->fd.smallest_seqno < f2.file->fd.smallest_seqno; |
4473 | 0 | }); |
4474 | 0 | return; |
4475 | 0 | } |
4476 | | |
4477 | 0 | bool should_move_files = |
4478 | 0 | compact_cursor->at(level).size() > 0 && temp->size() > 1; |
4479 | | |
4480 | | // The iterator points to the Fsize with smallest key larger than or equal to |
4481 | | // the given cursor |
4482 | 0 | std::vector<Fsize>::iterator current_file_iter; |
4483 | 0 | if (should_move_files) { |
4484 | | // Find the file of which the smallest key is larger than or equal to |
4485 | | // the cursor (the smallest key in the successor file of the last |
4486 | | // chosen file), skip this if the cursor is invalid or there is only |
4487 | | // one file in this level |
4488 | 0 | current_file_iter = std::lower_bound( |
4489 | 0 | temp->begin(), temp->end(), compact_cursor->at(level), |
4490 | 0 | [&](const Fsize& f, const InternalKey& cursor) -> bool { |
4491 | 0 | return icmp.Compare(cursor, f.file->smallest) > 0; |
4492 | 0 | }); |
4493 | |
|
4494 | 0 | should_move_files = |
4495 | 0 | current_file_iter != temp->end() && current_file_iter != temp->begin(); |
4496 | 0 | } |
4497 | 0 | if (should_move_files) { |
4498 | | // Construct a local temporary vector |
4499 | 0 | std::vector<Fsize> local_temp; |
4500 | 0 | local_temp.reserve(temp->size()); |
4501 | | // Move the selected File into the first position and its successors |
4502 | | // into the second, third, ..., positions |
4503 | 0 | for (auto iter = current_file_iter; iter != temp->end(); iter++) { |
4504 | 0 | local_temp.push_back(*iter); |
4505 | 0 | } |
4506 | | // Move the origin predecessors of the selected file in a round-robin |
4507 | | // manner |
4508 | 0 | for (auto iter = temp->begin(); iter != current_file_iter; iter++) { |
4509 | 0 | local_temp.push_back(*iter); |
4510 | 0 | } |
4511 | | // Replace all the items in temp |
4512 | 0 | for (size_t i = 0; i < local_temp.size(); i++) { |
4513 | 0 | temp->at(i) = local_temp[i]; |
4514 | 0 | } |
4515 | 0 | } |
4516 | 0 | } |
4517 | | } // anonymous namespace |
4518 | | |
4519 | | void VersionStorageInfo::UpdateFilesByCompactionPri( |
4520 | 215k | const ImmutableOptions& ioptions, const MutableCFOptions& options) { |
4521 | 215k | if (compaction_style_ == kCompactionStyleNone || |
4522 | 215k | compaction_style_ == kCompactionStyleFIFO || |
4523 | 215k | compaction_style_ == kCompactionStyleUniversal) { |
4524 | | // don't need this |
4525 | 0 | return; |
4526 | 0 | } |
4527 | | // No need to sort the highest level because it is never compacted. |
4528 | 1.51M | for (int level = 0; level < num_levels() - 1; level++) { |
4529 | 1.29M | const std::vector<FileMetaData*>& files = files_[level]; |
4530 | 1.29M | auto& files_by_compaction_pri = files_by_compaction_pri_[level]; |
4531 | 1.29M | assert(files_by_compaction_pri.size() == 0); |
4532 | | |
4533 | | // populate a temp vector for sorting based on size |
4534 | 1.29M | std::vector<Fsize> temp(files.size()); |
4535 | 1.40M | for (size_t i = 0; i < files.size(); i++) { |
4536 | 109k | temp[i].index = i; |
4537 | 109k | temp[i].file = files[i]; |
4538 | 109k | } |
4539 | | |
4540 | | // sort the top kNumberFilesToSort based on file size |
4541 | 1.29M | size_t num = VersionStorageInfo::kNumberFilesToSort; |
4542 | 1.29M | if (num > temp.size()) { |
4543 | 1.29M | num = temp.size(); |
4544 | 1.29M | } |
4545 | 1.29M | switch (ioptions.compaction_pri) { |
4546 | 0 | case kByCompensatedSize: |
4547 | 0 | std::partial_sort(temp.begin(), temp.begin() + num, temp.end(), |
4548 | 0 | CompareCompensatedSizeDescending); |
4549 | 0 | break; |
4550 | 0 | case kOldestLargestSeqFirst: |
4551 | 0 | std::sort(temp.begin(), temp.end(), |
4552 | 0 | [](const Fsize& f1, const Fsize& f2) -> bool { |
4553 | 0 | return f1.file->fd.largest_seqno < |
4554 | 0 | f2.file->fd.largest_seqno; |
4555 | 0 | }); |
4556 | 0 | break; |
4557 | 0 | case kOldestSmallestSeqFirst: |
4558 | 0 | std::sort(temp.begin(), temp.end(), |
4559 | 0 | [](const Fsize& f1, const Fsize& f2) -> bool { |
4560 | 0 | return f1.file->fd.smallest_seqno < |
4561 | 0 | f2.file->fd.smallest_seqno; |
4562 | 0 | }); |
4563 | 0 | break; |
4564 | 1.29M | case kMinOverlappingRatio: |
4565 | 1.29M | SortFileByOverlappingRatio(*internal_comparator_, files_[level], |
4566 | 1.29M | files_[level + 1], ioptions.clock, level, |
4567 | 1.29M | num_non_empty_levels_, options.ttl, &temp); |
4568 | 1.29M | break; |
4569 | 0 | case kRoundRobin: |
4570 | 0 | SortFileByRoundRobin(*internal_comparator_, &compact_cursor_, |
4571 | 0 | level0_non_overlapping_, level, &temp); |
4572 | 0 | break; |
4573 | 0 | default: |
4574 | 0 | assert(false); |
4575 | 1.29M | } |
4576 | 1.29M | assert(temp.size() == files.size()); |
4577 | | |
4578 | | // initialize files_by_compaction_pri_ |
4579 | 1.40M | for (size_t i = 0; i < temp.size(); i++) { |
4580 | 109k | files_by_compaction_pri.push_back(static_cast<int>(temp[i].index)); |
4581 | 109k | } |
4582 | 1.29M | next_file_to_compact_by_size_[level] = 0; |
4583 | 1.29M | assert(files_[level].size() == files_by_compaction_pri_[level].size()); |
4584 | 1.29M | } |
4585 | 215k | } |
4586 | | |
4587 | 215k | void VersionStorageInfo::GenerateLevel0NonOverlapping() { |
4588 | 215k | assert(!finalized_); |
4589 | 215k | level0_non_overlapping_ = true; |
4590 | 215k | if (level_files_brief_.size() == 0) { |
4591 | 153k | return; |
4592 | 153k | } |
4593 | | |
4594 | | // A copy of L0 files sorted by smallest key |
4595 | 62.4k | std::vector<FdWithKeyRange> level0_sorted_file( |
4596 | 62.4k | level_files_brief_[0].files, |
4597 | 62.4k | level_files_brief_[0].files + level_files_brief_[0].num_files); |
4598 | 62.4k | std::sort(level0_sorted_file.begin(), level0_sorted_file.end(), |
4599 | 62.4k | [this](const FdWithKeyRange& f1, const FdWithKeyRange& f2) -> bool { |
4600 | 59.7k | return (internal_comparator_->Compare(f1.smallest_key, |
4601 | 59.7k | f2.smallest_key) < 0); |
4602 | 59.7k | }); |
4603 | | |
4604 | 112k | for (size_t i = 1; i < level0_sorted_file.size(); ++i) { |
4605 | 51.9k | FdWithKeyRange& f = level0_sorted_file[i]; |
4606 | 51.9k | FdWithKeyRange& prev = level0_sorted_file[i - 1]; |
4607 | 51.9k | if (internal_comparator_->Compare(prev.largest_key, f.smallest_key) >= 0) { |
4608 | 1.69k | level0_non_overlapping_ = false; |
4609 | 1.69k | break; |
4610 | 1.69k | } |
4611 | 51.9k | } |
4612 | 62.4k | } |
4613 | | |
4614 | 215k | void VersionStorageInfo::GenerateBottommostFiles() { |
4615 | 215k | assert(!finalized_); |
4616 | 215k | assert(bottommost_files_.empty()); |
4617 | 432k | for (size_t level = 0; level < level_files_brief_.size(); ++level) { |
4618 | 389k | for (size_t file_idx = 0; file_idx < level_files_brief_[level].num_files; |
4619 | 216k | ++file_idx) { |
4620 | 173k | const FdWithKeyRange& f = level_files_brief_[level].files[file_idx]; |
4621 | 173k | int l0_file_idx; |
4622 | 173k | if (level == 0) { |
4623 | 109k | l0_file_idx = static_cast<int>(file_idx); |
4624 | 109k | } else { |
4625 | 63.9k | l0_file_idx = -1; |
4626 | 63.9k | } |
4627 | 173k | Slice smallest_user_key = ExtractUserKey(f.smallest_key); |
4628 | 173k | Slice largest_user_key = ExtractUserKey(f.largest_key); |
4629 | 173k | if (!RangeMightExistAfterSortedRun(smallest_user_key, largest_user_key, |
4630 | 173k | static_cast<int>(level), |
4631 | 173k | l0_file_idx)) { |
4632 | 100k | bottommost_files_.emplace_back(static_cast<int>(level), |
4633 | 100k | f.file_metadata); |
4634 | 100k | } |
4635 | 173k | } |
4636 | 216k | } |
4637 | 215k | } |
4638 | | |
4639 | 215k | void VersionStorageInfo::GenerateFileLocationIndex() { |
4640 | 215k | size_t num_files = 0; |
4641 | | |
4642 | 1.72M | for (int level = 0; level < num_levels_; ++level) { |
4643 | 1.51M | num_files += files_[level].size(); |
4644 | 1.51M | } |
4645 | | |
4646 | 215k | file_locations_.reserve(num_files); |
4647 | | |
4648 | 1.72M | for (int level = 0; level < num_levels_; ++level) { |
4649 | 1.68M | for (size_t pos = 0; pos < files_[level].size(); ++pos) { |
4650 | 173k | const FileMetaData* const meta = files_[level][pos]; |
4651 | 173k | assert(meta); |
4652 | | |
4653 | 173k | const uint64_t file_number = meta->fd.GetNumber(); |
4654 | | |
4655 | 173k | assert(file_locations_.find(file_number) == file_locations_.end()); |
4656 | 173k | file_locations_.emplace(file_number, FileLocation(level, pos)); |
4657 | 173k | } |
4658 | 1.51M | } |
4659 | 215k | } |
4660 | | |
4661 | | void VersionStorageInfo::UpdateOldestSnapshot( |
4662 | | SequenceNumber seqnum, bool allow_ingest_behind, const Comparator* ucmp, |
4663 | 1.36k | const std::string& full_history_ts_low) { |
4664 | 1.36k | assert(seqnum >= oldest_snapshot_seqnum_); |
4665 | 1.36k | oldest_snapshot_seqnum_ = seqnum; |
4666 | 1.36k | if (oldest_snapshot_seqnum_ > bottommost_files_mark_threshold_) { |
4667 | 644 | ComputeBottommostFilesMarkedForCompaction(allow_ingest_behind, ucmp, |
4668 | 644 | full_history_ts_low); |
4669 | 644 | } |
4670 | 1.36k | } |
4671 | | |
4672 | | void VersionStorageInfo::ComputeBottommostFilesMarkedForCompaction( |
4673 | | bool allow_ingest_behind, const Comparator* ucmp, |
4674 | 222k | const std::string& full_history_ts_low) { |
4675 | 222k | bottommost_files_marked_for_compaction_.clear(); |
4676 | 222k | bottommost_files_mark_threshold_ = kMaxSequenceNumber; |
4677 | 222k | if (allow_ingest_behind) { |
4678 | 0 | return; |
4679 | 0 | } |
4680 | | // If this DB began holding a file later than ownership_start_time_ub, it is |
4681 | | // too new to be marked for compaction. (Measuring from when the DB took the |
4682 | | // file rather than from file creation means a file ingested with old |
4683 | | // contents still gets the delay.) |
4684 | 222k | int64_t ownership_start_time_ub = 0; |
4685 | 222k | bool needs_delay = bottommost_file_compaction_delay_ > 0; |
4686 | 222k | if (needs_delay) { |
4687 | 0 | int64_t current_time = 0; |
4688 | 0 | clock_->GetCurrentTime(¤t_time).PermitUncheckedError(); |
4689 | | // Note that if GetCurrentTime() fails, current_time will be 0. |
4690 | | // We will treat it as is and treat all files as too new. |
4691 | | // The subtraction will not underflow since |
4692 | | // bottommost_file_compaction_delay_ is of type uint32_t. |
4693 | 0 | ownership_start_time_ub = |
4694 | 0 | current_time - static_cast<int64_t>(bottommost_file_compaction_delay_); |
4695 | 0 | } |
4696 | | |
4697 | | // A bottommost file should only be marked for compaction when that compaction |
4698 | | // could actually zero out its largest sequence number; otherwise the rewrite |
4699 | | // makes no progress and the file is re-marked forever (infinite compaction |
4700 | | // loop). The zeroability conditions -- the seqno->time "preserve" window and |
4701 | | // the UDT history cutoff -- are shared with CompactionIterator::PrepareOutput |
4702 | | // through BottommostSeqnoCanBeZeroed() so the two decisions cannot drift. |
4703 | | // Snapshot visibility is checked separately (largest_seqno < |
4704 | | // oldest_snapshot_seqnum_) since that gate legitimately differs between the |
4705 | | // two sites. |
4706 | 222k | const size_t ts_sz = ucmp ? ucmp->timestamp_size() : 0; |
4707 | 222k | const bool full_history_ts_low_set = !full_history_ts_low.empty(); |
4708 | | // Max seqno a bottommost compaction could zero given the preserve window. |
4709 | | // CompactionJob computes preserve_seqno_after_ the same way but then further |
4710 | | // caps it with min(., earliest_snapshot_). We intentionally omit that cap |
4711 | | // here: the snapshot-visibility gate above (largest_seqno < |
4712 | | // oldest_snapshot_seqnum_) already guarantees a marked file's largest seqno |
4713 | | // is below the earliest snapshot, so the cap can never make compaction unable |
4714 | | // to zero a file we marked. Omitting it can at most leave unmarked a file a |
4715 | | // compaction could still zero -- a missed optimization, not a loop. |
4716 | 222k | const SequenceNumber preserve_seqno_after = |
4717 | 222k | std::max(preserve_time_min_seqno_, SequenceNumber{1}) - 1; |
4718 | | |
4719 | 222k | for (auto& level_and_file : bottommost_files_) { |
4720 | 112k | if (!level_and_file.second->being_compacted && |
4721 | 106k | level_and_file.second->fd.largest_seqno != 0) { |
4722 | | // largest_seqno might be nonzero due to containing the final key in an |
4723 | | // earlier compaction, whose seqnum we didn't zero out. |
4724 | 98.8k | if (level_and_file.second->fd.largest_seqno < oldest_snapshot_seqnum_) { |
4725 | | // Compute the file's timestamp collapsibility (only meaningful under |
4726 | | // UDT with full_history_ts_low set). A file whose max timestamp is |
4727 | | // below full_history_ts_low can have its timestamp history collapsed |
4728 | | // and its seqno zeroed. An unknown (empty) max timestamp means the file |
4729 | | // predates timestamp metadata (e.g. written by an older version); mark |
4730 | | // it once so a bounded compaction backfills FileMetaData::max_timestamp |
4731 | | // and reclaims obsolete versions/tombstones. When full_history_ts_low |
4732 | | // is unset, the guard below (full_history_ts_low_set == false) keeps |
4733 | | // such a file unmarked, since compaction could not collapse it and the |
4734 | | // rewrite would loop. |
4735 | 1.80k | bool ts_below_full_history_ts_low = false; |
4736 | 1.80k | if (ts_sz > 0 && full_history_ts_low_set) { |
4737 | 0 | const std::string& max_ts = level_and_file.second->max_timestamp; |
4738 | 0 | ts_below_full_history_ts_low = |
4739 | 0 | max_ts.empty() || |
4740 | 0 | ucmp->CompareTimestamp(Slice(max_ts), full_history_ts_low) < 0; |
4741 | 0 | } |
4742 | 1.80k | if (!BottommostSeqnoCanBeZeroed( |
4743 | 1.80k | level_and_file.second->fd.largest_seqno, preserve_seqno_after, |
4744 | 1.80k | ts_sz, full_history_ts_low_set, ts_below_full_history_ts_low)) { |
4745 | 0 | continue; |
4746 | 0 | } |
4747 | | |
4748 | 1.80k | if (!needs_delay) { |
4749 | 1.80k | bottommost_files_marked_for_compaction_.push_back(level_and_file); |
4750 | 1.80k | } else if (ownership_start_time_ub > 0) { |
4751 | 0 | int64_t ownership_start_time = static_cast<int64_t>( |
4752 | 0 | level_and_file.second->TryGetDbOwnershipStartTime()); |
4753 | 0 | if (ownership_start_time == kUnknownDbOwnershipStartTime || |
4754 | 0 | ownership_start_time <= ownership_start_time_ub) { |
4755 | 0 | bottommost_files_marked_for_compaction_.push_back(level_and_file); |
4756 | 0 | } else { |
4757 | | // Just ignore this file for both |
4758 | | // bottommost_files_marked_for_compaction_ and |
4759 | | // bottommost_files_mark_threshold_. The next time |
4760 | | // this method is called, it will try this file again. The method |
4761 | | // is called after a new Version creation (compaction, flush, etc.), |
4762 | | // after a compaction is picked, and after a snapshot newer than |
4763 | | // bottommost_files_mark_threshold_ is released. |
4764 | 0 | } |
4765 | 0 | } else { |
4766 | | // ownership_start_time_ub <= 0, all files are too new to be marked |
4767 | | // for compaction. |
4768 | 0 | } |
4769 | 97.0k | } else { |
4770 | 97.0k | bottommost_files_mark_threshold_ = |
4771 | 97.0k | std::min(bottommost_files_mark_threshold_, |
4772 | 97.0k | level_and_file.second->fd.largest_seqno); |
4773 | 97.0k | } |
4774 | 98.8k | } |
4775 | 112k | } |
4776 | 222k | } |
4777 | | |
4778 | 551k | void Version::Ref() { ++refs_; } |
4779 | | |
4780 | 551k | bool Version::Unref() { |
4781 | 551k | assert(refs_ >= 1); |
4782 | 551k | --refs_; |
4783 | 551k | if (refs_ == 0) { |
4784 | 303k | delete this; |
4785 | 303k | return true; |
4786 | 303k | } |
4787 | 248k | return false; |
4788 | 551k | } |
4789 | | |
4790 | | bool VersionStorageInfo::OverlapInLevel(int level, |
4791 | | const Slice* smallest_user_key, |
4792 | 0 | const Slice* largest_user_key) { |
4793 | 0 | if (level >= num_non_empty_levels_) { |
4794 | | // empty level, no overlap |
4795 | 0 | return false; |
4796 | 0 | } |
4797 | 0 | return SomeFileOverlapsRange(*internal_comparator_, (level > 0), |
4798 | 0 | level_files_brief_[level], smallest_user_key, |
4799 | 0 | largest_user_key); |
4800 | 0 | } |
4801 | | |
4802 | | // Store in "*inputs" all files in "level" that overlap [begin,end] |
4803 | | // If hint_index is specified, then it points to a file in the |
4804 | | // overlapping range. |
4805 | | // The file_index returns a pointer to any file in an overlapping range. |
4806 | | void VersionStorageInfo::GetOverlappingInputs( |
4807 | | int level, const InternalKey* begin, const InternalKey* end, |
4808 | | std::vector<FileMetaData*>* inputs, int hint_index, int* file_index, |
4809 | | bool expand_range, const FileMetaData* starting_l0_file, |
4810 | 21.8k | InternalKey** next_smallest) const { |
4811 | 21.8k | if (level >= num_non_empty_levels_) { |
4812 | | // this level is empty, no overlapping inputs |
4813 | 5.21k | return; |
4814 | 5.21k | } |
4815 | | |
4816 | 16.6k | inputs->clear(); |
4817 | 16.6k | if (file_index) { |
4818 | 5.94k | *file_index = -1; |
4819 | 5.94k | } |
4820 | 16.6k | const Comparator* user_cmp = user_comparator_; |
4821 | 16.6k | if (level > 0) { |
4822 | 10.1k | GetOverlappingInputsRangeBinarySearch(level, begin, end, inputs, hint_index, |
4823 | 10.1k | file_index, false, next_smallest); |
4824 | 10.1k | return; |
4825 | 10.1k | } |
4826 | | |
4827 | 6.50k | if (next_smallest) { |
4828 | | // next_smallest key only makes sense for non-level 0, where files are |
4829 | | // non-overlapping |
4830 | 0 | *next_smallest = nullptr; |
4831 | 0 | } |
4832 | | |
4833 | 6.50k | Slice user_begin; |
4834 | 6.50k | Slice user_end; |
4835 | 6.50k | if (begin != nullptr) { |
4836 | 6.50k | user_begin = begin->user_key(); |
4837 | 6.50k | } |
4838 | 6.50k | if (end != nullptr) { |
4839 | 6.50k | user_end = end->user_key(); |
4840 | 6.50k | } |
4841 | | |
4842 | | // index stores the file index need to check. |
4843 | 6.50k | std::list<size_t> index; |
4844 | 6.50k | size_t start_index = 0; |
4845 | 6.50k | if (starting_l0_file != nullptr) { |
4846 | 0 | uint64_t starting_file_number = starting_l0_file->fd.GetNumber(); |
4847 | 0 | for (size_t i = 0; i < level_files_brief_[level].num_files; i++) { |
4848 | 0 | if (level_files_brief_[level].files[i].fd.GetNumber() == |
4849 | 0 | starting_file_number) { |
4850 | 0 | start_index = i; |
4851 | 0 | break; |
4852 | 0 | } |
4853 | 0 | } |
4854 | 0 | assert(start_index < level_files_brief_[level].num_files); |
4855 | 0 | } |
4856 | 27.5k | for (size_t i = start_index; i < level_files_brief_[level].num_files; i++) { |
4857 | 21.0k | index.emplace_back(i); |
4858 | 21.0k | } |
4859 | | |
4860 | 12.9k | while (!index.empty()) { |
4861 | 8.68k | bool found_overlapping_file = false; |
4862 | 8.68k | auto iter = index.begin(); |
4863 | 33.6k | while (iter != index.end()) { |
4864 | 24.9k | FdWithKeyRange* f = &(level_files_brief_[level].files[*iter]); |
4865 | 24.9k | const Slice file_start = ExtractUserKey(f->smallest_key); |
4866 | 24.9k | const Slice file_limit = ExtractUserKey(f->largest_key); |
4867 | 24.9k | if (begin != nullptr && |
4868 | 24.9k | user_cmp->CompareWithoutTimestamp(file_limit, user_begin) < 0) { |
4869 | | // "f" is completely before specified range; skip it |
4870 | 12 | iter++; |
4871 | 24.9k | } else if (end != nullptr && |
4872 | 24.9k | user_cmp->CompareWithoutTimestamp(file_start, user_end) > 0) { |
4873 | | // "f" is completely after specified range; skip it |
4874 | 7.90k | iter++; |
4875 | 17.0k | } else { |
4876 | | // if overlap |
4877 | 17.0k | inputs->emplace_back(files_[level][*iter]); |
4878 | 17.0k | found_overlapping_file = true; |
4879 | | // record the first file index. |
4880 | 17.0k | if (file_index && *file_index == -1) { |
4881 | 0 | *file_index = static_cast<int>(*iter); |
4882 | 0 | } |
4883 | | // the related file is overlap, erase to avoid checking again. |
4884 | 17.0k | iter = index.erase(iter); |
4885 | 17.0k | if (expand_range) { |
4886 | 17.0k | if (begin != nullptr && |
4887 | 17.0k | user_cmp->CompareWithoutTimestamp(file_start, user_begin) < 0) { |
4888 | 6 | user_begin = file_start; |
4889 | 6 | } |
4890 | 17.0k | if (end != nullptr && |
4891 | 17.0k | user_cmp->CompareWithoutTimestamp(file_limit, user_end) > 0) { |
4892 | 758 | user_end = file_limit; |
4893 | 758 | } |
4894 | 17.0k | } |
4895 | 17.0k | } |
4896 | 24.9k | } |
4897 | | // if all the files left are not overlap, break |
4898 | 8.68k | if (!found_overlapping_file) { |
4899 | 2.26k | break; |
4900 | 2.26k | } |
4901 | 8.68k | } |
4902 | 6.50k | } |
4903 | | |
4904 | | // Store in "*inputs" files in "level" that within range [begin,end] |
4905 | | // Guarantee a "clean cut" boundary between the files in inputs |
4906 | | // and the surrounding files and the maxinum number of files. |
4907 | | // This will ensure that no parts of a key are lost during compaction. |
4908 | | // If hint_index is specified, then it points to a file in the range. |
4909 | | // The file_index returns a pointer to any file in an overlapping range. |
4910 | | void VersionStorageInfo::GetCleanInputsWithinInterval( |
4911 | | int level, const InternalKey* begin, const InternalKey* end, |
4912 | 1.61k | std::vector<FileMetaData*>* inputs, int hint_index, int* file_index) const { |
4913 | 1.61k | inputs->clear(); |
4914 | 1.61k | if (file_index) { |
4915 | 0 | *file_index = -1; |
4916 | 0 | } |
4917 | 1.61k | if (level >= num_non_empty_levels_ || level == 0 || |
4918 | 1.61k | level_files_brief_[level].num_files == 0) { |
4919 | | // this level is empty, no inputs within range |
4920 | | // also don't support clean input interval within L0 |
4921 | 1.61k | return; |
4922 | 1.61k | } |
4923 | | |
4924 | 0 | GetOverlappingInputsRangeBinarySearch(level, begin, end, inputs, hint_index, |
4925 | 0 | file_index, true /* within_interval */); |
4926 | 0 | } |
4927 | | |
4928 | | // Store in "*inputs" all files in "level" that overlap [begin,end] |
4929 | | // Employ binary search to find at least one file that overlaps the |
4930 | | // specified range. From that file, iterate backwards and |
4931 | | // forwards to find all overlapping files. |
4932 | | // if within_range is set, then only store the maximum clean inputs |
4933 | | // within range [begin, end]. "clean" means there is a boundary |
4934 | | // between the files in "*inputs" and the surrounding files |
4935 | | void VersionStorageInfo::GetOverlappingInputsRangeBinarySearch( |
4936 | | int level, const InternalKey* begin, const InternalKey* end, |
4937 | | std::vector<FileMetaData*>* inputs, int hint_index, int* file_index, |
4938 | 10.1k | bool within_interval, InternalKey** next_smallest) const { |
4939 | 10.1k | assert(level > 0); |
4940 | | |
4941 | 10.1k | auto user_cmp = user_comparator_; |
4942 | 10.1k | const FdWithKeyRange* files = level_files_brief_[level].files; |
4943 | 10.1k | const int num_files = static_cast<int>(level_files_brief_[level].num_files); |
4944 | | |
4945 | | // begin to use binary search to find lower bound |
4946 | | // and upper bound. |
4947 | 10.1k | int start_index = 0; |
4948 | 10.1k | int end_index = num_files; |
4949 | | |
4950 | 10.1k | if (begin != nullptr) { |
4951 | | // if within_interval is true, with file_key would find |
4952 | | // not overlapping ranges in std::lower_bound. |
4953 | 10.1k | auto cmp = [&user_cmp, &within_interval](const FdWithKeyRange& f, |
4954 | 18.2k | const InternalKey* k) { |
4955 | 18.2k | auto& file_key = within_interval ? f.file_metadata->smallest |
4956 | 18.2k | : f.file_metadata->largest; |
4957 | 18.2k | return sstableKeyCompare(user_cmp, file_key, *k) < 0; |
4958 | 18.2k | }; |
4959 | | |
4960 | 10.1k | start_index = static_cast<int>( |
4961 | 10.1k | std::lower_bound(files, |
4962 | 10.1k | files + (hint_index == -1 ? num_files : hint_index), |
4963 | 10.1k | begin, cmp) - |
4964 | 10.1k | files); |
4965 | | |
4966 | 10.1k | if (start_index > 0 && within_interval) { |
4967 | 0 | bool is_overlapping = true; |
4968 | 0 | while (is_overlapping && start_index < num_files) { |
4969 | 0 | auto& pre_limit = files[start_index - 1].file_metadata->largest; |
4970 | 0 | auto& cur_start = files[start_index].file_metadata->smallest; |
4971 | 0 | is_overlapping = sstableKeyCompare(user_cmp, pre_limit, cur_start) == 0; |
4972 | 0 | start_index += is_overlapping; |
4973 | 0 | } |
4974 | 0 | } |
4975 | 10.1k | } |
4976 | | |
4977 | 10.1k | if (end != nullptr) { |
4978 | | // if within_interval is true, with file_key would find |
4979 | | // not overlapping ranges in std::upper_bound. |
4980 | 10.1k | auto cmp = [&user_cmp, &within_interval](const InternalKey* k, |
4981 | 15.9k | const FdWithKeyRange& f) { |
4982 | 15.9k | auto& file_key = within_interval ? f.file_metadata->largest |
4983 | 15.9k | : f.file_metadata->smallest; |
4984 | 15.9k | return sstableKeyCompare(user_cmp, *k, file_key) < 0; |
4985 | 15.9k | }; |
4986 | | |
4987 | 10.1k | end_index = static_cast<int>( |
4988 | 10.1k | std::upper_bound(files + start_index, files + num_files, end, cmp) - |
4989 | 10.1k | files); |
4990 | | |
4991 | 10.1k | if (end_index < num_files && within_interval) { |
4992 | 0 | bool is_overlapping = true; |
4993 | 0 | while (is_overlapping && end_index > start_index) { |
4994 | 0 | auto& next_start = files[end_index].file_metadata->smallest; |
4995 | 0 | auto& cur_limit = files[end_index - 1].file_metadata->largest; |
4996 | 0 | is_overlapping = |
4997 | 0 | sstableKeyCompare(user_cmp, cur_limit, next_start) == 0; |
4998 | 0 | end_index -= is_overlapping; |
4999 | 0 | } |
5000 | 0 | } |
5001 | 10.1k | } |
5002 | | |
5003 | 10.1k | assert(start_index <= end_index); |
5004 | | |
5005 | | // If there were no overlapping files, return immediately. |
5006 | 10.1k | if (start_index == end_index) { |
5007 | 2.27k | if (next_smallest) { |
5008 | 0 | *next_smallest = nullptr; |
5009 | 0 | } |
5010 | 2.27k | return; |
5011 | 2.27k | } |
5012 | | |
5013 | 10.1k | assert(start_index < end_index); |
5014 | | |
5015 | | // returns the index where an overlap is found |
5016 | 7.87k | if (file_index) { |
5017 | 5.60k | *file_index = start_index; |
5018 | 5.60k | } |
5019 | | |
5020 | | // insert overlapping files into vector |
5021 | 23.5k | for (int i = start_index; i < end_index; i++) { |
5022 | 15.7k | inputs->push_back(files_[level][i]); |
5023 | 15.7k | } |
5024 | | |
5025 | 7.87k | if (next_smallest != nullptr) { |
5026 | | // Provide the next key outside the range covered by inputs |
5027 | 0 | if (end_index < static_cast<int>(files_[level].size())) { |
5028 | 0 | **next_smallest = files_[level][end_index]->smallest; |
5029 | 0 | } else { |
5030 | 0 | *next_smallest = nullptr; |
5031 | 0 | } |
5032 | 0 | } |
5033 | 7.87k | } |
5034 | | |
5035 | 378 | uint64_t VersionStorageInfo::NumLevelBytes(int level) const { |
5036 | 378 | assert(level >= 0); |
5037 | 378 | assert(level < num_levels()); |
5038 | 378 | return TotalFileSize(files_[level]); |
5039 | 378 | } |
5040 | | |
5041 | | const char* VersionStorageInfo::LevelSummary( |
5042 | 7.79k | LevelSummaryStorage* scratch) const { |
5043 | 7.79k | int len = 0; |
5044 | 7.79k | if (compaction_style_ == kCompactionStyleLevel && num_levels() > 1) { |
5045 | 7.79k | assert(base_level_ < static_cast<int>(level_max_bytes_.size())); |
5046 | 7.79k | if (level_multiplier_ != 0.0) { |
5047 | 5.86k | len = snprintf( |
5048 | 5.86k | scratch->buffer, sizeof(scratch->buffer), |
5049 | 5.86k | "base level %d level multiplier %.2f max bytes base %" PRIu64 " ", |
5050 | 5.86k | base_level_, level_multiplier_, level_max_bytes_[base_level_]); |
5051 | 5.86k | } |
5052 | 7.79k | } |
5053 | 7.79k | len += |
5054 | 7.79k | snprintf(scratch->buffer + len, sizeof(scratch->buffer) - len, "files["); |
5055 | 62.3k | for (int i = 0; i < num_levels(); i++) { |
5056 | 54.5k | int sz = sizeof(scratch->buffer) - len; |
5057 | 54.5k | int ret = snprintf(scratch->buffer + len, sz, "%d ", int(files_[i].size())); |
5058 | 54.5k | if (ret < 0 || ret >= sz) { |
5059 | 0 | break; |
5060 | 0 | } |
5061 | 54.5k | len += ret; |
5062 | 54.5k | } |
5063 | 7.79k | if (len > 0) { |
5064 | | // overwrite the last space |
5065 | 7.79k | --len; |
5066 | 7.79k | } |
5067 | 7.79k | len += |
5068 | 7.79k | snprintf(scratch->buffer + len, sizeof(scratch->buffer) - len, |
5069 | 7.79k | "] max score %.2f, estimated pending compaction bytes %" PRIu64, |
5070 | 7.79k | compaction_score_[0], estimated_compaction_needed_bytes_); |
5071 | | |
5072 | 7.79k | if (!files_marked_for_compaction_.empty()) { |
5073 | 0 | snprintf(scratch->buffer + len, sizeof(scratch->buffer) - len, |
5074 | 0 | " (%" ROCKSDB_PRIszt " files need compaction)", |
5075 | 0 | files_marked_for_compaction_.size()); |
5076 | 0 | } |
5077 | | |
5078 | 7.79k | return scratch->buffer; |
5079 | 7.79k | } |
5080 | | |
5081 | | const char* VersionStorageInfo::LevelFileSummary(FileSummaryStorage* scratch, |
5082 | 0 | int level) const { |
5083 | 0 | int len = snprintf(scratch->buffer, sizeof(scratch->buffer), "files_size["); |
5084 | 0 | for (const auto& f : files_[level]) { |
5085 | 0 | int sz = sizeof(scratch->buffer) - len; |
5086 | 0 | char sztxt[16]; |
5087 | 0 | AppendHumanBytes(f->fd.GetFileSize(), sztxt, sizeof(sztxt)); |
5088 | 0 | int ret = snprintf(scratch->buffer + len, sz, |
5089 | 0 | "#%" PRIu64 "(seq=%" PRIu64 ",sz=%s,%d) ", |
5090 | 0 | f->fd.GetNumber(), f->fd.smallest_seqno, sztxt, |
5091 | 0 | static_cast<int>(f->being_compacted)); |
5092 | 0 | if (ret < 0 || ret >= sz) { |
5093 | 0 | break; |
5094 | 0 | } |
5095 | 0 | len += ret; |
5096 | 0 | } |
5097 | | // overwrite the last space (only if files_[level].size() is non-zero) |
5098 | 0 | if (files_[level].size() && len > 0) { |
5099 | 0 | --len; |
5100 | 0 | } |
5101 | 0 | snprintf(scratch->buffer + len, sizeof(scratch->buffer) - len, "]"); |
5102 | 0 | return scratch->buffer; |
5103 | 0 | } |
5104 | | |
5105 | 121k | bool VersionStorageInfo::HasMissingEpochNumber() const { |
5106 | 968k | for (int level = 0; level < num_levels_; ++level) { |
5107 | 847k | for (const FileMetaData* f : files_[level]) { |
5108 | 68.7k | if (f->epoch_number == kUnknownEpochNumber) { |
5109 | 0 | return true; |
5110 | 0 | } |
5111 | 68.7k | } |
5112 | 847k | } |
5113 | 121k | return false; |
5114 | 121k | } |
5115 | | |
5116 | 60.5k | uint64_t VersionStorageInfo::GetMaxEpochNumberOfFiles() const { |
5117 | 60.5k | uint64_t max_epoch_number = kUnknownEpochNumber; |
5118 | 484k | for (int level = 0; level < num_levels_; ++level) { |
5119 | 423k | for (const FileMetaData* f : files_[level]) { |
5120 | 68.7k | max_epoch_number = std::max(max_epoch_number, f->epoch_number); |
5121 | 68.7k | } |
5122 | 423k | } |
5123 | 60.5k | return max_epoch_number; |
5124 | 60.5k | } |
5125 | | |
5126 | | void VersionStorageInfo::RecoverEpochNumbers(ColumnFamilyData* cfd, |
5127 | 60.5k | bool restart_epoch, bool force) { |
5128 | 60.5k | if (restart_epoch) { |
5129 | 60.5k | cfd->ResetNextEpochNumber(); |
5130 | | |
5131 | 60.5k | bool reserve_epoch_num_for_file_ingested_behind = cfd->AllowIngestBehind(); |
5132 | 60.5k | if (reserve_epoch_num_for_file_ingested_behind) { |
5133 | 0 | uint64_t reserved_epoch_number = cfd->NewEpochNumber(); |
5134 | 0 | assert(reserved_epoch_number == |
5135 | 0 | kReservedEpochNumberForFileIngestedBehind); |
5136 | 0 | ROCKS_LOG_INFO(cfd->ioptions().info_log.get(), |
5137 | 0 | "[%s]CF has reserved epoch number %" PRIu64 |
5138 | 0 | " for files ingested " |
5139 | 0 | "behind since `Options::allow_ingest_behind` or " |
5140 | 0 | "`Options::cf_allow_ingest_behind` is true", |
5141 | 0 | cfd->GetName().c_str(), reserved_epoch_number); |
5142 | 0 | } |
5143 | 60.5k | } |
5144 | | |
5145 | 60.5k | bool missing_epoch_number = HasMissingEpochNumber(); |
5146 | 60.5k | if (missing_epoch_number || force) { |
5147 | 0 | for (int level = num_levels_ - 1; level >= 1; --level) { |
5148 | 0 | auto& files_at_level = files_[level]; |
5149 | 0 | if (files_at_level.empty()) { |
5150 | 0 | continue; |
5151 | 0 | } |
5152 | 0 | uint64_t next_epoch_number = cfd->NewEpochNumber(); |
5153 | 0 | for (FileMetaData* f : files_at_level) { |
5154 | 0 | f->epoch_number = next_epoch_number; |
5155 | 0 | } |
5156 | 0 | } |
5157 | 0 | for (auto file_meta_iter = files_[0].rbegin(); |
5158 | 0 | file_meta_iter != files_[0].rend(); file_meta_iter++) { |
5159 | 0 | FileMetaData* f = *file_meta_iter; |
5160 | 0 | f->epoch_number = cfd->NewEpochNumber(); |
5161 | 0 | } |
5162 | 0 | if (missing_epoch_number) { |
5163 | 0 | assert(epoch_number_requirement_ == |
5164 | 0 | EpochNumberRequirement::kMightMissing); |
5165 | 0 | ROCKS_LOG_WARN(cfd->ioptions().info_log.get(), |
5166 | 0 | "[%s]CF's epoch numbers are inferred based on seqno", |
5167 | 0 | cfd->GetName().c_str()); |
5168 | 0 | epoch_number_requirement_ = EpochNumberRequirement::kMustPresent; |
5169 | 0 | } |
5170 | 60.5k | } else { |
5171 | 60.5k | assert(epoch_number_requirement_ == EpochNumberRequirement::kMustPresent); |
5172 | 60.5k | cfd->SetNextEpochNumber( |
5173 | 60.5k | std::max(GetMaxEpochNumberOfFiles() + 1, cfd->GetNextEpochNumber())); |
5174 | 60.5k | } |
5175 | 60.5k | } |
5176 | | |
5177 | 0 | uint64_t VersionStorageInfo::MaxNextLevelOverlappingBytes() { |
5178 | 0 | uint64_t result = 0; |
5179 | 0 | std::vector<FileMetaData*> overlaps; |
5180 | 0 | for (int level = 1; level < num_levels() - 1; level++) { |
5181 | 0 | for (const auto& f : files_[level]) { |
5182 | 0 | GetOverlappingInputs(level + 1, &f->smallest, &f->largest, &overlaps); |
5183 | 0 | const uint64_t sum = TotalFileSize(overlaps); |
5184 | 0 | if (sum > result) { |
5185 | 0 | result = sum; |
5186 | 0 | } |
5187 | 0 | } |
5188 | 0 | } |
5189 | 0 | return result; |
5190 | 0 | } |
5191 | | |
5192 | 3.32M | uint64_t VersionStorageInfo::MaxBytesForLevel(int level) const { |
5193 | | // Note: the result for level zero is not really used since we set |
5194 | | // the level-0 compaction threshold based on number of files. |
5195 | 3.32M | assert(level >= 0); |
5196 | 3.32M | assert(level < static_cast<int>(level_max_bytes_.size())); |
5197 | 3.32M | return level_max_bytes_[level]; |
5198 | 3.32M | } |
5199 | | |
5200 | | void VersionStorageInfo::CalculateBaseBytes(const ImmutableOptions& ioptions, |
5201 | 215k | const MutableCFOptions& options) { |
5202 | | // Special logic to set number of sorted runs. |
5203 | | // It is to match the previous behavior when all files are in L0. |
5204 | 215k | int num_l0_count = static_cast<int>(files_[0].size()); |
5205 | 215k | if (compaction_style_ == kCompactionStyleUniversal) { |
5206 | | // For universal compaction, we use level0 score to indicate |
5207 | | // compaction score for the whole DB. Adding other levels as if |
5208 | | // they are L0 files. |
5209 | 0 | for (int i = 1; i < num_levels(); i++) { |
5210 | 0 | if (!files_[i].empty()) { |
5211 | 0 | num_l0_count++; |
5212 | 0 | } |
5213 | 0 | } |
5214 | 0 | } |
5215 | 215k | set_l0_delay_trigger_count(num_l0_count); |
5216 | | |
5217 | 215k | level_max_bytes_.resize(ioptions.num_levels); |
5218 | 215k | if (!ioptions.level_compaction_dynamic_level_bytes) { |
5219 | 0 | base_level_ = (ioptions.compaction_style == kCompactionStyleLevel) ? 1 : -1; |
5220 | | |
5221 | | // Calculate for static bytes base case |
5222 | 0 | for (int i = 0; i < ioptions.num_levels; ++i) { |
5223 | 0 | if (i == 0 && ioptions.compaction_style == kCompactionStyleUniversal) { |
5224 | 0 | level_max_bytes_[i] = options.max_bytes_for_level_base; |
5225 | 0 | } else if (i > 1) { |
5226 | 0 | level_max_bytes_[i] = MultiplyCheckOverflow( |
5227 | 0 | MultiplyCheckOverflow(level_max_bytes_[i - 1], |
5228 | 0 | options.max_bytes_for_level_multiplier), |
5229 | 0 | options.MaxBytesMultiplerAdditional(i - 1)); |
5230 | 0 | } else { |
5231 | 0 | level_max_bytes_[i] = options.max_bytes_for_level_base; |
5232 | 0 | } |
5233 | 0 | } |
5234 | 215k | } else { |
5235 | 215k | assert(ioptions.compaction_style == kCompactionStyleLevel); |
5236 | 215k | uint64_t max_level_size = 0; |
5237 | | |
5238 | 215k | int first_non_empty_level = -1; |
5239 | | // Find size of non-L0 level of most data. |
5240 | | // Cannot use the size of the last level because it can be empty or less |
5241 | | // than previous levels after compaction. |
5242 | 1.51M | for (int i = 1; i < num_levels_; i++) { |
5243 | 1.29M | uint64_t total_size = 0; |
5244 | 1.29M | for (const auto& f : files_[i]) { |
5245 | 63.9k | total_size += f->fd.GetFileSize(); |
5246 | 63.9k | } |
5247 | 1.29M | if (total_size > 0 && first_non_empty_level == -1) { |
5248 | 25.6k | first_non_empty_level = i; |
5249 | 25.6k | } |
5250 | 1.29M | if (total_size > max_level_size) { |
5251 | 25.6k | max_level_size = total_size; |
5252 | 25.6k | } |
5253 | 1.29M | } |
5254 | | |
5255 | | // Prefill every level's max bytes to disallow compaction from there. |
5256 | 1.72M | for (int i = 0; i < num_levels_; i++) { |
5257 | 1.51M | level_max_bytes_[i] = std::numeric_limits<uint64_t>::max(); |
5258 | 1.51M | } |
5259 | | |
5260 | 215k | lowest_unnecessary_level_ = -1; |
5261 | 215k | if (max_level_size == 0) { |
5262 | | // No data for L1 and up. L0 compacts to last level directly. |
5263 | | // No compaction from L1+ needs to be scheduled. |
5264 | 190k | base_level_ = num_levels_ - 1; |
5265 | 190k | } else { |
5266 | 25.6k | assert(first_non_empty_level >= 1); |
5267 | 25.6k | uint64_t base_bytes_max = options.max_bytes_for_level_base; |
5268 | 25.6k | uint64_t base_bytes_min = static_cast<uint64_t>( |
5269 | 25.6k | base_bytes_max / options.max_bytes_for_level_multiplier); |
5270 | | |
5271 | | // Try whether we can make last level's target size to be max_level_size |
5272 | 25.6k | uint64_t cur_level_size = max_level_size; |
5273 | 25.6k | for (int i = num_levels_ - 2; i >= first_non_empty_level; i--) { |
5274 | | // Round up after dividing |
5275 | 0 | cur_level_size = static_cast<uint64_t>( |
5276 | 0 | cur_level_size / options.max_bytes_for_level_multiplier); |
5277 | 0 | if (lowest_unnecessary_level_ == -1 && |
5278 | 0 | cur_level_size <= base_bytes_min && |
5279 | 0 | (options.preclude_last_level_data_seconds == 0 || |
5280 | 0 | i < num_levels_ - 2)) { |
5281 | | // When per_key_placement is enabled, the proximal level is |
5282 | | // necessary. |
5283 | 0 | lowest_unnecessary_level_ = i; |
5284 | 0 | } |
5285 | 0 | } |
5286 | | |
5287 | | // Calculate base level and its size. |
5288 | 25.6k | uint64_t base_level_size; |
5289 | 25.6k | if (cur_level_size <= base_bytes_min) { |
5290 | | // If per_key_placement is not enabled, |
5291 | | // either there is only one non-empty level after level 0, |
5292 | | // which can less than base_bytes_min AND necessary, |
5293 | | // or there is some unnecessary level. |
5294 | 25.6k | assert(first_non_empty_level == num_levels_ - 1 || |
5295 | 25.6k | options.preclude_last_level_data_seconds > 0 || |
5296 | 25.6k | lowest_unnecessary_level_ != -1); |
5297 | | // Case 1. If we make target size of last level to be max_level_size, |
5298 | | // target size of the first non-empty level would be smaller than |
5299 | | // base_bytes_min. We set it be base_bytes_min. |
5300 | 25.6k | base_level_size = base_bytes_min + 1U; |
5301 | 25.6k | base_level_ = first_non_empty_level; |
5302 | 25.6k | if (base_level_ < num_levels_ - 1) { |
5303 | 0 | ROCKS_LOG_INFO( |
5304 | 0 | ioptions.logger, |
5305 | 0 | "More existing levels in DB than needed: all non-zero " |
5306 | 0 | "levels <= level %d are unnecessary. " |
5307 | 0 | "max_bytes_for_level_multiplier may not be guaranteed.", |
5308 | 0 | lowest_unnecessary_level_); |
5309 | 0 | } |
5310 | 25.6k | } else { |
5311 | 0 | assert(lowest_unnecessary_level_ == -1); |
5312 | | // Find base level (where L0 data is compacted to). |
5313 | 0 | base_level_ = first_non_empty_level; |
5314 | 0 | while (base_level_ > 1 && cur_level_size > base_bytes_max) { |
5315 | 0 | --base_level_; |
5316 | 0 | cur_level_size = static_cast<uint64_t>( |
5317 | 0 | cur_level_size / options.max_bytes_for_level_multiplier); |
5318 | 0 | } |
5319 | 0 | if (cur_level_size > base_bytes_max) { |
5320 | | // Even L1 will be too large |
5321 | 0 | assert(base_level_ == 1); |
5322 | 0 | base_level_size = base_bytes_max; |
5323 | 0 | } else { |
5324 | 0 | base_level_size = std::max(static_cast<uint64_t>(1), cur_level_size); |
5325 | 0 | } |
5326 | 0 | } |
5327 | | |
5328 | 25.6k | level_multiplier_ = options.max_bytes_for_level_multiplier; |
5329 | 25.6k | assert(base_level_size > 0); |
5330 | | |
5331 | 25.6k | uint64_t level_size = base_level_size; |
5332 | 51.2k | for (int i = base_level_; i < num_levels_; i++) { |
5333 | 25.6k | if (i > base_level_) { |
5334 | 0 | level_size = MultiplyCheckOverflow(level_size, level_multiplier_); |
5335 | 0 | } |
5336 | | // Don't set any level below base_bytes_max. Otherwise, the LSM can |
5337 | | // assume an hourglass shape where L1+ sizes are smaller than L0. This |
5338 | | // causes compaction scoring, which depends on level sizes, to favor L1+ |
5339 | | // at the expense of L0, which may fill up and stall. |
5340 | 25.6k | level_max_bytes_[i] = std::max(level_size, base_bytes_max); |
5341 | 25.6k | } |
5342 | 25.6k | } |
5343 | 215k | } |
5344 | 215k | } |
5345 | | |
5346 | 0 | uint64_t VersionStorageInfo::EstimateLiveDataSize() const { |
5347 | | // Estimate the live data size by adding up the size of a maximal set of |
5348 | | // sst files with no range overlap in same or higher level. The less |
5349 | | // compacted, the more optimistic (smaller) this estimate is. Also, |
5350 | | // for multiple sorted runs within a level, file order will matter. |
5351 | 0 | uint64_t size = 0; |
5352 | |
|
5353 | 0 | auto ikey_lt = [this](InternalKey* x, InternalKey* y) { |
5354 | 0 | return internal_comparator_->Compare(*x, *y) < 0; |
5355 | 0 | }; |
5356 | | // (Ordered) map of largest keys in files being included in size estimate |
5357 | 0 | std::map<InternalKey*, FileMetaData*, decltype(ikey_lt)> ranges(ikey_lt); |
5358 | |
|
5359 | 0 | for (int l = num_levels_ - 1; l >= 0; l--) { |
5360 | 0 | bool found_end = false; |
5361 | 0 | for (auto file : files_[l]) { |
5362 | | // Find the first file already included with largest key is larger than |
5363 | | // the smallest key of `file`. If that file does not overlap with the |
5364 | | // current file, none of the files in the map does. If there is |
5365 | | // no potential overlap, we can safely insert the rest of this level |
5366 | | // (if the level is not 0) into the map without checking again because |
5367 | | // the elements in the level are sorted and non-overlapping. |
5368 | 0 | auto lb = (found_end && l != 0) ? ranges.end() |
5369 | 0 | : ranges.lower_bound(&file->smallest); |
5370 | 0 | found_end = (lb == ranges.end()); |
5371 | 0 | if (found_end || internal_comparator_->Compare( |
5372 | 0 | file->largest, (*lb).second->smallest) < 0) { |
5373 | 0 | ranges.emplace_hint(lb, &file->largest, file); |
5374 | 0 | size += file->fd.file_size; |
5375 | 0 | } |
5376 | 0 | } |
5377 | 0 | } |
5378 | | |
5379 | | // For BlobDB, the result also includes the exact value of live bytes in the |
5380 | | // blob files of the version. |
5381 | 0 | for (const auto& meta : blob_files_) { |
5382 | 0 | assert(meta); |
5383 | |
|
5384 | 0 | size += meta->GetTotalBlobBytes(); |
5385 | 0 | size -= meta->GetGarbageBlobBytes(); |
5386 | 0 | } |
5387 | |
|
5388 | 0 | return size; |
5389 | 0 | } |
5390 | | |
5391 | | bool VersionStorageInfo::RangeMightExistAfterSortedRun( |
5392 | | const Slice& smallest_user_key, const Slice& largest_user_key, |
5393 | 179k | int last_level, int last_l0_idx) { |
5394 | 179k | assert((last_l0_idx != -1) == (last_level == 0)); |
5395 | | // TODO(ajkr): this preserves earlier behavior where we considered an L0 file |
5396 | | // bottommost only if it's the oldest L0 file and there are no files on older |
5397 | | // levels. It'd be better to consider it bottommost if there's no overlap in |
5398 | | // older levels/files. |
5399 | 179k | if (last_level == 0 && |
5400 | 109k | last_l0_idx != static_cast<int>(LevelFiles(0).size() - 1)) { |
5401 | 52.9k | return true; |
5402 | 52.9k | } |
5403 | | |
5404 | | // Checks whether there are files living beyond the `last_level`. If lower |
5405 | | // levels have files, it checks for overlap between [`smallest_key`, |
5406 | | // `largest_key`] and those files. Bottomlevel optimizations can be made if |
5407 | | // there are no files in lower levels or if there is no overlap with the files |
5408 | | // in the lower levels. |
5409 | 448k | for (int level = last_level + 1; level < num_levels(); level++) { |
5410 | | // The range is not in the bottommost level if there are files in lower |
5411 | | // levels when the `last_level` is 0 or if there are files in lower levels |
5412 | | // which overlap with [`smallest_key`, `largest_key`]. |
5413 | 341k | if (files_[level].size() > 0 && |
5414 | 19.7k | (last_level == 0 || |
5415 | 19.7k | OverlapInLevel(level, &smallest_user_key, &largest_user_key))) { |
5416 | 19.7k | return true; |
5417 | 19.7k | } |
5418 | 341k | } |
5419 | 106k | return false; |
5420 | 126k | } |
5421 | | |
5422 | | Env::WriteLifeTimeHint VersionStorageInfo::CalculateSSTWriteHint( |
5423 | 20.7k | int level, CompactionStyleSet compaction_style_set) const { |
5424 | 20.7k | if (!compaction_style_set.Contains(compaction_style_)) { |
5425 | 0 | return Env::WLTH_NOT_SET; |
5426 | 0 | } |
5427 | | |
5428 | 20.7k | switch (compaction_style_) { |
5429 | 20.7k | case kCompactionStyleLevel: |
5430 | 20.7k | if (level == 0) { |
5431 | 18.3k | return Env::WLTH_MEDIUM; |
5432 | 18.3k | } |
5433 | | |
5434 | | // L1: medium, L2: long, ... |
5435 | 2.40k | if (level - base_level_ >= 2) { |
5436 | 0 | return Env::WLTH_EXTREME; |
5437 | 2.40k | } else if (level < base_level_) { |
5438 | | // There is no restriction which prevents level passed in to be smaller |
5439 | | // than base_level. |
5440 | 0 | return Env::WLTH_MEDIUM; |
5441 | 0 | } |
5442 | 2.40k | return static_cast<Env::WriteLifeTimeHint>( |
5443 | 2.40k | level - base_level_ + static_cast<int>(Env::WLTH_MEDIUM)); |
5444 | 0 | case kCompactionStyleUniversal: |
5445 | 0 | if (level == 0) { |
5446 | 0 | return Env::WLTH_SHORT; |
5447 | 0 | } |
5448 | 0 | if (level == 1) { |
5449 | 0 | return Env::WLTH_MEDIUM; |
5450 | 0 | } |
5451 | 0 | return Env::WLTH_LONG; |
5452 | 0 | case kCompactionStyleFIFO: |
5453 | 0 | case kCompactionStyleNone: |
5454 | 0 | default: |
5455 | 0 | return Env::WLTH_NOT_SET; |
5456 | 20.7k | } |
5457 | 20.7k | } |
5458 | | |
5459 | | void Version::AddLiveFiles(std::vector<uint64_t>* live_table_files, |
5460 | 121k | std::vector<uint64_t>* live_blob_files) const { |
5461 | 121k | assert(live_table_files); |
5462 | 121k | assert(live_blob_files); |
5463 | | |
5464 | 968k | for (int level = 0; level < storage_info_.num_levels(); ++level) { |
5465 | 847k | const auto& level_files = storage_info_.LevelFiles(level); |
5466 | 847k | for (const auto& meta : level_files) { |
5467 | 166k | assert(meta); |
5468 | | |
5469 | 166k | live_table_files->emplace_back(meta->fd.GetNumber()); |
5470 | 166k | } |
5471 | 847k | } |
5472 | | |
5473 | 121k | const auto& blob_files = storage_info_.GetBlobFiles(); |
5474 | 121k | for (const auto& meta : blob_files) { |
5475 | 0 | assert(meta); |
5476 | |
|
5477 | 0 | live_blob_files->emplace_back(meta->GetBlobFileNumber()); |
5478 | 0 | } |
5479 | 121k | } |
5480 | | |
5481 | | void Version::RemoveLiveFiles( |
5482 | | std::vector<ObsoleteFileInfo>& sst_delete_candidates, |
5483 | 24.7k | std::vector<ObsoleteBlobFileInfo>& blob_delete_candidates) const { |
5484 | 24.7k | for (ObsoleteFileInfo& fi : sst_delete_candidates) { |
5485 | 14.7k | if (!fi.only_delete_metadata && |
5486 | 13.5k | storage_info()->GetFileLocation(fi.metadata->fd.GetNumber()) != |
5487 | 13.5k | VersionStorageInfo::FileLocation::Invalid()) { |
5488 | 6.08k | fi.only_delete_metadata = true; |
5489 | 6.08k | } |
5490 | 14.7k | } |
5491 | | |
5492 | 24.7k | blob_delete_candidates.erase( |
5493 | 24.7k | std::remove_if( |
5494 | 24.7k | blob_delete_candidates.begin(), blob_delete_candidates.end(), |
5495 | 24.7k | [this](ObsoleteBlobFileInfo& x) { |
5496 | 0 | return storage_info()->GetBlobFileMetaData(x.GetBlobFileNumber()); |
5497 | 0 | }), |
5498 | 24.7k | blob_delete_candidates.end()); |
5499 | 24.7k | } |
5500 | | |
5501 | 0 | std::string Version::DebugString(bool hex, bool print_stats) const { |
5502 | 0 | std::string r; |
5503 | 0 | for (int level = 0; level < storage_info_.num_levels_; level++) { |
5504 | | // E.g., |
5505 | | // --- level 1 --- |
5506 | | // 17:123[1 .. 124]['a' .. 'd'] |
5507 | | // 20:43[124 .. 128]['e' .. 'g'] |
5508 | | // |
5509 | | // if print_stats=true: |
5510 | | // 17:123[1 .. 124]['a' .. 'd'](4096) |
5511 | 0 | r.append("--- level "); |
5512 | 0 | AppendNumberTo(&r, level); |
5513 | 0 | r.append(" --- version# "); |
5514 | 0 | AppendNumberTo(&r, version_number_); |
5515 | 0 | if (storage_info_.compact_cursor_[level].Valid()) { |
5516 | 0 | r.append(" --- compact_cursor: "); |
5517 | 0 | r.append(storage_info_.compact_cursor_[level].DebugString(hex)); |
5518 | 0 | } |
5519 | 0 | r.append(" ---\n"); |
5520 | 0 | const std::vector<FileMetaData*>& files = storage_info_.files_[level]; |
5521 | 0 | for (size_t i = 0; i < files.size(); i++) { |
5522 | 0 | r.push_back(' '); |
5523 | 0 | AppendNumberTo(&r, files[i]->fd.GetNumber()); |
5524 | 0 | r.push_back(':'); |
5525 | 0 | AppendNumberTo(&r, files[i]->fd.GetFileSize()); |
5526 | 0 | r.append("["); |
5527 | 0 | AppendNumberTo(&r, files[i]->fd.smallest_seqno); |
5528 | 0 | r.append(" .. "); |
5529 | 0 | AppendNumberTo(&r, files[i]->fd.largest_seqno); |
5530 | 0 | r.append("]"); |
5531 | 0 | r.append("["); |
5532 | 0 | r.append(files[i]->smallest.DebugString(hex)); |
5533 | 0 | r.append(" .. "); |
5534 | 0 | r.append(files[i]->largest.DebugString(hex)); |
5535 | 0 | r.append("]"); |
5536 | 0 | if (files[i]->oldest_blob_file_number != kInvalidBlobFileNumber) { |
5537 | 0 | r.append(" blob_file:"); |
5538 | 0 | AppendNumberTo(&r, files[i]->oldest_blob_file_number); |
5539 | 0 | } |
5540 | 0 | if (print_stats) { |
5541 | 0 | r.append("("); |
5542 | 0 | r.append(std::to_string( |
5543 | 0 | files[i]->stats.num_reads_sampled.load(std::memory_order_relaxed))); |
5544 | 0 | r.append(")"); |
5545 | 0 | } |
5546 | 0 | r.append("\n"); |
5547 | 0 | } |
5548 | 0 | } |
5549 | |
|
5550 | 0 | const auto& blob_files = storage_info_.GetBlobFiles(); |
5551 | 0 | if (!blob_files.empty()) { |
5552 | 0 | r.append("--- blob files --- version# "); |
5553 | 0 | AppendNumberTo(&r, version_number_); |
5554 | 0 | r.append(" ---\n"); |
5555 | 0 | for (const auto& blob_file_meta : blob_files) { |
5556 | 0 | assert(blob_file_meta); |
5557 | |
|
5558 | 0 | r.append(blob_file_meta->DebugString()); |
5559 | 0 | r.push_back('\n'); |
5560 | 0 | } |
5561 | 0 | } |
5562 | |
|
5563 | 0 | return r; |
5564 | 0 | } |
5565 | | |
5566 | | // this is used to batch writes to the manifest file |
5567 | | struct VersionSet::ManifestWriter { |
5568 | | Status status; |
5569 | | bool done; |
5570 | | InstrumentedCondVar cv; |
5571 | | ColumnFamilyData* cfd; |
5572 | | const autovector<VersionEdit*>& edit_list; |
5573 | | const std::function<void(const Status&)> manifest_write_callback; |
5574 | | int max_file_opening_threads; |
5575 | | |
5576 | | explicit ManifestWriter( |
5577 | | InstrumentedMutex* mu, ColumnFamilyData* _cfd, |
5578 | | const autovector<VersionEdit*>& e, |
5579 | | const std::function<void(const Status&)>& manifest_wcb, |
5580 | | int _max_file_opening_threads = 1) |
5581 | 98.9k | : done(false), |
5582 | 98.9k | cv(mu), |
5583 | 98.9k | cfd(_cfd), |
5584 | 98.9k | edit_list(e), |
5585 | 98.9k | manifest_write_callback(manifest_wcb), |
5586 | 98.9k | max_file_opening_threads(_max_file_opening_threads) {} |
5587 | | // No copying allowed |
5588 | | ManifestWriter(const ManifestWriter&) = delete; |
5589 | | ManifestWriter& operator=(const ManifestWriter&) = delete; |
5590 | | ManifestWriter(ManifestWriter&&) = delete; |
5591 | | ManifestWriter& operator=(ManifestWriter&&) = delete; |
5592 | 98.9k | ~ManifestWriter() { status.PermitUncheckedError(); } |
5593 | | |
5594 | 67.6k | bool IsAllVersionIndependentEdits() const { |
5595 | 67.6k | for (const auto& e : edit_list) { |
5596 | 67.6k | if (!e->IsWalManipulation() && !e->IsOptionsFileManipulation()) { |
5597 | 67.6k | return false; |
5598 | 67.6k | } |
5599 | 67.6k | } |
5600 | 0 | return true; |
5601 | 67.6k | } |
5602 | | }; |
5603 | | |
5604 | 393k | Status AtomicGroupReadBuffer::AddEdit(VersionEdit* edit) { |
5605 | 393k | assert(edit); |
5606 | 393k | if (edit->IsInAtomicGroup()) { |
5607 | 0 | TEST_SYNC_POINT("AtomicGroupReadBuffer::AddEdit:AtomicGroup"); |
5608 | 0 | if (replay_buffer_.empty()) { |
5609 | 0 | replay_buffer_.resize(edit->GetRemainingEntries() + 1); |
5610 | 0 | TEST_SYNC_POINT_CALLBACK( |
5611 | 0 | "AtomicGroupReadBuffer::AddEdit:FirstInAtomicGroup", edit); |
5612 | 0 | } |
5613 | 0 | read_edits_in_atomic_group_++; |
5614 | 0 | if (read_edits_in_atomic_group_ + edit->GetRemainingEntries() != |
5615 | 0 | static_cast<uint32_t>(replay_buffer_.size())) { |
5616 | 0 | TEST_SYNC_POINT_CALLBACK( |
5617 | 0 | "AtomicGroupReadBuffer::AddEdit:IncorrectAtomicGroupSize", edit); |
5618 | 0 | return Status::Corruption("corrupted atomic group"); |
5619 | 0 | } |
5620 | 0 | replay_buffer_[read_edits_in_atomic_group_ - 1] = *edit; |
5621 | 0 | if (read_edits_in_atomic_group_ == replay_buffer_.size()) { |
5622 | 0 | TEST_SYNC_POINT_CALLBACK( |
5623 | 0 | "AtomicGroupReadBuffer::AddEdit:LastInAtomicGroup", edit); |
5624 | 0 | return Status::OK(); |
5625 | 0 | } |
5626 | 0 | return Status::OK(); |
5627 | 0 | } |
5628 | | |
5629 | | // A normal edit. |
5630 | 393k | if (!replay_buffer().empty()) { |
5631 | 0 | TEST_SYNC_POINT_CALLBACK( |
5632 | 0 | "AtomicGroupReadBuffer::AddEdit:AtomicGroupMixedWithNormalEdits", edit); |
5633 | 0 | return Status::Corruption("corrupted atomic group"); |
5634 | 0 | } |
5635 | 393k | return Status::OK(); |
5636 | 393k | } |
5637 | | |
5638 | 0 | bool AtomicGroupReadBuffer::IsFull() const { |
5639 | 0 | return read_edits_in_atomic_group_ == replay_buffer_.size(); |
5640 | 0 | } |
5641 | | |
5642 | 0 | bool AtomicGroupReadBuffer::IsEmpty() const { return replay_buffer_.empty(); } |
5643 | | |
5644 | 0 | void AtomicGroupReadBuffer::Clear() { |
5645 | 0 | read_edits_in_atomic_group_ = 0; |
5646 | 0 | replay_buffer_.clear(); |
5647 | 0 | } |
5648 | | |
5649 | | VersionSet::VersionSet( |
5650 | | const std::string& dbname, const ImmutableDBOptions* _db_options, |
5651 | | const MutableDBOptions& mutable_db_options, |
5652 | | const FileOptions& storage_options, Cache* table_cache, |
5653 | | WriteBufferManager* write_buffer_manager, WriteController* write_controller, |
5654 | | BlockCacheTracer* const block_cache_tracer, |
5655 | | const std::shared_ptr<IOTracer>& io_tracer, const std::string& db_id, |
5656 | | const std::string& db_session_id, const std::string& daily_offpeak_time_utc, |
5657 | | ErrorHandler* error_handler, bool unchanging) |
5658 | 44.8k | : column_family_set_(new ColumnFamilySet( |
5659 | 44.8k | dbname, _db_options, storage_options, table_cache, |
5660 | 44.8k | write_buffer_manager, write_controller, block_cache_tracer, io_tracer, |
5661 | 44.8k | db_id, db_session_id, mutable_db_options.fast_sst_open)), |
5662 | 44.8k | table_cache_(table_cache), |
5663 | 44.8k | env_(_db_options->env), |
5664 | 44.8k | fs_(_db_options->fs, io_tracer), |
5665 | 44.8k | clock_(_db_options->clock), |
5666 | 44.8k | dbname_(dbname), |
5667 | 44.8k | db_id_(db_id), |
5668 | 44.8k | db_options_(_db_options), |
5669 | 44.8k | next_file_number_(2), |
5670 | 44.8k | manifest_file_number_(0), // Filled by Recover() |
5671 | 44.8k | options_file_number_(0), |
5672 | 44.8k | options_file_size_(0), |
5673 | 44.8k | has_committed_options_file_number_(false), |
5674 | 44.8k | pending_manifest_file_number_(0), |
5675 | 44.8k | last_sequence_(0), |
5676 | 44.8k | last_allocated_sequence_(0), |
5677 | 44.8k | last_published_sequence_(0), |
5678 | 44.8k | prev_log_number_(0), |
5679 | 44.8k | current_version_number_(0), |
5680 | 44.8k | manifest_file_size_(0), |
5681 | 44.8k | manifest_recovery_last_valid_record_end_(0), |
5682 | 44.8k | manifest_last_valid_record_end_(0), |
5683 | 44.8k | manifest_last_valid_record_end_file_number_(0), |
5684 | 44.8k | force_new_manifest_on_open_(false), |
5685 | 44.8k | last_compacted_manifest_file_size_(0), |
5686 | 44.8k | file_options_(storage_options), |
5687 | 44.8k | block_cache_tracer_(block_cache_tracer), |
5688 | 44.8k | io_tracer_(io_tracer), |
5689 | 44.8k | db_session_id_(db_session_id), |
5690 | 44.8k | offpeak_time_option_(OffpeakTimeOption(daily_offpeak_time_utc)), |
5691 | 44.8k | error_handler_(error_handler), |
5692 | 44.8k | unchanging_(unchanging), |
5693 | 44.8k | closed_(false) { |
5694 | 44.8k | UpdatedMutableDbOptions(mutable_db_options, /*mu=*/nullptr); |
5695 | 44.8k | } |
5696 | | |
5697 | 44.8k | Status VersionSet::Close(FSDirectory* db_dir, InstrumentedMutex* mu) { |
5698 | 44.8k | Status s; |
5699 | 44.8k | if (closed_ || unchanging_ || !manifest_file_number_ || !descriptor_log_) { |
5700 | 0 | return s; |
5701 | 0 | } |
5702 | | |
5703 | 44.8k | std::string manifest_file_name = |
5704 | 44.8k | DescriptorFileName(dbname_, manifest_file_number_); |
5705 | 44.8k | uint64_t size = 0; |
5706 | 44.8k | IOStatus io_s = descriptor_log_->Close(WriteOptions()); |
5707 | 44.8k | descriptor_log_.reset(); |
5708 | 44.8k | TEST_SYNC_POINT("VersionSet::Close:AfterClose"); |
5709 | 44.8k | if (io_s.ok()) { |
5710 | 44.8k | io_s = fs_->GetFileSize(manifest_file_name, IOOptions(), &size, nullptr); |
5711 | 44.8k | } |
5712 | 44.8k | if (!io_s.ok() || size != manifest_file_size_) { |
5713 | 0 | if (io_s.ok()) { |
5714 | | // This means the size is not as expected. So we treat it as a |
5715 | | // corruption and set io_s appropriately |
5716 | 0 | io_s = IOStatus::Corruption(); |
5717 | 0 | } |
5718 | 0 | ColumnFamilyData* cfd = GetColumnFamilySet()->GetDefault(); |
5719 | 0 | IOErrorInfo io_error_info(io_s, FileOperationType::kVerify, |
5720 | 0 | manifest_file_name, /*length=*/size, |
5721 | 0 | /*offset=*/0); |
5722 | |
|
5723 | 0 | for (auto& listener : cfd->ioptions().listeners) { |
5724 | 0 | listener->OnIOError(io_error_info); |
5725 | 0 | } |
5726 | 0 | io_s.PermitUncheckedError(); |
5727 | 0 | io_error_info.io_status.PermitUncheckedError(); |
5728 | 0 | ROCKS_LOG_ERROR(db_options_->info_log, |
5729 | 0 | "MANIFEST verification on Close, " |
5730 | 0 | "filename %s, expected size %" PRIu64 |
5731 | 0 | " failed with status %s and " |
5732 | 0 | "actual size %" PRIu64 "\n", |
5733 | 0 | manifest_file_name.c_str(), manifest_file_size_, |
5734 | 0 | io_s.ToString().c_str(), size); |
5735 | 0 | VersionEdit edit; |
5736 | 0 | assert(cfd); |
5737 | 0 | s = LogAndApply(cfd, ReadOptions(), WriteOptions(), &edit, mu, db_dir); |
5738 | 0 | } |
5739 | | |
5740 | | // Content validation: read back the manifest and verify CRC + decode. |
5741 | | // Loop up to 2 checks with 1 rewrite attempt in between, so we also verify |
5742 | | // the rewritten manifest is healthy. |
5743 | 44.8k | if (s.ok() && verify_manifest_content_on_close_) { |
5744 | 0 | TEST_SYNC_POINT("VersionSet::Close:BeforeContentValidation"); |
5745 | 0 | constexpr int kMaxContentChecks = 2; |
5746 | 0 | for (int content_check = 0; s.ok() && content_check < kMaxContentChecks; |
5747 | 0 | ++content_check) { |
5748 | | // Re-read the manifest file name in case it was rotated by a rewrite |
5749 | 0 | std::string content_manifest_name = |
5750 | 0 | DescriptorFileName(dbname_, manifest_file_number_); |
5751 | 0 | std::unique_ptr<FSSequentialFile> manifest_file; |
5752 | 0 | IOStatus content_io_s = fs_->NewSequentialFile( |
5753 | 0 | content_manifest_name, fs_->OptimizeForManifestRead(file_options_), |
5754 | 0 | &manifest_file, nullptr); |
5755 | 0 | if (!content_io_s.ok()) { |
5756 | | // Surface I/O errors to the caller -- users who call DB::Close() and |
5757 | | // check the status should know about filesystem problems. |
5758 | 0 | s = content_io_s; |
5759 | 0 | ROCKS_LOG_ERROR(db_options_->info_log, |
5760 | 0 | "MANIFEST content verification on Close: " |
5761 | 0 | "could not open %s for reading: %s\n", |
5762 | 0 | content_manifest_name.c_str(), |
5763 | 0 | content_io_s.ToString().c_str()); |
5764 | 0 | break; |
5765 | 0 | } |
5766 | 0 | std::unique_ptr<SequentialFileReader> manifest_file_reader( |
5767 | 0 | new SequentialFileReader(std::move(manifest_file), |
5768 | 0 | content_manifest_name, |
5769 | 0 | db_options_->log_readahead_size, io_tracer_, |
5770 | 0 | db_options_->listeners)); |
5771 | 0 | LogReporter reporter; |
5772 | 0 | Status log_read_status; |
5773 | 0 | reporter.status = &log_read_status; |
5774 | 0 | log::Reader reader(nullptr, std::move(manifest_file_reader), &reporter, |
5775 | 0 | /*checksum=*/true, /*log_num=*/0); |
5776 | 0 | Slice record; |
5777 | 0 | std::string scratch; |
5778 | 0 | bool content_corrupt = false; |
5779 | 0 | while (reader.ReadRecord(&record, &scratch, |
5780 | 0 | WALRecoveryMode::kAbsoluteConsistency)) { |
5781 | 0 | VersionEdit edit; |
5782 | 0 | Status decode_s = edit.DecodeFrom(record); |
5783 | 0 | if (!decode_s.ok()) { |
5784 | 0 | content_corrupt = true; |
5785 | 0 | break; |
5786 | 0 | } |
5787 | 0 | } |
5788 | 0 | if (!content_corrupt && !log_read_status.ok()) { |
5789 | 0 | content_corrupt = true; |
5790 | 0 | } |
5791 | 0 | if (!content_corrupt) { |
5792 | | // Manifest is healthy, no need to check again |
5793 | 0 | break; |
5794 | 0 | } |
5795 | 0 | RecordTick(db_options_->statistics.get(), |
5796 | 0 | MANIFEST_VALIDATION_FAILURE_COUNT); |
5797 | 0 | IOStatus corrupt_io_s = |
5798 | 0 | IOStatus::Corruption("MANIFEST content validation failed"); |
5799 | 0 | IOErrorInfo io_error_info(corrupt_io_s, FileOperationType::kVerify, |
5800 | 0 | content_manifest_name, /*length=*/0, |
5801 | 0 | /*offset=*/0); |
5802 | 0 | for (auto& listener : db_options_->listeners) { |
5803 | 0 | listener->OnIOError(io_error_info); |
5804 | 0 | } |
5805 | 0 | corrupt_io_s.PermitUncheckedError(); |
5806 | 0 | io_error_info.io_status.PermitUncheckedError(); |
5807 | 0 | if (content_check == 0) { |
5808 | | // First check failed -- rewrite and verify again |
5809 | 0 | ROCKS_LOG_ERROR(db_options_->info_log, |
5810 | 0 | "MANIFEST content verification on Close failed, " |
5811 | 0 | "filename %s, rewriting manifest\n", |
5812 | 0 | content_manifest_name.c_str()); |
5813 | 0 | ColumnFamilyData* cfd = GetColumnFamilySet()->GetDefault(); |
5814 | 0 | VersionEdit recovery_edit; |
5815 | 0 | assert(cfd); |
5816 | 0 | s = LogAndApply(cfd, ReadOptions(), WriteOptions(), &recovery_edit, mu, |
5817 | 0 | db_dir); |
5818 | 0 | } else { |
5819 | | // Rewritten manifest is also corrupt -- likely a recurring filesystem |
5820 | | // issue. Surface it so DB::Close() callers can detect the problem. |
5821 | 0 | ROCKS_LOG_ERROR(db_options_->info_log, |
5822 | 0 | "MANIFEST content verification on Close failed again " |
5823 | 0 | "after rewrite, filename %s\n", |
5824 | 0 | content_manifest_name.c_str()); |
5825 | 0 | s = Status::Corruption( |
5826 | 0 | "MANIFEST content verification failed after rewrite: " + |
5827 | 0 | content_manifest_name); |
5828 | 0 | } |
5829 | 0 | } |
5830 | 0 | } |
5831 | | |
5832 | 44.8k | closed_ = true; |
5833 | 44.8k | return s; |
5834 | 44.8k | } |
5835 | | |
5836 | 44.8k | VersionSet::~VersionSet() { |
5837 | | // Must clean up column families to make all files "obsolete" |
5838 | 44.8k | column_family_set_.reset(); |
5839 | | |
5840 | 81.9k | for (auto& file : obsolete_files_) { |
5841 | | // NOTE: DB is shutting down, so file is probably not obsolete, just |
5842 | | // no longer referenced by Versions in memory. |
5843 | | // For more context, see comment on "table_cache_->EraseUnRefEntries()" |
5844 | | // in DBImpl::CloseHelper(). |
5845 | | // Using uncache_aggressiveness=0 overrides any previous marking to |
5846 | | // attempt to uncache the file's blocks (which after cleaning up |
5847 | | // column families could cause use-after-free) |
5848 | 81.9k | TableCache::ReleaseObsolete( |
5849 | 81.9k | table_cache_, file.metadata->fd.GetNumber(), |
5850 | 81.9k | file.metadata->fd.pinned_reader.GetCacheHandle(), |
5851 | 81.9k | /*uncache_aggressiveness=*/0); |
5852 | 81.9k | file.DeleteMetadata(); |
5853 | 81.9k | } |
5854 | 44.8k | obsolete_files_.clear(); |
5855 | 44.8k | io_status_.PermitUncheckedError(); |
5856 | 44.8k | } |
5857 | | |
5858 | 0 | void VersionSet::ApplyCommittedOptionsFileNumber(uint64_t number) { |
5859 | 0 | options_file_number_ = number; |
5860 | 0 | has_committed_options_file_number_ = true; |
5861 | 0 | } |
5862 | | |
5863 | 121k | void VersionSet::ApplyLegacyOptionsFileNumber(uint64_t number) { |
5864 | 121k | options_file_number_ = number; |
5865 | 121k | has_committed_options_file_number_ = false; |
5866 | 121k | } |
5867 | | |
5868 | 0 | void VersionSet::Reset() { |
5869 | 0 | if (column_family_set_) { |
5870 | 0 | WriteBufferManager* wbm = column_family_set_->write_buffer_manager(); |
5871 | 0 | WriteController* wc = column_family_set_->write_controller(); |
5872 | 0 | FlushInitiator* flush_initiator = column_family_set_->flush_initiator(); |
5873 | 0 | const bool fast_sst_open = column_family_set_->GetFastSstOpen(); |
5874 | | |
5875 | | // Destruction removes column families one at a time. Detach the accounting |
5876 | | // target first so each removal does not rescan all remaining column |
5877 | | // families while falling back to another MANIFEST. |
5878 | 0 | column_family_set_->SetFlushInitiator(nullptr); |
5879 | | |
5880 | | // Clear TableCache to prevent use-after-free: Reset() deletes old |
5881 | | // ColumnFamilySet but reuses table_cache_, which may contain |
5882 | | // BlockBasedTable entries with dangling references to deleted CFD's |
5883 | | // ioptions. |
5884 | 0 | if (table_cache_) { |
5885 | 0 | table_cache_->EraseUnRefEntries(); |
5886 | 0 | } |
5887 | | |
5888 | | // db_id becomes the source of truth after DBImpl::Recover(): |
5889 | | // https://github.com/meta/rocksdb/blob/v7.3.1/db/db_impl/db_impl_open.cc#L527 |
5890 | | // Note: we may not be able to recover db_id from MANIFEST if |
5891 | | // options.write_dbid_to_manifest is false (default). |
5892 | 0 | column_family_set_.reset( |
5893 | 0 | new ColumnFamilySet(dbname_, db_options_, file_options_, table_cache_, |
5894 | 0 | wbm, wc, block_cache_tracer_, io_tracer_, db_id_, |
5895 | 0 | db_session_id_, fast_sst_open)); |
5896 | 0 | column_family_set_->SetFlushInitiator(flush_initiator); |
5897 | 0 | } |
5898 | 0 | db_id_.clear(); |
5899 | 0 | next_file_number_.store(2); |
5900 | 0 | min_log_number_to_keep_.store(0); |
5901 | 0 | manifest_file_number_ = 0; |
5902 | 0 | options_file_number_ = 0; |
5903 | 0 | options_file_size_ = 0; |
5904 | 0 | has_committed_options_file_number_ = false; |
5905 | 0 | pending_manifest_file_number_ = 0; |
5906 | 0 | last_sequence_.store(0); |
5907 | 0 | last_allocated_sequence_.store(0); |
5908 | 0 | last_published_sequence_.store(0); |
5909 | 0 | prev_log_number_ = 0; |
5910 | 0 | descriptor_log_.reset(); |
5911 | 0 | current_version_number_ = 0; |
5912 | 0 | manifest_writers_.clear(); |
5913 | 0 | manifest_file_size_ = 0; |
5914 | 0 | manifest_recovery_last_valid_record_end_ = 0; |
5915 | 0 | manifest_last_valid_record_end_ = 0; |
5916 | 0 | manifest_last_valid_record_end_file_number_ = 0; |
5917 | 0 | force_new_manifest_on_open_ = false; |
5918 | 0 | last_compacted_manifest_file_size_ = 0; |
5919 | 0 | TuneMaxManifestFileSize(); |
5920 | 0 | obsolete_files_.clear(); |
5921 | 0 | obsolete_manifests_.clear(); |
5922 | 0 | wals_.Reset(); |
5923 | 0 | } |
5924 | | |
5925 | | void VersionSet::UpdatedMutableDbOptions( |
5926 | 44.8k | const MutableDBOptions& updated_options, InstrumentedMutex* mu) { |
5927 | | // Must be holding mutex if not called during initialization |
5928 | 44.8k | if (mu) { |
5929 | 0 | mu->AssertHeld(); |
5930 | 44.8k | } else { |
5931 | | // manifest_file_size_ must be 0 if called from the constructor |
5932 | 44.8k | assert(manifest_file_size_ == 0); |
5933 | 44.8k | } |
5934 | 44.8k | file_options_.writable_file_max_buffer_size = |
5935 | 44.8k | updated_options.writable_file_max_buffer_size; |
5936 | 44.8k | min_max_manifest_file_size_ = updated_options.max_manifest_file_size; |
5937 | 44.8k | max_manifest_space_amp_pct_ = static_cast<unsigned>( |
5938 | 44.8k | std::max(updated_options.max_manifest_space_amp_pct, 0)); |
5939 | 44.8k | manifest_preallocation_size_ = updated_options.manifest_preallocation_size; |
5940 | 44.8k | verify_manifest_content_on_close_ = |
5941 | 44.8k | updated_options.verify_manifest_content_on_close; |
5942 | | |
5943 | 44.8k | if (periodic_compaction_phaser_.SetConfig( |
5944 | 44.8k | updated_options.periodic_compaction_phase_recovery_percent)) { |
5945 | | // (Re)anchor phasing so that switching it on (at open or via SetDBOptions) |
5946 | | // spreads the initial periodic-compaction catch-up burst over time rather |
5947 | | // than triggering it all at once. |
5948 | 0 | int64_t now = 0; |
5949 | 0 | if (clock_ != nullptr && clock_->GetCurrentTime(&now).ok()) { |
5950 | 0 | periodic_compaction_phaser_.Reanchor(static_cast<uint64_t>(now)); |
5951 | 0 | } |
5952 | 0 | if (mu != nullptr) { |
5953 | | // Live SetDBOptions change (not construction): push the refreshed phase |
5954 | | // params into each column family's current Version so the change takes |
5955 | | // effect on the next periodic re-evaluation (which recomputes scores on |
5956 | | // the current Version) rather than only when the next Version is built. |
5957 | | // Safe because periodic_compaction_phase_params_ is read only under the |
5958 | | // DB mutex, which is held here. |
5959 | 0 | for (auto* cfd : *column_family_set_) { |
5960 | 0 | if (cfd->IsDropped()) { |
5961 | 0 | continue; |
5962 | 0 | } |
5963 | 0 | Version* v = cfd->current(); |
5964 | 0 | if (v != nullptr) { |
5965 | 0 | v->storage_info_.periodic_compaction_phase_params_ = |
5966 | 0 | GetPeriodicCompactionPhaseParams(cfd->GetID()); |
5967 | 0 | } |
5968 | 0 | } |
5969 | 0 | } |
5970 | 0 | } |
5971 | | |
5972 | 44.8k | TuneMaxManifestFileSize(); |
5973 | 44.8k | } |
5974 | | |
5975 | | PeriodicCompactionPhaseParams VersionSet::GetPeriodicCompactionPhaseParams( |
5976 | 215k | uint32_t cf_id) const { |
5977 | 215k | return periodic_compaction_phaser_.ParamsForCf(cf_id); |
5978 | 215k | } |
5979 | | |
5980 | 0 | void VersionSet::ReanchorCompactionPhase() { |
5981 | | // Caller holds the DB mutex. Move the phasing anchor to now and refresh each |
5982 | | // CF's current Version's cached params, mirroring the refresh done for |
5983 | | // seed/recovery changes in UpdatedMutableDbOptions(). Used when a CF's |
5984 | | // periodic_compaction_seconds changes, so a turn-down's newly past-due cohort |
5985 | | // is spread rather than fired at once. Re-anchoring is DB-level and benign to |
5986 | | // CFs whose interval did not change (their not-past-due files keep phasing). |
5987 | 0 | int64_t now = 0; |
5988 | 0 | if (clock_ != nullptr && clock_->GetCurrentTime(&now).ok()) { |
5989 | 0 | periodic_compaction_phaser_.Reanchor(static_cast<uint64_t>(now)); |
5990 | 0 | } |
5991 | 0 | for (auto* cfd : *column_family_set_) { |
5992 | 0 | if (cfd->IsDropped()) { |
5993 | 0 | continue; |
5994 | 0 | } |
5995 | 0 | Version* v = cfd->current(); |
5996 | 0 | if (v != nullptr) { |
5997 | 0 | v->storage_info_.periodic_compaction_phase_params_ = |
5998 | 0 | GetPeriodicCompactionPhaseParams(cfd->GetID()); |
5999 | 0 | } |
6000 | 0 | } |
6001 | 0 | } |
6002 | | |
6003 | 126k | void VersionSet::TuneMaxManifestFileSize() { |
6004 | 126k | tuned_max_manifest_file_size_ = |
6005 | 126k | std::max(min_max_manifest_file_size_, |
6006 | 126k | last_compacted_manifest_file_size_ * |
6007 | 126k | (100U + max_manifest_space_amp_pct_) / 100U); |
6008 | 126k | } |
6009 | | |
6010 | | void VersionSet::AppendVersion(ColumnFamilyData* column_family_data, |
6011 | 215k | Version* v) { |
6012 | | // compute new compaction score |
6013 | 215k | v->storage_info()->ComputeCompactionScore( |
6014 | 215k | column_family_data->ioptions(), |
6015 | 215k | column_family_data->GetLatestMutableCFOptions(), |
6016 | 215k | column_family_data->GetFullHistoryTsLow()); |
6017 | | |
6018 | | // Mark v finalized |
6019 | 215k | v->storage_info_.SetFinalized(); |
6020 | | |
6021 | | // Make "v" current |
6022 | 215k | assert(v->refs_ == 0); |
6023 | 215k | Version* current = column_family_data->current(); |
6024 | 215k | assert(v != current); |
6025 | 215k | if (current != nullptr) { |
6026 | 128k | assert(current->refs_ > 0); |
6027 | 128k | current->Unref(); |
6028 | 128k | } |
6029 | 215k | column_family_data->SetCurrent(v); |
6030 | 215k | v->Ref(); |
6031 | | |
6032 | | // Append to linked list |
6033 | 215k | v->prev_ = column_family_data->dummy_versions()->prev_; |
6034 | 215k | v->next_ = column_family_data->dummy_versions(); |
6035 | 215k | v->prev_->next_ = v; |
6036 | 215k | v->next_->prev_ = v; |
6037 | 215k | } |
6038 | | |
6039 | | Status VersionSet::ProcessManifestWrites( |
6040 | | std::deque<ManifestWriter>& writers, InstrumentedMutex* mu, |
6041 | | FSDirectory* dir_contains_current_file, bool new_descriptor_log, |
6042 | | const ColumnFamilyOptions* new_cf_options, |
6043 | | bool new_cf_options_are_sanitized, const ReadOptions& read_options, |
6044 | 83.3k | const WriteOptions& write_options) { |
6045 | 83.3k | mu->AssertHeld(); |
6046 | 83.3k | assert(!writers.empty()); |
6047 | 83.3k | ManifestWriter& first_writer = writers.front(); |
6048 | 83.3k | ManifestWriter* last_writer = &first_writer; |
6049 | | |
6050 | | // Takes this batch, from the queue head through `last_writer`, off |
6051 | | // manifest_writers_: records `s` on each writer, runs its callback, and wakes |
6052 | | // writers other threads wait on, then the next batch's leader. Every return |
6053 | | // path must do this, because the writers in `writers` live on the caller's |
6054 | | // stack and a queued pointer to one would outlive them. |
6055 | 83.3k | auto finish_batch = [&](const Status& s) { |
6056 | 98.9k | while (true) { |
6057 | 98.9k | ManifestWriter* ready = manifest_writers_.front(); |
6058 | 98.9k | manifest_writers_.pop_front(); |
6059 | 98.9k | bool need_signal = true; |
6060 | 114k | for (const auto& w : writers) { |
6061 | 114k | if (&w == ready) { |
6062 | 98.9k | need_signal = false; |
6063 | 98.9k | break; |
6064 | 98.9k | } |
6065 | 114k | } |
6066 | 98.9k | ready->status = s; |
6067 | 98.9k | ready->done = true; |
6068 | 98.9k | if (ready->manifest_write_callback) { |
6069 | 83.3k | (ready->manifest_write_callback)(s); |
6070 | 83.3k | } |
6071 | 98.9k | if (need_signal) { |
6072 | 0 | ready->cv.Signal(); |
6073 | 0 | } |
6074 | 98.9k | if (ready == last_writer) { |
6075 | 83.3k | break; |
6076 | 83.3k | } |
6077 | 98.9k | } |
6078 | 83.3k | if (!manifest_writers_.empty()) { |
6079 | 898 | manifest_writers_.front()->cv.Signal(); |
6080 | 898 | } |
6081 | 83.3k | }; |
6082 | | |
6083 | | // Supports opening table readers with multiple threads, mostly useful for |
6084 | | // batched external sst file ingestion. |
6085 | 83.3k | int batch_max_file_opening_threads = 1; |
6086 | | |
6087 | 83.3k | assert(!manifest_writers_.empty()); |
6088 | 83.3k | assert(manifest_writers_.front() == &first_writer); |
6089 | | |
6090 | 83.3k | autovector<VersionEdit*> batch_edits; |
6091 | | // This vector keeps track of the corresponding user-defined timestamp size |
6092 | | // for `batch_edits` side by side, which is only needed for encoding a |
6093 | | // `VersionEdit` that adds new SST files. |
6094 | | // Note that anytime `batch_edits` has new element added or get existing |
6095 | | // element removed, `batch_edits_ts_sz` should be updated too. |
6096 | 83.3k | autovector<std::optional<size_t>> batch_edits_ts_sz; |
6097 | 83.3k | autovector<Version*> versions; |
6098 | 83.3k | std::vector<std::unique_ptr<BaseReferencedVersionBuilder>> builder_guards; |
6099 | 83.3k | autovector<const autovector<uint64_t>*> files_to_quarantine_if_commit_fail; |
6100 | 83.3k | autovector<uint64_t> limbo_descriptor_log_file_number; |
6101 | | |
6102 | | // Tracking `max_last_sequence` is needed to ensure we write |
6103 | | // `VersionEdit::last_sequence_`s in non-decreasing order according to the |
6104 | | // recovery code's requirement. It also allows us to defer updating |
6105 | | // `descriptor_last_sequence_` until the apply phase, after the log phase |
6106 | | // succeeds. |
6107 | 83.3k | SequenceNumber max_last_sequence = descriptor_last_sequence_; |
6108 | | |
6109 | 83.3k | bool skip_manifest_write = |
6110 | 83.3k | first_writer.edit_list.front()->IsNoManifestWriteDummy(); |
6111 | | |
6112 | 83.3k | if (first_writer.edit_list.front()->IsColumnFamilyManipulation()) { |
6113 | | // No group commits for column family add or drop |
6114 | 31.3k | LogAndApplyCFHelper(first_writer.edit_list.front(), &max_last_sequence); |
6115 | 31.3k | batch_edits.push_back(first_writer.edit_list.front()); |
6116 | 31.3k | batch_edits_ts_sz.push_back(std::nullopt); |
6117 | 51.9k | } else { |
6118 | 51.9k | auto it = manifest_writers_.cbegin(); |
6119 | 51.9k | size_t group_start = std::numeric_limits<size_t>::max(); |
6120 | 51.9k | Status batch_apply_status; |
6121 | 67.6k | for (;;) { |
6122 | 67.6k | assert(!(*it)->edit_list.front()->IsColumnFamilyManipulation()); |
6123 | 67.6k | last_writer = *it; |
6124 | 67.6k | batch_max_file_opening_threads = |
6125 | 67.6k | std::max(batch_max_file_opening_threads, |
6126 | 67.6k | last_writer->max_file_opening_threads); |
6127 | 67.6k | assert(last_writer != nullptr); |
6128 | 67.6k | assert(last_writer->cfd != nullptr); |
6129 | 67.6k | if (!batch_apply_status.ok()) { |
6130 | | // An earlier edit failed, so apply no more edits, but keep grouping |
6131 | | // until the loop conditions below end the batch as usual. LogAndApply() |
6132 | | // returns once a caller's first writer is done, so none of its writers |
6133 | | // may be left queued. |
6134 | 67.6k | } else if (last_writer->cfd->IsDropped()) { |
6135 | | // If we detect a dropped CF at this point, and the corresponding |
6136 | | // version edits belong to an atomic group, then we need to find out |
6137 | | // the preceding version edits in the same atomic group, and update |
6138 | | // their `remaining_entries_` member variable because we are NOT going |
6139 | | // to write the version edits' of dropped CF to the MANIFEST. If we |
6140 | | // don't update, then Recover can report corrupted atomic group |
6141 | | // because the `remaining_entries_` do not match. |
6142 | 0 | if (!batch_edits.empty()) { |
6143 | 0 | if (batch_edits.back()->IsInAtomicGroup() && |
6144 | 0 | batch_edits.back()->GetRemainingEntries() > 0) { |
6145 | 0 | assert(group_start < batch_edits.size()); |
6146 | 0 | const auto& edit_list = last_writer->edit_list; |
6147 | 0 | size_t k = 0; |
6148 | 0 | while (k < edit_list.size()) { |
6149 | 0 | if (!edit_list[k]->IsInAtomicGroup()) { |
6150 | 0 | break; // inner while-`k` loop |
6151 | 0 | } else if (edit_list[k]->GetRemainingEntries() == 0) { |
6152 | 0 | ++k; |
6153 | 0 | break; // inner while-`k` loop |
6154 | 0 | } |
6155 | 0 | ++k; |
6156 | 0 | } |
6157 | 0 | for (auto i = group_start; i < batch_edits.size(); ++i) { |
6158 | 0 | assert(static_cast<uint32_t>(k) <= |
6159 | 0 | batch_edits.back()->GetRemainingEntries()); |
6160 | 0 | batch_edits[i]->SetRemainingEntries( |
6161 | 0 | batch_edits[i]->GetRemainingEntries() - |
6162 | 0 | static_cast<uint32_t>(k)); |
6163 | 0 | } |
6164 | 0 | } |
6165 | 0 | } |
6166 | 67.6k | } else { |
6167 | | // We do a linear search on versions because versions is small. |
6168 | | // TODO(yanqin) maybe consider unordered_map |
6169 | 67.6k | Version* version = nullptr; |
6170 | 67.6k | VersionBuilder* builder = nullptr; |
6171 | 83.3k | for (int i = 0; i != static_cast<int>(versions.size()); ++i) { |
6172 | 15.6k | uint32_t cf_id = last_writer->cfd->GetID(); |
6173 | 15.6k | if (versions[i]->cfd()->GetID() == cf_id) { |
6174 | 0 | version = versions[i]; |
6175 | 0 | assert(!builder_guards.empty() && |
6176 | 0 | builder_guards.size() == versions.size()); |
6177 | 0 | builder = builder_guards[i]->version_builder(); |
6178 | 0 | TEST_SYNC_POINT_CALLBACK( |
6179 | 0 | "VersionSet::ProcessManifestWrites:SameColumnFamily", &cf_id); |
6180 | 0 | break; // inner for-`i` loop |
6181 | 0 | } |
6182 | 15.6k | } |
6183 | 67.6k | if (version == nullptr) { |
6184 | | // WAL and OPTIONS protocol manipulations do not change LSM Versions. |
6185 | 67.6k | if (!last_writer->IsAllVersionIndependentEdits()) { |
6186 | 67.6k | version = new Version( |
6187 | 67.6k | last_writer->cfd, this, file_options_, |
6188 | 67.6k | last_writer->cfd ? last_writer->cfd->GetLatestMutableCFOptions() |
6189 | 67.6k | : MutableCFOptions(*new_cf_options), |
6190 | 67.6k | io_tracer_, current_version_number_++); |
6191 | 67.6k | versions.push_back(version); |
6192 | 67.6k | builder_guards.emplace_back(new BaseReferencedVersionBuilder( |
6193 | 67.6k | last_writer->cfd, last_writer->cfd->current())); |
6194 | 67.6k | builder = builder_guards.back()->version_builder(); |
6195 | 67.6k | } |
6196 | 67.6k | assert(last_writer->IsAllVersionIndependentEdits() || builder); |
6197 | 67.6k | assert(last_writer->IsAllVersionIndependentEdits() || version); |
6198 | 67.6k | TEST_SYNC_POINT_CALLBACK( |
6199 | 67.6k | "VersionSet::ProcessManifestWrites:NewVersion", version); |
6200 | 67.6k | } |
6201 | 67.6k | const Comparator* ucmp = last_writer->cfd->user_comparator(); |
6202 | 67.6k | assert(ucmp); |
6203 | 67.6k | std::optional<size_t> edit_ts_sz = ucmp->timestamp_size(); |
6204 | 179k | for (const auto& e : last_writer->edit_list) { |
6205 | 179k | if (e->IsInAtomicGroup()) { |
6206 | 0 | if (batch_edits.empty() || !batch_edits.back()->IsInAtomicGroup() || |
6207 | 0 | (batch_edits.back()->IsInAtomicGroup() && |
6208 | 0 | batch_edits.back()->GetRemainingEntries() == 0)) { |
6209 | 0 | group_start = batch_edits.size(); |
6210 | 0 | } |
6211 | 179k | } else if (group_start != std::numeric_limits<size_t>::max()) { |
6212 | 0 | group_start = std::numeric_limits<size_t>::max(); |
6213 | 0 | } |
6214 | 179k | batch_apply_status = LogAndApplyHelper(last_writer->cfd, builder, e, |
6215 | 179k | &max_last_sequence, mu); |
6216 | 179k | if (!batch_apply_status.ok()) { |
6217 | 0 | break; // inner for-`e` loop |
6218 | 0 | } |
6219 | 179k | batch_edits.push_back(e); |
6220 | 179k | batch_edits_ts_sz.push_back(edit_ts_sz); |
6221 | 179k | } |
6222 | 67.6k | } |
6223 | | // Loop increment/conditions |
6224 | 67.6k | ++it; |
6225 | 67.6k | if (it == manifest_writers_.cend()) { |
6226 | 51.9k | break; |
6227 | 51.9k | } |
6228 | 15.6k | if (skip_manifest_write) { |
6229 | | // no grouping when skipping manifest write |
6230 | 0 | break; |
6231 | 0 | } |
6232 | 15.6k | const auto* next = (*it)->edit_list.front(); |
6233 | 15.6k | if (next->IsColumnFamilyManipulation() || |
6234 | 15.6k | next->IsNoManifestWriteDummy()) { |
6235 | | // no group commits for column family add or drop |
6236 | | // nor for dummy skipping manifest write |
6237 | 0 | break; |
6238 | 0 | } |
6239 | 15.6k | } |
6240 | 51.9k | if (!batch_apply_status.ok()) { |
6241 | 0 | for (auto v : versions) { |
6242 | 0 | delete v; |
6243 | 0 | } |
6244 | 0 | finish_batch(batch_apply_status); |
6245 | 0 | return batch_apply_status; |
6246 | 0 | } |
6247 | 119k | for (int i = 0; i < static_cast<int>(versions.size()); ++i) { |
6248 | 67.6k | assert(!builder_guards.empty() && |
6249 | 67.6k | builder_guards.size() == versions.size()); |
6250 | 67.6k | auto* builder = builder_guards[i]->version_builder(); |
6251 | 67.6k | Status s = builder->SaveTo(versions[i]->storage_info()); |
6252 | 67.6k | if (!s.ok()) { |
6253 | | // free up the allocated memory |
6254 | 0 | for (auto v : versions) { |
6255 | 0 | delete v; |
6256 | 0 | } |
6257 | 0 | finish_batch(s); |
6258 | 0 | return s; |
6259 | 0 | } |
6260 | 67.6k | } |
6261 | 51.9k | } |
6262 | | |
6263 | | #ifndef NDEBUG |
6264 | | // Verify that version edits of atomic groups have correct |
6265 | | // remaining_entries_. |
6266 | | size_t k = 0; |
6267 | | while (k < batch_edits.size()) { |
6268 | | while (k < batch_edits.size() && !batch_edits[k]->IsInAtomicGroup()) { |
6269 | | ++k; |
6270 | | } |
6271 | | if (k == batch_edits.size()) { |
6272 | | break; |
6273 | | } |
6274 | | size_t i = k; |
6275 | | while (i < batch_edits.size()) { |
6276 | | if (!batch_edits[i]->IsInAtomicGroup()) { |
6277 | | break; |
6278 | | } |
6279 | | assert(i - k + batch_edits[i]->GetRemainingEntries() == |
6280 | | batch_edits[k]->GetRemainingEntries()); |
6281 | | if (batch_edits[i]->GetRemainingEntries() == 0) { |
6282 | | ++i; |
6283 | | break; |
6284 | | } |
6285 | | ++i; |
6286 | | } |
6287 | | assert(batch_edits[i - 1]->IsInAtomicGroup()); |
6288 | | assert(0 == batch_edits[i - 1]->GetRemainingEntries()); |
6289 | | std::vector<VersionEdit*> tmp; |
6290 | | for (size_t j = k; j != i; ++j) { |
6291 | | tmp.emplace_back(batch_edits[j]); |
6292 | | } |
6293 | | TEST_SYNC_POINT_CALLBACK( |
6294 | | "VersionSet::ProcessManifestWrites:CheckOneAtomicGroup", &tmp); |
6295 | | k = i; |
6296 | | } |
6297 | | if (skip_manifest_write) { |
6298 | | // no grouping when skipping manifest write |
6299 | | assert(last_writer == &first_writer); |
6300 | | } |
6301 | | #endif // NDEBUG |
6302 | | |
6303 | 83.3k | uint64_t prev_manifest_file_size = manifest_file_size_; |
6304 | 83.3k | assert(pending_manifest_file_number_ == 0); |
6305 | 83.3k | bool has_foreground_operation = false; |
6306 | 210k | for (const VersionEdit* e : batch_edits) { |
6307 | 210k | if (e->IsForegroundOperation()) { |
6308 | 31.3k | has_foreground_operation = true; |
6309 | 31.3k | break; |
6310 | 31.3k | } |
6311 | 210k | } |
6312 | | // For MANIFEST write batches with any foreground operation (external file |
6313 | | // ingestion/import, DeleteFilesInRange(s), and column family manipulations |
6314 | | // like CreateColumnFamily and DropColumnFamily), relax the size limit by 25% |
6315 | | // to reduce the likelihood of a user operation blocking on MANIFEST rotation. |
6316 | | // Background-only batches (flush/compaction) still rotate at the normal |
6317 | | // threshold. |
6318 | | // TODO/future: for workloads like atomic-replace ingestion-only, with zero |
6319 | | // or few flushes and compactions, it might be nice to trigger background |
6320 | | // manifest rotation if we are beyond the soft limit. But the vast majority |
6321 | | // of workloads should have plenty of background manifest ops to avoid |
6322 | | // foreground rotation. |
6323 | 83.3k | uint64_t enforced_limit = tuned_max_manifest_file_size_; |
6324 | 83.3k | if (has_foreground_operation) { |
6325 | 31.3k | uint64_t new_limit = enforced_limit + enforced_limit / 4; |
6326 | | // don't keep in case of overflow |
6327 | 31.3k | enforced_limit = std::max(enforced_limit, new_limit); |
6328 | 31.3k | } |
6329 | 83.3k | if (!skip_manifest_write && |
6330 | 83.3k | (!descriptor_log_ || prev_manifest_file_size >= enforced_limit)) { |
6331 | 44.8k | TEST_SYNC_POINT("VersionSet::ProcessManifestWrites:BeforeNewManifest"); |
6332 | 44.8k | new_descriptor_log = true; |
6333 | 44.8k | } else { |
6334 | 38.4k | pending_manifest_file_number_ = manifest_file_number_; |
6335 | 38.4k | } |
6336 | | |
6337 | | // Local cached copy of state variable(s). WriteCurrentStateToManifest() |
6338 | | // reads its content after releasing db mutex to avoid race with |
6339 | | // SwitchMemtable(). |
6340 | 83.3k | std::unordered_map<uint32_t, MutableCFState> curr_state; |
6341 | 83.3k | VersionEdit wal_additions; |
6342 | 83.3k | OptionsFileManifestState options_manifest_state; |
6343 | 83.3k | if (new_descriptor_log) { |
6344 | 44.8k | pending_manifest_file_number_ = NewFileNumber(); |
6345 | 44.8k | batch_edits.back()->SetNextFile(next_file_number_.load()); |
6346 | | |
6347 | | // if we are writing out new snapshot make sure to persist max column |
6348 | | // family. |
6349 | 44.8k | if (column_family_set_->GetMaxColumnFamily() > 0) { |
6350 | 22.1k | first_writer.edit_list.front()->SetMaxColumnFamily( |
6351 | 22.1k | column_family_set_->GetMaxColumnFamily()); |
6352 | 22.1k | } |
6353 | 60.5k | for (const auto* cfd : *column_family_set_) { |
6354 | 60.5k | assert(curr_state.find(cfd->GetID()) == curr_state.end()); |
6355 | 60.5k | curr_state.emplace( |
6356 | 60.5k | cfd->GetID(), |
6357 | 60.5k | MutableCFState(cfd->GetLogNumber(), cfd->GetFullHistoryTsLow())); |
6358 | 60.5k | } |
6359 | | |
6360 | 44.8k | for (const auto& wal : wals_.GetWals()) { |
6361 | 0 | wal_additions.AddWal(wal.first, wal.second); |
6362 | 0 | } |
6363 | 44.8k | options_manifest_state = options_file_manifest_state(); |
6364 | 44.8k | } |
6365 | | |
6366 | 83.3k | uint64_t new_manifest_file_size = 0; |
6367 | 83.3k | Status s; |
6368 | 83.3k | IOStatus io_s; |
6369 | 83.3k | IOStatus manifest_io_status; |
6370 | 83.3k | manifest_io_status.PermitUncheckedError(); |
6371 | 83.3k | std::unique_ptr<log::Writer> new_desc_log_ptr; |
6372 | | // Save before releasing mu |
6373 | 83.3k | uint64_t manifest_preallocation_size = manifest_preallocation_size_; |
6374 | 83.3k | if (skip_manifest_write) { |
6375 | 0 | if (s.ok()) { |
6376 | 0 | constexpr bool update_stats = true; |
6377 | 0 | for (int i = 0; i < static_cast<int>(versions.size()); ++i) { |
6378 | | // NOTE: normally called with DB mutex released, but we don't |
6379 | | // want to release the DB mutex in this mode of LogAndApply |
6380 | 0 | versions[i]->PrepareAppend(read_options, update_stats); |
6381 | 0 | } |
6382 | 0 | } |
6383 | 83.3k | } else { |
6384 | 83.3k | FileOptions opt_file_opts = GetFileOptionsForManifestWrite(); |
6385 | 83.3k | mu->Unlock(); |
6386 | 83.3k | TEST_SYNC_POINT("VersionSet::LogAndApply:WriteManifestStart"); |
6387 | 83.3k | TEST_SYNC_POINT_CALLBACK("VersionSet::LogAndApply:WriteManifest", nullptr); |
6388 | 83.3k | if (!first_writer.edit_list.front()->IsColumnFamilyManipulation()) { |
6389 | 119k | for (int i = 0; i < static_cast<int>(versions.size()); ++i) { |
6390 | 67.6k | assert(!builder_guards.empty() && |
6391 | 67.6k | builder_guards.size() == versions.size()); |
6392 | 67.6k | ColumnFamilyData* cfd = versions[i]->cfd_; |
6393 | 67.6k | s = builder_guards[i]->version_builder()->LoadTableHandlers( |
6394 | 67.6k | cfd->internal_stats(), batch_max_file_opening_threads, |
6395 | 67.6k | true /* prefetch_index_and_filter_in_cache */, |
6396 | 67.6k | false /* is_initial_load */, versions[i]->GetMutableCFOptions(), |
6397 | 67.6k | MaxFileSizeForL0MetaPin(versions[i]->GetMutableCFOptions()), |
6398 | 67.6k | read_options); |
6399 | 67.6k | if (!s.ok()) { |
6400 | 0 | if (db_options_->paranoid_checks) { |
6401 | 0 | break; |
6402 | 0 | } |
6403 | 0 | s = Status::OK(); |
6404 | 0 | } |
6405 | 67.6k | } |
6406 | 51.9k | } |
6407 | | |
6408 | 83.3k | log::Writer* raw_desc_log_ptr = descriptor_log_.get(); |
6409 | 83.3k | if (s.ok() && new_descriptor_log) { |
6410 | | // This is fine because everything inside of this block is serialized -- |
6411 | | // only one thread can be here at the same time |
6412 | | // create new manifest file |
6413 | 44.8k | std::string descriptor_fname = |
6414 | 44.8k | DescriptorFileName(dbname_, pending_manifest_file_number_); |
6415 | 44.8k | std::unique_ptr<FSWritableFile> descriptor_file; |
6416 | 44.8k | io_s = NewWritableFile(fs_.get(), descriptor_fname, &descriptor_file, |
6417 | 44.8k | opt_file_opts); |
6418 | 44.8k | if (io_s.ok()) { |
6419 | 44.8k | new_desc_log_ptr = |
6420 | 44.8k | CreateManifestWriter(std::move(descriptor_file), descriptor_fname, |
6421 | 44.8k | opt_file_opts, manifest_preallocation_size); |
6422 | 44.8k | raw_desc_log_ptr = new_desc_log_ptr.get(); |
6423 | 44.8k | s = WriteCurrentStateToManifest(write_options, curr_state, |
6424 | 44.8k | wal_additions, options_manifest_state, |
6425 | 44.8k | raw_desc_log_ptr, io_s); |
6426 | 44.8k | assert(s == io_s); |
6427 | 44.8k | } |
6428 | 44.8k | if (!io_s.ok()) { |
6429 | 0 | manifest_io_status = io_s; |
6430 | 0 | s = io_s; |
6431 | 0 | } |
6432 | 44.8k | } |
6433 | | |
6434 | 83.3k | if (s.ok()) { |
6435 | 83.3k | if (!first_writer.edit_list.front()->IsColumnFamilyManipulation()) { |
6436 | 51.9k | constexpr bool update_stats = true; |
6437 | | |
6438 | 119k | for (int i = 0; i < static_cast<int>(versions.size()); ++i) { |
6439 | 67.6k | versions[i]->PrepareAppend(read_options, update_stats); |
6440 | 67.6k | } |
6441 | 51.9k | } |
6442 | | |
6443 | | // Write new records to MANIFEST log |
6444 | | #ifndef NDEBUG |
6445 | | size_t idx = 0; |
6446 | | #endif |
6447 | 83.3k | assert(batch_edits.size() == batch_edits_ts_sz.size()); |
6448 | 294k | for (size_t bidx = 0; bidx < batch_edits.size(); bidx++) { |
6449 | 210k | auto& e = batch_edits[bidx]; |
6450 | 210k | files_to_quarantine_if_commit_fail.push_back( |
6451 | 210k | e->GetFilesToQuarantineIfCommitFail()); |
6452 | 210k | std::string record; |
6453 | 210k | if (!e->EncodeTo(&record, batch_edits_ts_sz[bidx])) { |
6454 | 0 | s = Status::Corruption("Unable to encode VersionEdit:" + |
6455 | 0 | e->DebugString(true)); |
6456 | 0 | break; |
6457 | 0 | } |
6458 | 210k | TEST_KILL_RANDOM_WITH_WEIGHT("VersionSet::LogAndApply:BeforeAddRecord", |
6459 | 210k | REDUCE_ODDS2); |
6460 | | #ifndef NDEBUG |
6461 | | if (batch_edits.size() > 1 && batch_edits.size() - 1 == idx) { |
6462 | | TEST_SYNC_POINT_CALLBACK( |
6463 | | "VersionSet::ProcessManifestWrites:BeforeWriteLastVersionEdit:" |
6464 | | "0", |
6465 | | nullptr); |
6466 | | TEST_SYNC_POINT( |
6467 | | "VersionSet::ProcessManifestWrites:BeforeWriteLastVersionEdit:" |
6468 | | "1"); |
6469 | | } |
6470 | | ++idx; |
6471 | | #endif /* !NDEBUG */ |
6472 | 210k | TEST_SYNC_POINT("VersionSet::ProcessManifestWrites:AddRecord"); |
6473 | 210k | io_s = raw_desc_log_ptr->AddRecord(write_options, record); |
6474 | 210k | if (!io_s.ok()) { |
6475 | 0 | s = io_s; |
6476 | 0 | manifest_io_status = io_s; |
6477 | 0 | break; |
6478 | 0 | } |
6479 | 210k | } |
6480 | | |
6481 | 83.3k | if (s.ok()) { |
6482 | 83.3k | io_s = |
6483 | 83.3k | SyncManifest(db_options_, write_options, raw_desc_log_ptr->file()); |
6484 | 83.3k | manifest_io_status = io_s; |
6485 | 83.3k | TEST_SYNC_POINT_CALLBACK( |
6486 | 83.3k | "VersionSet::ProcessManifestWrites:AfterSyncManifest", &io_s); |
6487 | 83.3k | } |
6488 | 83.3k | if (!io_s.ok()) { |
6489 | 0 | s = io_s; |
6490 | 0 | ROCKS_LOG_ERROR(db_options_->info_log, "MANIFEST write %s\n", |
6491 | 0 | s.ToString().c_str()); |
6492 | 0 | } |
6493 | 83.3k | } |
6494 | | |
6495 | | // If we just created a new descriptor file, install it by writing a |
6496 | | // new CURRENT file that points to it. |
6497 | 83.3k | if (s.ok()) { |
6498 | 83.3k | assert(manifest_io_status.ok()); |
6499 | 83.3k | } |
6500 | 83.3k | if (s.ok() && new_descriptor_log) { |
6501 | 44.8k | io_s = SetCurrentFile( |
6502 | 44.8k | write_options, fs_.get(), dbname_, pending_manifest_file_number_, |
6503 | 44.8k | file_options_.temperature, dir_contains_current_file); |
6504 | 44.8k | TEST_SYNC_POINT_CALLBACK( |
6505 | 44.8k | "VersionSet::ProcessManifestWrites:AfterSetCurrentFile", &io_s); |
6506 | 44.8k | if (!io_s.ok()) { |
6507 | 0 | s = io_s; |
6508 | | // Quarantine old manifest file in case new manifest file's CURRENT |
6509 | | // file wasn't created successfully and the old manifest is needed. |
6510 | 0 | limbo_descriptor_log_file_number.push_back(manifest_file_number_); |
6511 | 0 | files_to_quarantine_if_commit_fail.push_back( |
6512 | 0 | &limbo_descriptor_log_file_number); |
6513 | 0 | } |
6514 | 44.8k | } |
6515 | | |
6516 | 83.3k | if (s.ok()) { |
6517 | | // find offset in manifest file where this version is stored. |
6518 | 83.3k | new_manifest_file_size = raw_desc_log_ptr->file()->GetFileSize(); |
6519 | 83.3k | if (new_descriptor_log) { |
6520 | 44.8k | ROCKS_LOG_INFO(db_options_->info_log, |
6521 | 44.8k | "Created manifest %" PRIu64 |
6522 | 44.8k | ", compacted+appended from %" PRIu64 " to %" PRIu64 |
6523 | 44.8k | ", db_session_id=%s\n", |
6524 | 44.8k | pending_manifest_file_number_, prev_manifest_file_size, |
6525 | 44.8k | new_manifest_file_size, db_session_id_.c_str()); |
6526 | 44.8k | } |
6527 | 83.3k | } |
6528 | | |
6529 | 83.3k | if (first_writer.edit_list.front()->IsColumnFamilyDrop()) { |
6530 | 15.6k | TEST_SYNC_POINT("VersionSet::LogAndApply::ColumnFamilyDrop:0"); |
6531 | 15.6k | TEST_SYNC_POINT("VersionSet::LogAndApply::ColumnFamilyDrop:1"); |
6532 | 15.6k | TEST_SYNC_POINT("VersionSet::LogAndApply::ColumnFamilyDrop:2"); |
6533 | 15.6k | } |
6534 | | |
6535 | 83.3k | LogFlush(db_options_->info_log); |
6536 | 83.3k | TEST_SYNC_POINT("VersionSet::LogAndApply:WriteManifestDone"); |
6537 | 83.3k | mu->Lock(); |
6538 | 83.3k | } |
6539 | | |
6540 | 83.3k | if (s.ok()) { |
6541 | | // Apply WAL edits, DB mutex must be held. |
6542 | 210k | for (auto& e : batch_edits) { |
6543 | 210k | if (e->IsWalAddition()) { |
6544 | 0 | s = wals_.AddWals(e->GetWalAdditions()); |
6545 | 210k | } else if (e->IsWalDeletion()) { |
6546 | 0 | s = wals_.DeleteWalsBefore(e->GetWalDeletion().GetLogNumber()); |
6547 | 0 | } |
6548 | 210k | if (!s.ok()) { |
6549 | 0 | break; |
6550 | 0 | } |
6551 | 210k | } |
6552 | 83.3k | } |
6553 | | |
6554 | 83.3k | if (!io_s.ok()) { |
6555 | 0 | if (io_status_.ok()) { |
6556 | 0 | io_status_ = io_s; |
6557 | 0 | if (error_handler_) { |
6558 | 0 | error_handler_->AddFilesToQuarantine( |
6559 | 0 | files_to_quarantine_if_commit_fail); |
6560 | 0 | } |
6561 | 0 | } |
6562 | 83.3k | } else if (!io_status_.ok()) { |
6563 | 0 | io_status_ = io_s; |
6564 | 0 | if (error_handler_) { |
6565 | 0 | error_handler_->ClearFilesToQuarantine(); |
6566 | 0 | } |
6567 | 0 | } |
6568 | | |
6569 | | // Append the old manifest file to the obsolete_manifest_ list to be deleted |
6570 | | // by PurgeObsoleteFiles later. |
6571 | 83.3k | if (s.ok() && new_descriptor_log) { |
6572 | 44.8k | descriptor_log_ = std::move(new_desc_log_ptr); |
6573 | 44.8k | obsolete_manifests_.emplace_back( |
6574 | 44.8k | DescriptorFileName("", manifest_file_number_)); |
6575 | 44.8k | last_compacted_manifest_file_size_ = new_manifest_file_size; |
6576 | 44.8k | TuneMaxManifestFileSize(); |
6577 | 44.8k | } |
6578 | | |
6579 | | // Install the new versions |
6580 | 83.3k | if (s.ok()) { |
6581 | 83.3k | if (first_writer.edit_list.front()->IsColumnFamilyAdd()) { |
6582 | 15.6k | assert(batch_edits.size() == 1); |
6583 | 15.6k | assert(new_cf_options != nullptr); |
6584 | 15.6k | assert(max_last_sequence == descriptor_last_sequence_); |
6585 | 15.6k | CreateColumnFamily(*new_cf_options, read_options, |
6586 | 15.6k | first_writer.edit_list.front(), |
6587 | 15.6k | /*read_only=*/false, new_cf_options_are_sanitized); |
6588 | 67.6k | } else if (first_writer.edit_list.front()->IsColumnFamilyDrop()) { |
6589 | 15.6k | assert(batch_edits.size() == 1); |
6590 | 15.6k | assert(max_last_sequence == descriptor_last_sequence_); |
6591 | 15.6k | first_writer.cfd->SetDropped(); |
6592 | 15.6k | first_writer.cfd->UnrefAndTryDelete(); |
6593 | 51.9k | } else { |
6594 | | // Each version in versions corresponds to a column family. |
6595 | | // For each column family, update its log number indicating that logs |
6596 | | // with number smaller than this should be ignored. |
6597 | 51.9k | uint64_t last_min_log_number_to_keep = 0; |
6598 | 179k | for (const auto& e : batch_edits) { |
6599 | 179k | ColumnFamilyData* cfd = nullptr; |
6600 | 179k | if (!e->IsColumnFamilyManipulation()) { |
6601 | 179k | cfd = column_family_set_->GetColumnFamily(e->GetColumnFamily()); |
6602 | | // e would not have been added to batch_edits if its corresponding |
6603 | | // column family is dropped. |
6604 | 179k | assert(cfd); |
6605 | 179k | } |
6606 | 179k | if (cfd) { |
6607 | 179k | if (e->HasLogNumber() && e->GetLogNumber() > cfd->GetLogNumber()) { |
6608 | 54.2k | cfd->SetLogNumber(e->GetLogNumber()); |
6609 | 54.2k | } |
6610 | 179k | if (e->HasFullHistoryTsLow()) { |
6611 | 0 | cfd->SetFullHistoryTsLow(e->GetFullHistoryTsLow()); |
6612 | 0 | } |
6613 | 179k | } |
6614 | 179k | if (e->HasMinLogNumberToKeep()) { |
6615 | 38.5k | last_min_log_number_to_keep = |
6616 | 38.5k | std::max(last_min_log_number_to_keep, e->GetMinLogNumberToKeep()); |
6617 | 38.5k | } |
6618 | 179k | } |
6619 | | |
6620 | 51.9k | if (last_min_log_number_to_keep != 0) { |
6621 | 38.5k | MarkMinLogNumberToKeep(last_min_log_number_to_keep); |
6622 | 38.5k | } |
6623 | | |
6624 | 119k | for (int i = 0; i < static_cast<int>(versions.size()); ++i) { |
6625 | 67.6k | ColumnFamilyData* cfd = versions[i]->cfd_; |
6626 | 67.6k | AppendVersion(cfd, versions[i]); |
6627 | 67.6k | } |
6628 | 51.9k | } |
6629 | 83.3k | if (!skip_manifest_write) { |
6630 | 210k | for (const auto* edit : batch_edits) { |
6631 | 210k | assert(edit != nullptr); |
6632 | 210k | if (edit->HasCommittedOptionsFileNumber()) { |
6633 | 0 | ApplyCommittedOptionsFileNumber( |
6634 | 0 | edit->GetCommittedOptionsFileNumber()); |
6635 | 0 | } |
6636 | 210k | } |
6637 | 83.3k | assert(max_last_sequence >= descriptor_last_sequence_); |
6638 | 83.3k | descriptor_last_sequence_ = max_last_sequence; |
6639 | 83.3k | manifest_file_number_ = pending_manifest_file_number_; |
6640 | 83.3k | manifest_file_size_ = new_manifest_file_size; |
6641 | 83.3k | manifest_last_valid_record_end_ = new_manifest_file_size; |
6642 | 83.3k | manifest_last_valid_record_end_file_number_ = manifest_file_number_; |
6643 | 83.3k | prev_log_number_ = first_writer.edit_list.front()->GetPrevLogNumber(); |
6644 | 83.3k | } |
6645 | 83.3k | } else { |
6646 | 0 | std::string version_edits; |
6647 | 0 | for (auto& e : batch_edits) { |
6648 | 0 | version_edits += ("\n" + e->DebugString(true)); |
6649 | 0 | } |
6650 | 0 | ROCKS_LOG_ERROR(db_options_->info_log, |
6651 | 0 | "Error in committing version edit to MANIFEST: %s", |
6652 | 0 | version_edits.c_str()); |
6653 | 0 | for (auto v : versions) { |
6654 | 0 | delete v; |
6655 | 0 | } |
6656 | | // If manifest append failed for whatever reason, the file could be |
6657 | | // corrupted. So we need to force the next version update to start a |
6658 | | // new manifest file. |
6659 | 0 | descriptor_log_.reset(); |
6660 | 0 | new_desc_log_ptr.reset(); |
6661 | | // If manifest operations failed, then we know the CURRENT file still |
6662 | | // points to the original MANIFEST. Therefore, we can safely delete the |
6663 | | // new MANIFEST. |
6664 | | // If manifest operations succeeded, and we are here, then it is possible |
6665 | | // that renaming tmp file to CURRENT failed. |
6666 | | // |
6667 | | // On local POSIX-compliant FS, the CURRENT must point to the original |
6668 | | // MANIFEST. We can delete the new MANIFEST for simplicity, but we can |
6669 | | // also keep it. Future recovery will ignore this MANIFEST. It's also ok |
6670 | | // for the process not to crash and continue using the db. Any future |
6671 | | // LogAndApply() call will switch to a new MANIFEST and update CURRENT, |
6672 | | // still ignoring this one. |
6673 | | // |
6674 | | // On non-local FS, it is |
6675 | | // possible that the rename operation succeeded on the server (remote) |
6676 | | // side, but the client somehow returns a non-ok status to RocksDB. Note |
6677 | | // that this does not violate atomicity. Should we delete the new MANIFEST |
6678 | | // successfully, a subsequent recovery attempt will likely see the CURRENT |
6679 | | // pointing to the new MANIFEST, thus fail. We will not be able to open |
6680 | | // the DB again. Therefore, if manifest operations succeed, we should keep |
6681 | | // the the new MANIFEST. If the process proceeds, any future LogAndApply() |
6682 | | // call will switch to a new MANIFEST and update CURRENT. If user tries to |
6683 | | // re-open the DB, |
6684 | | // a) CURRENT points to the new MANIFEST, and the new MANIFEST is present. |
6685 | | // b) CURRENT points to the original MANIFEST, and the original MANIFEST |
6686 | | // also exists. |
6687 | 0 | if (!manifest_io_status.ok() && new_descriptor_log) { |
6688 | 0 | ROCKS_LOG_INFO(db_options_->info_log, |
6689 | 0 | "Deleting manifest %" PRIu64 " current manifest %" PRIu64 |
6690 | 0 | "\n", |
6691 | 0 | pending_manifest_file_number_, manifest_file_number_); |
6692 | 0 | Status manifest_del_status = env_->DeleteFile( |
6693 | 0 | DescriptorFileName(dbname_, pending_manifest_file_number_)); |
6694 | 0 | if (!manifest_del_status.ok()) { |
6695 | 0 | ROCKS_LOG_WARN(db_options_->info_log, |
6696 | 0 | "Failed to delete manifest %" PRIu64 ": %s", |
6697 | 0 | pending_manifest_file_number_, |
6698 | 0 | manifest_del_status.ToString().c_str()); |
6699 | 0 | } |
6700 | 0 | } |
6701 | 0 | } |
6702 | | |
6703 | 83.3k | pending_manifest_file_number_ = 0; |
6704 | | |
6705 | | #ifndef NDEBUG |
6706 | | // This is here kind of awkwardly because there's no other consistency |
6707 | | // checks on `VersionSet`'s updates for the new `Version`s. We might want |
6708 | | // to move it to a dedicated function, or remove it if we gain enough |
6709 | | // confidence in `descriptor_last_sequence_`. |
6710 | | if (s.ok()) { |
6711 | | for (const auto* v : versions) { |
6712 | | const auto* vstorage = v->storage_info(); |
6713 | | for (int level = 0; level < vstorage->num_levels(); ++level) { |
6714 | | for (const auto& file : vstorage->LevelFiles(level)) { |
6715 | | assert(file->fd.largest_seqno <= descriptor_last_sequence_); |
6716 | | } |
6717 | | } |
6718 | | } |
6719 | | } |
6720 | | #endif // NDEBUG |
6721 | | |
6722 | 83.3k | finish_batch(s); |
6723 | 83.3k | return s; |
6724 | 83.3k | } |
6725 | | |
6726 | 0 | void VersionSet::WakeUpWaitingManifestWriters() { |
6727 | | // wake up all the waiting writers |
6728 | | // Notify new head of manifest write queue. |
6729 | 0 | if (!manifest_writers_.empty()) { |
6730 | 0 | manifest_writers_.front()->cv.Signal(); |
6731 | 0 | } |
6732 | 0 | } |
6733 | | |
6734 | | // 'datas' is grammatically incorrect. We still use this notation to indicate |
6735 | | // that this variable represents a collection of column_family_data. |
6736 | | Status VersionSet::LogAndApply( |
6737 | | const autovector<ColumnFamilyData*>& column_family_datas, |
6738 | | const ReadOptions& read_options, const WriteOptions& write_options, |
6739 | | const autovector<autovector<VersionEdit*>>& edit_lists, |
6740 | | InstrumentedMutex* mu, FSDirectory* dir_contains_current_file, |
6741 | | bool new_descriptor_log, const ColumnFamilyOptions* new_cf_options, |
6742 | | const std::vector<std::function<void(const Status&)>>& manifest_wcbs, |
6743 | 67.6k | const std::function<Status()>& pre_cb, int max_file_opening_threads) { |
6744 | 67.6k | return LogAndApplyImpl(column_family_datas, read_options, write_options, |
6745 | 67.6k | edit_lists, mu, dir_contains_current_file, |
6746 | 67.6k | new_descriptor_log, new_cf_options, |
6747 | 67.6k | /*new_cf_options_are_sanitized=*/false, manifest_wcbs, |
6748 | 67.6k | pre_cb, max_file_opening_threads); |
6749 | 67.6k | } |
6750 | | |
6751 | | Status VersionSet::LogAndApplyColumnFamilyAddWithSanitizedOptions( |
6752 | | const ReadOptions& read_options, const WriteOptions& write_options, |
6753 | | VersionEdit* edit, InstrumentedMutex* mu, |
6754 | | FSDirectory* dir_contains_current_file, |
6755 | 15.6k | const ColumnFamilyOptions& sanitized_options) { |
6756 | 15.6k | assert(edit != nullptr); |
6757 | 15.6k | assert(edit->IsColumnFamilyAdd()); |
6758 | 15.6k | autovector<ColumnFamilyData*> cfds{nullptr}; |
6759 | 15.6k | autovector<VersionEdit*> edit_list{edit}; |
6760 | 15.6k | autovector<autovector<VersionEdit*>> edit_lists{edit_list}; |
6761 | 15.6k | return LogAndApplyImpl(cfds, read_options, write_options, edit_lists, mu, |
6762 | 15.6k | dir_contains_current_file, |
6763 | 15.6k | /*new_descriptor_log=*/false, &sanitized_options, |
6764 | 15.6k | /*new_cf_options_are_sanitized=*/true, |
6765 | 15.6k | /*manifest_wcbs=*/{}, /*pre_cb=*/{}, |
6766 | 15.6k | /*max_file_opening_threads=*/1); |
6767 | 15.6k | } |
6768 | | |
6769 | | Status VersionSet::LogAndApplyImpl( |
6770 | | const autovector<ColumnFamilyData*>& column_family_datas, |
6771 | | const ReadOptions& read_options, const WriteOptions& write_options, |
6772 | | const autovector<autovector<VersionEdit*>>& edit_lists, |
6773 | | InstrumentedMutex* mu, FSDirectory* dir_contains_current_file, |
6774 | | bool new_descriptor_log, const ColumnFamilyOptions* new_cf_options, |
6775 | | bool new_cf_options_are_sanitized, |
6776 | | const std::vector<std::function<void(const Status&)>>& manifest_wcbs, |
6777 | 83.3k | const std::function<Status()>& pre_cb, int max_file_opening_threads) { |
6778 | 83.3k | mu->AssertHeld(); |
6779 | 83.3k | int num_edits = 0; |
6780 | 98.9k | for (const auto& elist : edit_lists) { |
6781 | 98.9k | num_edits += static_cast<int>(elist.size()); |
6782 | 98.9k | } |
6783 | 83.3k | if (num_edits == 0) { |
6784 | 0 | return Status::OK(); |
6785 | 83.3k | } else if (num_edits > 1) { |
6786 | | #ifndef NDEBUG |
6787 | | for (const auto& edit_list : edit_lists) { |
6788 | | for (const auto& edit : edit_list) { |
6789 | | assert(!edit->IsColumnFamilyManipulation()); |
6790 | | assert(!edit->IsNoManifestWriteDummy()); |
6791 | | } |
6792 | | } |
6793 | | #endif /* ! NDEBUG */ |
6794 | 38.5k | } |
6795 | | |
6796 | 83.3k | int num_cfds = static_cast<int>(column_family_datas.size()); |
6797 | 83.3k | if (num_cfds == 1 && column_family_datas[0] == nullptr) { |
6798 | 15.6k | assert(edit_lists.size() == 1 && edit_lists[0].size() == 1); |
6799 | 15.6k | assert(edit_lists[0][0]->IsColumnFamilyAdd()); |
6800 | 15.6k | assert(new_cf_options != nullptr); |
6801 | 15.6k | } |
6802 | 83.3k | std::deque<ManifestWriter> writers; |
6803 | 83.3k | if (num_cfds > 0) { |
6804 | 83.3k | assert(static_cast<size_t>(num_cfds) == edit_lists.size()); |
6805 | 83.3k | } |
6806 | 182k | for (int i = 0; i < num_cfds; ++i) { |
6807 | 98.9k | const auto wcb = |
6808 | 98.9k | manifest_wcbs.empty() ? [](const Status&) {} : manifest_wcbs[i]; |
6809 | 98.9k | writers.emplace_back(mu, column_family_datas[i], edit_lists[i], wcb, |
6810 | 98.9k | max_file_opening_threads); |
6811 | 98.9k | manifest_writers_.push_back(&writers[i]); |
6812 | 98.9k | } |
6813 | 83.3k | assert(!writers.empty()); |
6814 | 83.3k | ManifestWriter& first_writer = writers.front(); |
6815 | 83.3k | TEST_SYNC_POINT_CALLBACK("VersionSet::LogAndApply:BeforeWriterWaiting", |
6816 | 83.3k | nullptr); |
6817 | 84.1k | while (!first_writer.done && &first_writer != manifest_writers_.front()) { |
6818 | 898 | first_writer.cv.Wait(); |
6819 | 898 | } |
6820 | 83.3k | if (first_writer.done) { |
6821 | | // All non-CF-manipulation operations can be grouped together and |
6822 | | // committed to MANIFEST. They should all have finished. The status code |
6823 | | // is stored in the first manifest writer. |
6824 | | #ifndef NDEBUG |
6825 | | for (const auto& writer : writers) { |
6826 | | assert(writer.done); |
6827 | | } |
6828 | | TEST_SYNC_POINT_CALLBACK("VersionSet::LogAndApply:WakeUpAndDone", mu); |
6829 | | #endif /* !NDEBUG */ |
6830 | | // FIXME: One MANIFEST write failure can cause all writes to SetBGError, |
6831 | | // should only SetBGError once. |
6832 | 0 | return first_writer.status; |
6833 | 0 | } |
6834 | 83.3k | TEST_SYNC_POINT_CALLBACK("VersionSet::LogAndApply:WakeUpAndNotDone", mu); |
6835 | | |
6836 | 83.3k | int num_undropped_cfds = 0; |
6837 | 98.9k | for (auto cfd : column_family_datas) { |
6838 | | // if cfd == nullptr, it is a column family add. |
6839 | 98.9k | if (cfd == nullptr || !cfd->IsDropped()) { |
6840 | 98.9k | ++num_undropped_cfds; |
6841 | 98.9k | } |
6842 | 98.9k | } |
6843 | 83.3k | Status s; |
6844 | 83.3k | if (0 == num_undropped_cfds) { |
6845 | 0 | s = Status::ColumnFamilyDropped(); |
6846 | 0 | } |
6847 | | // Call pre_cb once we know we have work to do and are scheduled as the |
6848 | | // exclusive manifest writer (and new Version appender) |
6849 | 83.3k | if (s.ok() && pre_cb) { |
6850 | 0 | s = pre_cb(); |
6851 | 0 | } |
6852 | 83.3k | if (!s.ok()) { |
6853 | | // Revert manifest_writers_ |
6854 | 0 | for (int i = 0; i != num_cfds; ++i) { |
6855 | 0 | manifest_writers_.pop_front(); |
6856 | 0 | } |
6857 | | // Notify new head of manifest write queue. |
6858 | 0 | if (!manifest_writers_.empty()) { |
6859 | 0 | manifest_writers_.front()->cv.Signal(); |
6860 | 0 | } |
6861 | 0 | return s; |
6862 | 83.3k | } else { |
6863 | 83.3k | return ProcessManifestWrites(writers, mu, dir_contains_current_file, |
6864 | 83.3k | new_descriptor_log, new_cf_options, |
6865 | 83.3k | new_cf_options_are_sanitized, read_options, |
6866 | 83.3k | write_options); |
6867 | 83.3k | } |
6868 | 83.3k | } |
6869 | | |
6870 | | void VersionSet::LogAndApplyCFHelper(VersionEdit* edit, |
6871 | 31.3k | SequenceNumber* max_last_sequence) { |
6872 | 31.3k | assert(max_last_sequence != nullptr); |
6873 | 31.3k | assert(edit->IsColumnFamilyManipulation()); |
6874 | 31.3k | edit->SetNextFile(next_file_number_.load()); |
6875 | 31.3k | assert(!edit->HasLastSequence()); |
6876 | 31.3k | edit->SetLastSequence(*max_last_sequence); |
6877 | 31.3k | if (edit->IsColumnFamilyDrop()) { |
6878 | | // if we drop column family, we have to make sure to save max column |
6879 | | // family, so that we don't reuse existing ID |
6880 | 15.6k | edit->SetMaxColumnFamily(column_family_set_->GetMaxColumnFamily()); |
6881 | 15.6k | } |
6882 | 31.3k | } |
6883 | | |
6884 | | Status VersionSet::LogAndApplyHelper(ColumnFamilyData* cfd, |
6885 | | VersionBuilder* builder, VersionEdit* edit, |
6886 | | SequenceNumber* max_last_sequence, |
6887 | 179k | InstrumentedMutex* mu) { |
6888 | 179k | #ifdef NDEBUG |
6889 | 179k | (void)cfd; |
6890 | 179k | #endif |
6891 | 179k | mu->AssertHeld(); |
6892 | 179k | assert(!edit->IsColumnFamilyManipulation()); |
6893 | 179k | assert(max_last_sequence != nullptr); |
6894 | | |
6895 | 179k | if (edit->HasLogNumber()) { |
6896 | 54.2k | assert(edit->GetLogNumber() >= cfd->GetLogNumber()); |
6897 | 54.2k | assert(edit->GetLogNumber() < next_file_number_.load()); |
6898 | 54.2k | } |
6899 | | |
6900 | 179k | if (!edit->HasPrevLogNumber()) { |
6901 | 177k | edit->SetPrevLogNumber(prev_log_number_); |
6902 | 177k | } |
6903 | 179k | edit->SetNextFile(next_file_number_.load()); |
6904 | 179k | if (edit->HasLastSequence() && edit->GetLastSequence() > *max_last_sequence) { |
6905 | 17.9k | *max_last_sequence = edit->GetLastSequence(); |
6906 | 161k | } else { |
6907 | 161k | edit->SetLastSequence(*max_last_sequence); |
6908 | 161k | } |
6909 | | |
6910 | | // WAL and OPTIONS protocol edits update DB-wide metadata without changing an |
6911 | | // LSM Version, so they do not need a VersionBuilder. |
6912 | 179k | assert(builder || edit->IsWalManipulation() || |
6913 | 179k | edit->IsOptionsFileManipulation()); |
6914 | 179k | return builder ? builder->Apply(edit) : Status::OK(); |
6915 | 179k | } |
6916 | | |
6917 | 83.3k | FileOptions VersionSet::GetFileOptionsForManifestWrite() const { |
6918 | 83.3k | FileOptions opt_file_opts = fs_->OptimizeForManifestWrite(file_options_); |
6919 | | // DB-configured temperature wins over the FS's default (when not kUnknown). |
6920 | 83.3k | if (file_options_.temperature != Temperature::kUnknown) { |
6921 | 0 | opt_file_opts.temperature = file_options_.temperature; |
6922 | 0 | } |
6923 | 83.3k | return opt_file_opts; |
6924 | 83.3k | } |
6925 | | |
6926 | | std::unique_ptr<log::Writer> VersionSet::CreateManifestWriter( |
6927 | | std::unique_ptr<FSWritableFile> file, const std::string& fname, |
6928 | | const FileOptions& opts, uint64_t preallocation_size, |
6929 | 44.8k | uint64_t initial_file_size) const { |
6930 | 44.8k | file->SetPreallocationBlockSize(preallocation_size); |
6931 | 44.8k | FileTypeSet tmp_set = db_options_->checksum_handoff_file_types; |
6932 | 44.8k | auto file_writer = std::make_unique<WritableFileWriter>( |
6933 | 44.8k | std::move(file), fname, opts, clock_, io_tracer_, |
6934 | 44.8k | /*stats=*/nullptr, Histograms::HISTOGRAM_ENUM_MAX, db_options_->listeners, |
6935 | 44.8k | /*file_checksum_gen_factory=*/nullptr, |
6936 | 44.8k | tmp_set.Contains(FileType::kDescriptorFile), |
6937 | 44.8k | tmp_set.Contains(FileType::kDescriptorFile), initial_file_size); |
6938 | 44.8k | const size_t block_offset = initial_file_size % log::kBlockSize; |
6939 | 44.8k | return std::make_unique<log::Writer>( |
6940 | 44.8k | std::move(file_writer), /*log_number=*/0, /*recycle_log_files=*/false, |
6941 | 44.8k | /*manual_flush=*/false, kNoCompression, |
6942 | 44.8k | /*track_and_verify_wals=*/false, block_offset); |
6943 | 44.8k | } |
6944 | | |
6945 | 0 | Status VersionSet::GetManifestAppendBoundary(uint64_t* manifest_size) const { |
6946 | 0 | assert(manifest_size != nullptr); |
6947 | 0 | if (manifest_last_valid_record_end_file_number_ != manifest_file_number_) { |
6948 | 0 | return Status::TryAgain( |
6949 | 0 | "Current MANIFEST changed while determining its append boundary"); |
6950 | 0 | } |
6951 | 0 | if (manifest_last_valid_record_end_ == 0 || |
6952 | 0 | manifest_last_valid_record_end_ > manifest_file_size_) { |
6953 | 0 | return Status::Corruption("Invalid MANIFEST append boundary"); |
6954 | 0 | } |
6955 | 0 | *manifest_size = manifest_last_valid_record_end_; |
6956 | 0 | return Status::OK(); |
6957 | 0 | } |
6958 | | |
6959 | 0 | Status VersionSet::ReopenManifestForAppend(const std::string& manifest_path) { |
6960 | 0 | assert(db_options_->reuse_manifest_on_open); |
6961 | 0 | assert(manifest_recovery_last_valid_record_end_ > 0); |
6962 | 0 | assert(manifest_last_valid_record_end_file_number_ == manifest_file_number_); |
6963 | | |
6964 | | // Disabled under best_efforts_recovery: that mode rebuilds the |
6965 | | // MANIFEST + CURRENT from scratch via the side-effect of |
6966 | | // LogAndApplyForRecovery emitting an edit. Reusing the (possibly |
6967 | | // stale) prior MANIFEST contradicts the salvage contract. |
6968 | 0 | if (db_options_->UsesBestEffortsRecoveryPath()) { |
6969 | 0 | return Status::OK(); |
6970 | 0 | } |
6971 | | |
6972 | 0 | auto force_new_manifest_on_open = [&]() { |
6973 | 0 | force_new_manifest_on_open_ = true; |
6974 | 0 | }; |
6975 | |
|
6976 | 0 | FileOptions opt_file_opts = GetFileOptionsForManifestWrite(); |
6977 | | |
6978 | | // Bail if the on-disk size diverges from what Recover's Reader |
6979 | | // consumed -- likely a torn tail from a prior crash; mid-block append |
6980 | | // would mis-frame the record stream. |
6981 | 0 | uint64_t physical_size = 0; |
6982 | 0 | IOStatus stat_s = fs_->GetFileSize(manifest_path, IOOptions(), &physical_size, |
6983 | 0 | /*dbg=*/nullptr); |
6984 | 0 | if (!stat_s.ok() || |
6985 | 0 | physical_size != manifest_recovery_last_valid_record_end_) { |
6986 | 0 | ROCKS_LOG_WARN(db_options_->info_log, |
6987 | 0 | "reuse_manifest_on_open: physical size %" PRIu64 |
6988 | 0 | " != last valid record end %" PRIu64 |
6989 | 0 | " (tail corruption?); creating fresh MANIFEST during open", |
6990 | 0 | physical_size, manifest_recovery_last_valid_record_end_); |
6991 | 0 | force_new_manifest_on_open(); |
6992 | 0 | return Status::OK(); |
6993 | 0 | } |
6994 | | |
6995 | | // Direct-writes path is hard to reason about for an already-populated |
6996 | | // file (alignment + tail-pad). POSIX's OptimizeForManifestWrite forces |
6997 | | // it off, but custom FileSystems may not. |
6998 | 0 | if (opt_file_opts.use_direct_writes) { |
6999 | 0 | ROCKS_LOG_WARN(db_options_->info_log, |
7000 | 0 | "reuse_manifest_on_open: direct writes enabled; " |
7001 | 0 | "creating fresh MANIFEST during open"); |
7002 | 0 | force_new_manifest_on_open(); |
7003 | 0 | return Status::OK(); |
7004 | 0 | } |
7005 | | |
7006 | 0 | std::unique_ptr<FSWritableFile> manifest_file; |
7007 | 0 | IOStatus io_s = fs_->ReopenWritableFile(manifest_path, opt_file_opts, |
7008 | 0 | &manifest_file, /*dbg=*/nullptr); |
7009 | 0 | if (!io_s.ok()) { |
7010 | 0 | ROCKS_LOG_WARN(db_options_->info_log, |
7011 | 0 | "Failed to reopen MANIFEST for append: %s", |
7012 | 0 | io_s.ToString().c_str()); |
7013 | 0 | force_new_manifest_on_open(); |
7014 | 0 | return Status::OK(); |
7015 | 0 | } |
7016 | | |
7017 | 0 | const uint64_t reopened_size = |
7018 | 0 | manifest_file->GetFileSize(opt_file_opts.io_options, /*dbg=*/nullptr); |
7019 | 0 | if (reopened_size != manifest_recovery_last_valid_record_end_) { |
7020 | 0 | ROCKS_LOG_WARN(db_options_->info_log, |
7021 | 0 | "reuse_manifest_on_open: reopened handle size %" PRIu64 |
7022 | 0 | " != last valid record end %" PRIu64 |
7023 | 0 | "; creating fresh MANIFEST during open", |
7024 | 0 | reopened_size, manifest_recovery_last_valid_record_end_); |
7025 | 0 | force_new_manifest_on_open(); |
7026 | 0 | return Status::OK(); |
7027 | 0 | } |
7028 | | |
7029 | 0 | descriptor_log_ = CreateManifestWriter( |
7030 | 0 | std::move(manifest_file), manifest_path, opt_file_opts, |
7031 | 0 | manifest_preallocation_size_, reopened_size); |
7032 | |
|
7033 | 0 | ROCKS_LOG_INFO( |
7034 | 0 | db_options_->info_log, |
7035 | 0 | "Reusing existing MANIFEST file: %s (valid data size: %" PRIu64 ")", |
7036 | 0 | manifest_path.c_str(), manifest_recovery_last_valid_record_end_); |
7037 | 0 | TEST_SYNC_POINT("VersionSet::ReopenManifestForAppend:Reopened"); |
7038 | 0 | return Status::OK(); |
7039 | 0 | } |
7040 | | |
7041 | | Status VersionSet::AppendColumnFamilyDropsToManifest( |
7042 | | const std::string& manifest_path, uint64_t manifest_size, |
7043 | | const std::vector<uint32_t>& cf_ids, const WriteOptions& write_options, |
7044 | | uint64_t manifest_preallocation_size, |
7045 | 0 | uint64_t committed_options_file_number) { |
7046 | 0 | if (cf_ids.empty() && committed_options_file_number == 0) { |
7047 | 0 | return Status::OK(); |
7048 | 0 | } |
7049 | 0 | if (committed_options_file_number == std::numeric_limits<uint64_t>::max()) { |
7050 | 0 | return Status::InvalidArgument("OPTIONS file number is too large"); |
7051 | 0 | } |
7052 | 0 | const uint64_t next_options_file_number = |
7053 | 0 | committed_options_file_number == 0 ? 0 |
7054 | 0 | : committed_options_file_number + 1; |
7055 | | |
7056 | | // Rewrite the checkpoint MANIFEST via a bounded-memory buffered copy into a |
7057 | | // .tmp file (append drops, sync, close), then atomically rename over the |
7058 | | // original. Avoids depending on FileSystem::ReopenWritableFile (optional, |
7059 | | // unsupported on some backends) and avoids reading the whole MANIFEST into |
7060 | | // memory (which can reach ~1GB depending on configuration). |
7061 | 0 | const std::string tmp_path = manifest_path + ".tmp"; |
7062 | | |
7063 | | // Best-effort cleanup of any stale .tmp left by a prior aborted run. |
7064 | 0 | fs_->DeleteFile(tmp_path, IOOptions(), /*dbg=*/nullptr) |
7065 | 0 | .PermitUncheckedError(); |
7066 | |
|
7067 | 0 | std::unique_ptr<FSSequentialFile> src_file; |
7068 | 0 | { |
7069 | 0 | IOStatus io_s = fs_->NewSequentialFile(manifest_path, FileOptions(), |
7070 | 0 | &src_file, /*dbg=*/nullptr); |
7071 | 0 | if (!io_s.ok()) { |
7072 | 0 | return io_s; |
7073 | 0 | } |
7074 | 0 | } |
7075 | | |
7076 | 0 | FileOptions opt_file_opts = GetFileOptionsForManifestWrite(); |
7077 | | // Buffered writes only: direct writes would need alignment / tail-pad |
7078 | | // reasoning that isn't worth the complexity for a checkpoint MANIFEST. |
7079 | 0 | opt_file_opts.use_direct_writes = false; |
7080 | |
|
7081 | 0 | std::unique_ptr<FSWritableFile> dst_file; |
7082 | 0 | { |
7083 | 0 | IOStatus io_s = fs_->NewWritableFile(tmp_path, opt_file_opts, &dst_file, |
7084 | 0 | /*dbg=*/nullptr); |
7085 | 0 | if (!io_s.ok()) { |
7086 | 0 | return io_s; |
7087 | 0 | } |
7088 | 0 | } |
7089 | | |
7090 | | // Copy exactly manifest_size bytes through a fixed-size buffer. |
7091 | 0 | constexpr size_t kBufSize = 1 * 1024 * 1024; |
7092 | 0 | std::string buffer(kBufSize, '\0'); |
7093 | 0 | uint64_t remaining = manifest_size; |
7094 | 0 | while (remaining > 0) { |
7095 | 0 | Slice result; |
7096 | 0 | const size_t to_read = |
7097 | 0 | static_cast<size_t>(std::min<uint64_t>(kBufSize, remaining)); |
7098 | 0 | IOStatus read_s = src_file->Read(to_read, opt_file_opts.io_options, &result, |
7099 | 0 | buffer.data(), /*dbg=*/nullptr); |
7100 | 0 | if (!read_s.ok()) { |
7101 | 0 | return read_s; |
7102 | 0 | } |
7103 | 0 | if (result.empty()) { |
7104 | 0 | return Status::Corruption( |
7105 | 0 | "Unexpected EOF reading source checkpoint MANIFEST"); |
7106 | 0 | } |
7107 | 0 | IOStatus write_s = dst_file->Append(result, opt_file_opts.io_options, |
7108 | 0 | /*dbg=*/nullptr); |
7109 | 0 | if (!write_s.ok()) { |
7110 | 0 | return write_s; |
7111 | 0 | } |
7112 | 0 | remaining -= result.size(); |
7113 | 0 | } |
7114 | 0 | src_file.reset(); |
7115 | | |
7116 | | // Seeds initial_block_offset = manifest_size % kBlockSize so drop-record |
7117 | | // framing continues from where the copied contents left off, matching the |
7118 | | // position-based log reader. |
7119 | 0 | std::unique_ptr<log::Writer> writer = |
7120 | 0 | CreateManifestWriter(std::move(dst_file), tmp_path, opt_file_opts, |
7121 | 0 | manifest_preallocation_size, manifest_size); |
7122 | | |
7123 | | // Always attempt to Close the writer on any exit path, folding any Close |
7124 | | // error into the primary status. Guards against dropping the underlying |
7125 | | // FSWritableFile with buffered data / no filesystem close hook. |
7126 | 0 | Status s; |
7127 | 0 | auto close_writer = [&]() { |
7128 | 0 | if (writer == nullptr) { |
7129 | 0 | return; |
7130 | 0 | } |
7131 | 0 | Status close_s = writer->Close(write_options); |
7132 | 0 | if (s.ok() && !close_s.ok()) { |
7133 | 0 | s = close_s; |
7134 | 0 | } |
7135 | 0 | writer.reset(); |
7136 | 0 | }; |
7137 | |
|
7138 | 0 | for (size_t i = 0; i < cf_ids.size(); ++i) { |
7139 | 0 | assert(writer != nullptr); |
7140 | 0 | VersionEdit edit; |
7141 | 0 | edit.SetColumnFamily(cf_ids[i]); |
7142 | 0 | edit.DropColumnFamily(); |
7143 | 0 | if (i + 1 == cf_ids.size()) { |
7144 | 0 | if (committed_options_file_number != 0) { |
7145 | 0 | edit.SetCommittedOptionsFileNumber(committed_options_file_number); |
7146 | 0 | edit.SetNextFile(next_options_file_number); |
7147 | 0 | } |
7148 | 0 | } |
7149 | 0 | std::string record; |
7150 | 0 | if (!edit.EncodeTo(&record)) { |
7151 | 0 | s = Status::Corruption( |
7152 | 0 | "Unable to encode column family drop VersionEdit for checkpoint"); |
7153 | 0 | close_writer(); |
7154 | 0 | return s; |
7155 | 0 | } |
7156 | 0 | IOStatus add_s = writer->AddRecord(write_options, record); |
7157 | 0 | if (!add_s.ok()) { |
7158 | 0 | s = add_s; |
7159 | 0 | close_writer(); |
7160 | 0 | return s; |
7161 | 0 | } |
7162 | 0 | } |
7163 | | |
7164 | 0 | if (cf_ids.empty()) { |
7165 | 0 | assert(writer != nullptr); |
7166 | 0 | VersionEdit edit; |
7167 | 0 | if (committed_options_file_number != 0) { |
7168 | 0 | edit.SetCommittedOptionsFileNumber(committed_options_file_number); |
7169 | 0 | edit.SetNextFile(next_options_file_number); |
7170 | 0 | } |
7171 | 0 | std::string record; |
7172 | 0 | if (!edit.EncodeTo(&record)) { |
7173 | 0 | s = Status::Corruption("Unable to encode committed OPTIONS VersionEdit"); |
7174 | 0 | close_writer(); |
7175 | 0 | return s; |
7176 | 0 | } |
7177 | 0 | IOStatus add_s = writer->AddRecord(write_options, record); |
7178 | 0 | if (!add_s.ok()) { |
7179 | 0 | s = add_s; |
7180 | 0 | close_writer(); |
7181 | 0 | return s; |
7182 | 0 | } |
7183 | 0 | } |
7184 | | |
7185 | 0 | IOStatus sync_s = SyncManifest(db_options_, write_options, writer->file()); |
7186 | 0 | if (!sync_s.ok()) { |
7187 | 0 | s = sync_s; |
7188 | 0 | close_writer(); |
7189 | 0 | return s; |
7190 | 0 | } |
7191 | 0 | close_writer(); |
7192 | 0 | if (!s.ok()) { |
7193 | 0 | return s; |
7194 | 0 | } |
7195 | | |
7196 | | // Replace the original with the rewritten .tmp. |
7197 | 0 | IOStatus rename_s = |
7198 | 0 | fs_->RenameFile(tmp_path, manifest_path, IOOptions(), /*dbg=*/nullptr); |
7199 | 0 | if (!rename_s.ok()) { |
7200 | 0 | return rename_s; |
7201 | 0 | } |
7202 | 0 | const size_t slash = manifest_path.find_last_of("/\\"); |
7203 | 0 | const std::string manifest_dir = |
7204 | 0 | slash == std::string::npos ? "." : manifest_path.substr(0, slash); |
7205 | 0 | std::unique_ptr<FSDirectory> dir; |
7206 | 0 | IOStatus dir_s = fs_->NewDirectory(manifest_dir, IOOptions(), &dir, nullptr); |
7207 | 0 | if (!dir_s.ok()) { |
7208 | 0 | return dir_s; |
7209 | 0 | } |
7210 | 0 | assert(dir != nullptr); |
7211 | 0 | dir_s = dir->FsyncWithDirOptions(IOOptions(), nullptr, |
7212 | 0 | DirFsyncOptions(manifest_path)); |
7213 | 0 | if (!dir_s.ok()) { |
7214 | 0 | return dir_s; |
7215 | 0 | } |
7216 | 0 | return Status::OK(); |
7217 | 0 | } |
7218 | | |
7219 | | Status VersionSet::Recover( |
7220 | | const std::vector<ColumnFamilyDescriptor>& column_families, bool read_only, |
7221 | | std::string* db_id, bool no_error_if_files_missing, bool is_retry, |
7222 | 44.8k | Status* log_status) { |
7223 | 44.8k | const ReadOptions read_options(Env::IOActivity::kDBOpen); |
7224 | | // Read "CURRENT" file, which contains a pointer to the current manifest |
7225 | | // file |
7226 | 44.8k | std::string manifest_path; |
7227 | 44.8k | Status s = GetCurrentManifestPath(dbname_, fs_.get(), is_retry, |
7228 | 44.8k | &manifest_path, &manifest_file_number_); |
7229 | 44.8k | if (!s.ok()) { |
7230 | 0 | return s; |
7231 | 0 | } |
7232 | | |
7233 | 44.8k | ROCKS_LOG_INFO(db_options_->info_log, "Recovering from manifest file: %s\n", |
7234 | 44.8k | manifest_path.c_str()); |
7235 | | |
7236 | 44.8k | std::unique_ptr<SequentialFileReader> manifest_file_reader; |
7237 | 44.8k | { |
7238 | 44.8k | std::unique_ptr<FSSequentialFile> manifest_file; |
7239 | 44.8k | s = fs_->NewSequentialFile(manifest_path, |
7240 | 44.8k | fs_->OptimizeForManifestRead(file_options_), |
7241 | 44.8k | &manifest_file, nullptr); |
7242 | 44.8k | if (!s.ok()) { |
7243 | 0 | return s; |
7244 | 0 | } |
7245 | 44.8k | manifest_file_reader.reset(new SequentialFileReader( |
7246 | 44.8k | std::move(manifest_file), manifest_path, |
7247 | 44.8k | db_options_->log_readahead_size, io_tracer_, db_options_->listeners, |
7248 | 44.8k | /*rate_limiter=*/nullptr, is_retry)); |
7249 | 44.8k | } |
7250 | 0 | TEST_SYNC_POINT("VersionSet::Recover:StartManifestRead"); |
7251 | | |
7252 | 44.8k | uint64_t current_manifest_file_size = 0; |
7253 | 44.8k | uint64_t log_number = 0; |
7254 | 44.8k | { |
7255 | 44.8k | force_new_manifest_on_open_ = false; |
7256 | 44.8k | VersionSet::LogReporter reporter; |
7257 | 44.8k | Status log_read_status; |
7258 | 44.8k | reporter.status = &log_read_status; |
7259 | 44.8k | log::Reader reader(nullptr, std::move(manifest_file_reader), &reporter, |
7260 | 44.8k | true /* checksum */, 0 /* log_number */); |
7261 | 44.8k | VersionEditHandler handler( |
7262 | 44.8k | read_only, column_families, const_cast<VersionSet*>(this), |
7263 | 44.8k | /*track_found_and_missing_files=*/false, no_error_if_files_missing, |
7264 | 44.8k | io_tracer_, read_options, /*allow_incomplete_valid_version=*/false, |
7265 | 44.8k | EpochNumberRequirement::kMightMissing, |
7266 | 44.8k | /*skip_load_table_files=*/db_options_->open_files_async); |
7267 | 44.8k | handler.Iterate(reader, &log_read_status); |
7268 | 44.8k | s = handler.status(); |
7269 | 44.8k | if (s.ok()) { |
7270 | 44.8k | log_number = handler.GetVersionEditParams().GetLogNumber(); |
7271 | 44.8k | current_manifest_file_size = reader.GetReadOffset(); |
7272 | 44.8k | assert(current_manifest_file_size != 0); |
7273 | 44.8k | handler.GetDbId(db_id); |
7274 | 44.8k | } |
7275 | 44.8k | if (s.ok()) { |
7276 | 44.8k | RecoverEpochNumbers(); |
7277 | 44.8k | } |
7278 | 44.8k | if (log_status) { |
7279 | 44.8k | *log_status = log_read_status; |
7280 | 44.8k | } |
7281 | 44.8k | } |
7282 | | |
7283 | 44.8k | if (s.ok()) { |
7284 | 44.8k | manifest_file_size_ = current_manifest_file_size; |
7285 | 44.8k | ROCKS_LOG_INFO( |
7286 | 44.8k | db_options_->info_log, |
7287 | 44.8k | "Recovered from manifest file:%s succeeded," |
7288 | 44.8k | "manifest_file_number is %" PRIu64 ", next_file_number is %" PRIu64 |
7289 | 44.8k | ", last_sequence is %" PRIu64 ", log_number is %" PRIu64 |
7290 | 44.8k | ",prev_log_number is %" PRIu64 ",max_column_family is %" PRIu32 |
7291 | 44.8k | ",min_log_number_to_keep is %" PRIu64 "\n", |
7292 | 44.8k | manifest_path.c_str(), manifest_file_number_, next_file_number_.load(), |
7293 | 44.8k | last_sequence_.load(), log_number, prev_log_number_, |
7294 | 44.8k | column_family_set_->GetMaxColumnFamily(), min_log_number_to_keep()); |
7295 | | |
7296 | 60.5k | for (auto cfd : *column_family_set_) { |
7297 | 60.5k | if (cfd->IsDropped()) { |
7298 | 0 | continue; |
7299 | 0 | } |
7300 | 60.5k | ROCKS_LOG_INFO(db_options_->info_log, |
7301 | 60.5k | "Column family [%s] (ID %" PRIu32 |
7302 | 60.5k | "), log number is %" PRIu64 "\n", |
7303 | 60.5k | cfd->GetName().c_str(), cfd->GetID(), cfd->GetLogNumber()); |
7304 | 60.5k | } |
7305 | 44.8k | } |
7306 | | |
7307 | 44.8k | if (s.ok() && !read_only && db_options_->reuse_manifest_on_open && |
7308 | 0 | manifest_recovery_last_valid_record_end_ > 0) { |
7309 | 0 | s = ReopenManifestForAppend(manifest_path); |
7310 | 0 | } |
7311 | | |
7312 | 44.8k | return s; |
7313 | 44.8k | } |
7314 | | |
7315 | | namespace { |
7316 | | class ManifestPicker { |
7317 | | public: |
7318 | | explicit ManifestPicker(const std::string& dbname, |
7319 | | const std::vector<std::string>& files_in_dbname); |
7320 | | // REQUIRES Valid() == true |
7321 | | std::string GetNextManifest(uint64_t* file_number, std::string* file_name); |
7322 | 0 | bool Valid() const { return manifest_file_iter_ != manifest_files_.end(); } |
7323 | | |
7324 | | private: |
7325 | | const std::string& dbname_; |
7326 | | // MANIFEST file names(s) |
7327 | | std::vector<std::string> manifest_files_; |
7328 | | std::vector<std::string>::const_iterator manifest_file_iter_; |
7329 | | }; |
7330 | | |
7331 | | ManifestPicker::ManifestPicker(const std::string& dbname, |
7332 | | const std::vector<std::string>& files_in_dbname) |
7333 | 0 | : dbname_(dbname) { |
7334 | | // populate manifest files |
7335 | 0 | assert(!files_in_dbname.empty()); |
7336 | 0 | for (const auto& fname : files_in_dbname) { |
7337 | 0 | uint64_t file_num = 0; |
7338 | 0 | FileType file_type; |
7339 | 0 | bool parse_ok = ParseFileName(fname, &file_num, &file_type); |
7340 | 0 | if (parse_ok && file_type == kDescriptorFile) { |
7341 | 0 | manifest_files_.push_back(fname); |
7342 | 0 | } |
7343 | 0 | } |
7344 | | // seek to first manifest |
7345 | 0 | std::sort(manifest_files_.begin(), manifest_files_.end(), |
7346 | 0 | [](const std::string& lhs, const std::string& rhs) { |
7347 | 0 | uint64_t num1 = 0; |
7348 | 0 | uint64_t num2 = 0; |
7349 | 0 | FileType type1; |
7350 | 0 | FileType type2; |
7351 | 0 | bool parse_ok1 = ParseFileName(lhs, &num1, &type1); |
7352 | 0 | bool parse_ok2 = ParseFileName(rhs, &num2, &type2); |
7353 | | #ifndef NDEBUG |
7354 | | assert(parse_ok1); |
7355 | | assert(parse_ok2); |
7356 | | #else |
7357 | 0 | (void)parse_ok1; |
7358 | 0 | (void)parse_ok2; |
7359 | 0 | #endif |
7360 | 0 | return num1 > num2; |
7361 | 0 | }); |
7362 | 0 | manifest_file_iter_ = manifest_files_.begin(); |
7363 | 0 | } |
7364 | | |
7365 | | std::string ManifestPicker::GetNextManifest(uint64_t* number, |
7366 | 0 | std::string* file_name) { |
7367 | 0 | assert(Valid()); |
7368 | 0 | std::string ret; |
7369 | 0 | if (manifest_file_iter_ != manifest_files_.end()) { |
7370 | 0 | ret.assign(dbname_); |
7371 | 0 | if (ret.back() != kFilePathSeparator) { |
7372 | 0 | ret.push_back(kFilePathSeparator); |
7373 | 0 | } |
7374 | 0 | ret.append(*manifest_file_iter_); |
7375 | 0 | if (number) { |
7376 | 0 | FileType type; |
7377 | 0 | bool parse = ParseFileName(*manifest_file_iter_, number, &type); |
7378 | 0 | assert(type == kDescriptorFile); |
7379 | | #ifndef NDEBUG |
7380 | | assert(parse); |
7381 | | #else |
7382 | 0 | (void)parse; |
7383 | 0 | #endif |
7384 | 0 | } |
7385 | 0 | if (file_name) { |
7386 | 0 | *file_name = *manifest_file_iter_; |
7387 | 0 | } |
7388 | 0 | ++manifest_file_iter_; |
7389 | 0 | } |
7390 | 0 | return ret; |
7391 | 0 | } |
7392 | | } // anonymous namespace |
7393 | | |
7394 | | Status VersionSet::TryRecover( |
7395 | | const std::vector<ColumnFamilyDescriptor>& column_families, bool read_only, |
7396 | | const std::vector<std::string>& files_in_dbname, std::string* db_id, |
7397 | 0 | bool* has_missing_table_file) { |
7398 | 0 | ManifestPicker manifest_picker(dbname_, files_in_dbname); |
7399 | 0 | if (!manifest_picker.Valid()) { |
7400 | 0 | return Status::Corruption("Cannot locate MANIFEST file in " + dbname_); |
7401 | 0 | } |
7402 | 0 | Status s; |
7403 | 0 | std::string manifest_path = |
7404 | 0 | manifest_picker.GetNextManifest(&manifest_file_number_, nullptr); |
7405 | 0 | while (!manifest_path.empty()) { |
7406 | 0 | s = TryRecoverFromOneManifest(manifest_path, column_families, read_only, |
7407 | 0 | db_id, has_missing_table_file); |
7408 | 0 | if (s.ok() || !manifest_picker.Valid()) { |
7409 | 0 | break; |
7410 | 0 | } |
7411 | 0 | Reset(); |
7412 | 0 | manifest_path = |
7413 | 0 | manifest_picker.GetNextManifest(&manifest_file_number_, nullptr); |
7414 | 0 | } |
7415 | 0 | return s; |
7416 | 0 | } |
7417 | | |
7418 | | Status VersionSet::TryRecoverFromOneManifest( |
7419 | | const std::string& manifest_path, |
7420 | | const std::vector<ColumnFamilyDescriptor>& column_families, bool read_only, |
7421 | 0 | std::string* db_id, bool* has_missing_table_file) { |
7422 | 0 | const ReadOptions read_options(Env::IOActivity::kDBOpen); |
7423 | 0 | ROCKS_LOG_INFO(db_options_->info_log, "Trying to recover from manifest: %s\n", |
7424 | 0 | manifest_path.c_str()); |
7425 | 0 | std::unique_ptr<SequentialFileReader> manifest_file_reader; |
7426 | 0 | Status s; |
7427 | 0 | { |
7428 | 0 | std::unique_ptr<FSSequentialFile> manifest_file; |
7429 | 0 | s = fs_->NewSequentialFile(manifest_path, |
7430 | 0 | fs_->OptimizeForManifestRead(file_options_), |
7431 | 0 | &manifest_file, nullptr); |
7432 | 0 | if (!s.ok()) { |
7433 | 0 | return s; |
7434 | 0 | } |
7435 | 0 | manifest_file_reader.reset(new SequentialFileReader( |
7436 | 0 | std::move(manifest_file), manifest_path, |
7437 | 0 | db_options_->log_readahead_size, io_tracer_, db_options_->listeners)); |
7438 | 0 | } |
7439 | | |
7440 | 0 | assert(s.ok()); |
7441 | 0 | VersionSet::LogReporter reporter; |
7442 | 0 | reporter.status = &s; |
7443 | 0 | log::Reader reader(nullptr, std::move(manifest_file_reader), &reporter, |
7444 | 0 | /*checksum=*/true, /*log_num=*/0); |
7445 | 0 | VersionEditHandlerPointInTime handler_pit( |
7446 | 0 | read_only, column_families, const_cast<VersionSet*>(this), io_tracer_, |
7447 | 0 | read_options, /*allow_incomplete_valid_version=*/true, |
7448 | | /*record_missing_blob_files_as_dropped=*/ |
7449 | 0 | db_options_->best_efforts_blob_db_repair, |
7450 | 0 | /*trust_manifest_recovery=*/false, |
7451 | 0 | /*defer_sst_file_opening=*/false, EpochNumberRequirement::kMightMissing); |
7452 | |
|
7453 | 0 | handler_pit.Iterate(reader, &s); |
7454 | |
|
7455 | 0 | handler_pit.GetDbId(db_id); |
7456 | |
|
7457 | 0 | assert(nullptr != has_missing_table_file); |
7458 | 0 | *has_missing_table_file = handler_pit.HasMissingFiles(); |
7459 | |
|
7460 | 0 | s = handler_pit.status(); |
7461 | 0 | if (s.ok()) { |
7462 | 0 | RecoverEpochNumbers(); |
7463 | 0 | if (db_options_->best_efforts_blob_db_repair) { |
7464 | 0 | LogDroppedBlobFiles(); |
7465 | 0 | } |
7466 | 0 | } |
7467 | 0 | return s; |
7468 | 0 | } |
7469 | | |
7470 | 0 | void VersionSet::LogDroppedBlobFiles() const { |
7471 | 0 | for (ColumnFamilyData* cfd : *column_family_set_) { |
7472 | 0 | if (cfd->IsDropped()) { |
7473 | 0 | continue; |
7474 | 0 | } |
7475 | 0 | const std::vector<uint64_t>& dropped_blob_files = |
7476 | 0 | cfd->current()->storage_info()->GetDroppedBlobFiles(); |
7477 | 0 | if (dropped_blob_files.empty()) { |
7478 | 0 | continue; |
7479 | 0 | } |
7480 | 0 | std::string numbers; |
7481 | 0 | for (const uint64_t blob_file_number : dropped_blob_files) { |
7482 | 0 | numbers.append(" #"); |
7483 | 0 | AppendNumberTo(&numbers, blob_file_number); |
7484 | 0 | } |
7485 | 0 | ROCKS_LOG_WARN(db_options_->info_log, |
7486 | 0 | "[%s] %" ROCKSDB_PRIszt |
7487 | 0 | " dropped blob files are in effect after best-efforts " |
7488 | 0 | "BlobDB repair:%s", |
7489 | 0 | cfd->GetName().c_str(), dropped_blob_files.size(), |
7490 | 0 | numbers.c_str()); |
7491 | 0 | } |
7492 | 0 | } |
7493 | | |
7494 | 44.8k | void VersionSet::RecoverEpochNumbers() { |
7495 | 60.5k | for (auto cfd : *column_family_set_) { |
7496 | 60.5k | if (cfd->IsDropped()) { |
7497 | 0 | continue; |
7498 | 0 | } |
7499 | 60.5k | assert(cfd->initialized()); |
7500 | 60.5k | cfd->RecoverEpochNumbers(); |
7501 | 60.5k | } |
7502 | 44.8k | } |
7503 | | |
7504 | | Status VersionSet::ListColumnFamilies(std::vector<std::string>* column_families, |
7505 | | const std::string& dbname, |
7506 | 0 | FileSystem* fs) { |
7507 | | // Read "CURRENT" file, which contains a pointer to the current manifest |
7508 | | // file |
7509 | 0 | std::string manifest_path; |
7510 | 0 | uint64_t manifest_file_number; |
7511 | 0 | Status s = GetCurrentManifestPath(dbname, fs, /*is_retry=*/false, |
7512 | 0 | &manifest_path, &manifest_file_number); |
7513 | 0 | if (!s.ok()) { |
7514 | 0 | return s; |
7515 | 0 | } |
7516 | 0 | return ListColumnFamiliesFromManifest(manifest_path, fs, column_families); |
7517 | 0 | } |
7518 | | |
7519 | | Status VersionSet::ListColumnFamiliesFromManifest( |
7520 | | const std::string& manifest_path, FileSystem* fs, |
7521 | 0 | std::vector<std::string>* column_families) { |
7522 | | // TODO: plumb Env::IOActivity, Env::IOPriority |
7523 | 0 | const ReadOptions read_options; |
7524 | 0 | std::unique_ptr<SequentialFileReader> file_reader; |
7525 | 0 | Status s; |
7526 | 0 | { |
7527 | 0 | std::unique_ptr<FSSequentialFile> file; |
7528 | | // these are just for performance reasons, not correctness, |
7529 | | // so we're fine using the defaults |
7530 | 0 | s = fs->NewSequentialFile(manifest_path, FileOptions(), &file, nullptr); |
7531 | 0 | if (!s.ok()) { |
7532 | 0 | return s; |
7533 | 0 | } |
7534 | 0 | file_reader = std::make_unique<SequentialFileReader>( |
7535 | 0 | std::move(file), manifest_path, /*io_tracer=*/nullptr); |
7536 | 0 | } |
7537 | | |
7538 | 0 | VersionSet::LogReporter reporter; |
7539 | 0 | reporter.status = &s; |
7540 | 0 | log::Reader reader(nullptr, std::move(file_reader), &reporter, |
7541 | 0 | true /* checksum */, 0 /* log_number */); |
7542 | |
|
7543 | 0 | ListColumnFamiliesHandler handler(read_options); |
7544 | 0 | handler.Iterate(reader, &s); |
7545 | |
|
7546 | 0 | assert(column_families); |
7547 | 0 | column_families->clear(); |
7548 | 0 | if (handler.status().ok()) { |
7549 | 0 | for (const auto& iter : handler.GetColumnFamilyNames()) { |
7550 | 0 | column_families->push_back(iter.second); |
7551 | 0 | } |
7552 | 0 | } |
7553 | |
|
7554 | 0 | return handler.status(); |
7555 | 0 | } |
7556 | | |
7557 | | Status VersionSet::GetOptionsFileManifestState( |
7558 | | const std::string& dbname, FileSystem* fs, |
7559 | 0 | OptionsFileManifestState* manifest_state) { |
7560 | 0 | return GetOptionsFileManifestState( |
7561 | 0 | dbname, fs, manifest_state, /*next_file_number=*/nullptr, |
7562 | 0 | /*last_valid_manifest_record_end=*/nullptr); |
7563 | 0 | } |
7564 | | |
7565 | | Status VersionSet::GetOptionsFileManifestState( |
7566 | | const std::string& dbname, FileSystem* fs, |
7567 | | OptionsFileManifestState* manifest_state, uint64_t* next_file_number, |
7568 | 0 | uint64_t* last_valid_manifest_record_end) { |
7569 | 0 | assert(fs != nullptr); |
7570 | 0 | assert(manifest_state != nullptr); |
7571 | 0 | *manifest_state = OptionsFileManifestState(); |
7572 | 0 | if (next_file_number != nullptr) { |
7573 | 0 | *next_file_number = 0; |
7574 | 0 | } |
7575 | 0 | if (last_valid_manifest_record_end != nullptr) { |
7576 | 0 | *last_valid_manifest_record_end = 0; |
7577 | 0 | } |
7578 | |
|
7579 | 0 | constexpr int kMaxAttempts = 2; |
7580 | 0 | for (int attempt = 0; attempt < kMaxAttempts; ++attempt) { |
7581 | 0 | std::string manifest_path; |
7582 | 0 | uint64_t manifest_file_number = 0; |
7583 | 0 | Status s = GetCurrentManifestPath(dbname, fs, /*is_retry=*/attempt > 0, |
7584 | 0 | &manifest_path, &manifest_file_number); |
7585 | 0 | if (s.IsNotFound()) { |
7586 | 0 | if (attempt + 1 == kMaxAttempts) { |
7587 | 0 | return Status::OK(); |
7588 | 0 | } |
7589 | 0 | continue; |
7590 | 0 | } |
7591 | 0 | if (!s.ok()) { |
7592 | 0 | return s; |
7593 | 0 | } |
7594 | | |
7595 | 0 | std::unique_ptr<FSSequentialFile> file; |
7596 | 0 | s = fs->NewSequentialFile(manifest_path, FileOptions(), &file, nullptr); |
7597 | 0 | if (s.IsNotFound() || s.IsPathNotFound()) { |
7598 | 0 | if (attempt + 1 == kMaxAttempts) { |
7599 | 0 | return s; |
7600 | 0 | } |
7601 | 0 | continue; |
7602 | 0 | } |
7603 | 0 | if (!s.ok()) { |
7604 | 0 | return s; |
7605 | 0 | } |
7606 | 0 | auto file_reader = std::make_unique<SequentialFileReader>( |
7607 | 0 | std::move(file), manifest_path, /*io_tracer=*/nullptr); |
7608 | 0 | LogReporter reporter; |
7609 | 0 | reporter.status = &s; |
7610 | 0 | log::Reader reader(nullptr, std::move(file_reader), &reporter, |
7611 | 0 | /*checksum=*/true, /*log_number=*/0); |
7612 | |
|
7613 | 0 | OptionsFileManifestState found_manifest_state; |
7614 | | // The default column family is implicit in MANIFEST snapshots and is not |
7615 | | // written as a kColumnFamilyAdd record. |
7616 | 0 | found_manifest_state.column_family_names.emplace(0, |
7617 | 0 | kDefaultColumnFamilyName); |
7618 | 0 | uint64_t found_next_file_number = 0; |
7619 | 0 | uint64_t found_last_valid_record_end = 0; |
7620 | 0 | auto apply_edit = [&](const VersionEdit& edit) { |
7621 | 0 | found_manifest_state.ApplyColumnFamilyEdit(edit); |
7622 | 0 | if (edit.HasCommittedOptionsFileNumber()) { |
7623 | 0 | found_manifest_state.ApplyCommit(edit.GetCommittedOptionsFileNumber()); |
7624 | 0 | } |
7625 | 0 | if (edit.HasNextFile()) { |
7626 | 0 | found_next_file_number = edit.GetNextFile(); |
7627 | 0 | } |
7628 | 0 | }; |
7629 | |
|
7630 | 0 | AtomicGroupReadBuffer atomic_group; |
7631 | 0 | Slice record; |
7632 | 0 | std::string scratch; |
7633 | 0 | while (s.ok() && reader.ReadRecord(&record, &scratch)) { |
7634 | 0 | VersionEdit edit; |
7635 | 0 | s = edit.DecodeFrom(record); |
7636 | 0 | if (!s.ok()) { |
7637 | 0 | break; |
7638 | 0 | } |
7639 | 0 | s = atomic_group.AddEdit(&edit); |
7640 | 0 | if (!s.ok()) { |
7641 | 0 | break; |
7642 | 0 | } |
7643 | 0 | if (edit.IsInAtomicGroup()) { |
7644 | 0 | if (!atomic_group.IsFull()) { |
7645 | 0 | continue; |
7646 | 0 | } |
7647 | 0 | found_last_valid_record_end = reader.LastRecordEnd(); |
7648 | 0 | for (const auto& grouped_edit : atomic_group.replay_buffer()) { |
7649 | 0 | apply_edit(grouped_edit); |
7650 | 0 | } |
7651 | 0 | atomic_group.Clear(); |
7652 | 0 | } else { |
7653 | 0 | found_last_valid_record_end = reader.LastRecordEnd(); |
7654 | 0 | apply_edit(edit); |
7655 | 0 | } |
7656 | 0 | } |
7657 | 0 | if (!s.ok()) { |
7658 | 0 | return s; |
7659 | 0 | } |
7660 | | |
7661 | 0 | *manifest_state = found_manifest_state; |
7662 | 0 | if (next_file_number != nullptr) { |
7663 | 0 | *next_file_number = found_next_file_number; |
7664 | 0 | } |
7665 | 0 | if (last_valid_manifest_record_end != nullptr) { |
7666 | 0 | *last_valid_manifest_record_end = found_last_valid_record_end; |
7667 | 0 | } |
7668 | 0 | return Status::OK(); |
7669 | 0 | } |
7670 | | |
7671 | 0 | return Status::OK(); |
7672 | 0 | } |
7673 | | |
7674 | | Status VersionSet::ResolveOptionsFileNumber( |
7675 | | const std::string& dbname, FileSystem* fs, |
7676 | | const OptionsFileManifestState& manifest_state, |
7677 | | const std::vector<std::string>& filenames, |
7678 | 44.8k | bool inspect_options_file_tracking, OptionsFileSelection* selection) { |
7679 | 44.8k | assert(fs != nullptr); |
7680 | 44.8k | assert(selection != nullptr); |
7681 | 44.8k | *selection = OptionsFileSelection(); |
7682 | | |
7683 | 44.8k | std::map<uint64_t, std::string, std::greater<uint64_t>> options_files; |
7684 | 704k | for (const std::string& filename : filenames) { |
7685 | 704k | uint64_t number = 0; |
7686 | 704k | FileType type; |
7687 | 704k | if (ParseFileName(filename, &number, &type) && type == kOptionsFile) { |
7688 | 67.1k | options_files.emplace(number, filename); |
7689 | 67.1k | } |
7690 | 704k | } |
7691 | 44.8k | if (options_files.empty()) { |
7692 | 8.13k | return Status::OK(); |
7693 | 8.13k | } |
7694 | | |
7695 | 36.7k | const auto highest = options_files.begin(); |
7696 | 36.7k | if (!manifest_state.has_committed_options_file_number && |
7697 | 36.7k | !inspect_options_file_tracking) { |
7698 | 36.7k | selection->file_number = highest->first; |
7699 | 36.7k | return Status::OK(); |
7700 | 36.7k | } |
7701 | | |
7702 | 0 | auto is_tracked = [&](const std::string& filename, bool* tracked) -> Status { |
7703 | 0 | assert(tracked != nullptr); |
7704 | 0 | *tracked = false; |
7705 | 0 | const std::string path = dbname + "/" + filename; |
7706 | 0 | TEST_SYNC_POINT( |
7707 | 0 | "VersionSet::ResolveOptionsFileNumber:InspectTrackingMarker"); |
7708 | 0 | std::unique_ptr<FSSequentialFile> file; |
7709 | 0 | Status s = fs->NewSequentialFile(path, FileOptions(), &file, nullptr); |
7710 | 0 | if (!s.ok()) { |
7711 | 0 | return s; |
7712 | 0 | } |
7713 | 0 | LineFileReader reader(std::move(file), path, /*io_tracer=*/nullptr, |
7714 | 0 | std::vector<std::shared_ptr<EventListener>>{}, |
7715 | 0 | /*rate_limiter=*/nullptr); |
7716 | 0 | bool in_db_options = false; |
7717 | 0 | std::string line; |
7718 | 0 | while (reader.ReadLine(&line, Env::IO_TOTAL)) { |
7719 | 0 | line = RocksDBOptionsParser::TrimAndRemoveComment(line); |
7720 | 0 | if (line.empty()) { |
7721 | 0 | continue; |
7722 | 0 | } |
7723 | 0 | if (line.front() == '[' && line.back() == ']') { |
7724 | 0 | if (in_db_options) { |
7725 | 0 | return Status::OK(); |
7726 | 0 | } |
7727 | 0 | in_db_options = line == "[DBOptions]"; |
7728 | 0 | continue; |
7729 | 0 | } |
7730 | 0 | if (!in_db_options) { |
7731 | 0 | continue; |
7732 | 0 | } |
7733 | 0 | const size_t separator = line.find('='); |
7734 | 0 | if (separator == std::string::npos) { |
7735 | 0 | continue; |
7736 | 0 | } |
7737 | 0 | const std::string name = RocksDBOptionsParser::TrimAndRemoveComment( |
7738 | 0 | line.substr(0, separator), /*trim_only=*/true); |
7739 | 0 | if (name != "track_options_file_number_in_manifest") { |
7740 | 0 | continue; |
7741 | 0 | } |
7742 | 0 | const std::string value = RocksDBOptionsParser::TrimAndRemoveComment( |
7743 | 0 | line.substr(separator + 1), /*trim_only=*/true); |
7744 | 0 | if (value == "true" || value == "1") { |
7745 | 0 | *tracked = true; |
7746 | 0 | return Status::OK(); |
7747 | 0 | } |
7748 | 0 | if (value == "false" || value == "0") { |
7749 | 0 | return Status::OK(); |
7750 | 0 | } |
7751 | 0 | return Status::InvalidArgument( |
7752 | 0 | "Invalid track_options_file_number_in_manifest value in " + path); |
7753 | 0 | } |
7754 | 0 | return reader.GetStatus(); |
7755 | 0 | }; |
7756 | |
|
7757 | 0 | if (!manifest_state.has_committed_options_file_number) { |
7758 | 0 | selection->file_number = highest->first; |
7759 | 0 | bool highest_is_tracked = false; |
7760 | 0 | Status marker_status = is_tracked(highest->second, &highest_is_tracked); |
7761 | | // Marker inspection only selects between legacy and MANIFEST semantics. |
7762 | | // The caller that consumes the file will report any actual read error. |
7763 | 0 | selection->used_fallback = !marker_status.ok() || highest_is_tracked; |
7764 | 0 | return Status::OK(); |
7765 | 0 | } |
7766 | | |
7767 | 0 | std::vector<uint64_t> rejected_tracked_file_numbers; |
7768 | 0 | for (auto it = options_files.begin(); it != options_files.end(); ++it) { |
7769 | 0 | if (it->first == manifest_state.committed_options_file_number) { |
7770 | 0 | selection->file_number = it->first; |
7771 | 0 | selection->selected_by_manifest = true; |
7772 | 0 | selection->rejected_tracked_file_numbers = |
7773 | 0 | std::move(rejected_tracked_file_numbers); |
7774 | 0 | return Status::OK(); |
7775 | 0 | } |
7776 | 0 | if (it->first < manifest_state.committed_options_file_number) { |
7777 | 0 | break; |
7778 | 0 | } |
7779 | 0 | bool tracked = false; |
7780 | 0 | Status marker_status = is_tracked(it->second, &tracked); |
7781 | 0 | if (!marker_status.ok()) { |
7782 | | // An unreadable higher-numbered file cannot safely override a committed |
7783 | | // snapshot, but it also must not make DB::Open fail. Loading the selected |
7784 | | // file remains responsible for reporting content errors to utility APIs. |
7785 | | // Keep the unreadable candidate because it might be a legacy snapshot. |
7786 | 0 | continue; |
7787 | 0 | } |
7788 | 0 | if (!tracked) { |
7789 | 0 | selection->file_number = it->first; |
7790 | 0 | selection->rejected_tracked_file_numbers = |
7791 | 0 | std::move(rejected_tracked_file_numbers); |
7792 | 0 | return Status::OK(); |
7793 | 0 | } |
7794 | 0 | rejected_tracked_file_numbers.push_back(it->first); |
7795 | 0 | } |
7796 | | |
7797 | 0 | selection->file_number = highest->first; |
7798 | 0 | selection->used_fallback = true; |
7799 | 0 | return Status::OK(); |
7800 | 0 | } |
7801 | | |
7802 | | Status VersionSet::ReduceNumberOfLevels(const std::string& dbname, |
7803 | | const Options* options, |
7804 | | const FileOptions& file_options, |
7805 | 0 | int new_levels) { |
7806 | 0 | if (new_levels <= 1) { |
7807 | 0 | return Status::InvalidArgument( |
7808 | 0 | "Number of levels needs to be bigger than 1"); |
7809 | 0 | } |
7810 | | |
7811 | | // TODO: plumb Env::IOActivity, Env::IOPriority |
7812 | 0 | const ReadOptions read_options; |
7813 | 0 | const WriteOptions write_options; |
7814 | |
|
7815 | 0 | ImmutableDBOptions imm_db_options(*options); |
7816 | 0 | MutableDBOptions mutable_db_options(*options); |
7817 | 0 | ColumnFamilyOptions cf_options(*options); |
7818 | 0 | std::shared_ptr<Cache> tc(NewLRUCache(options->max_open_files - 10, |
7819 | 0 | options->table_cache_numshardbits)); |
7820 | 0 | WriteController wc(options->delayed_write_rate); |
7821 | 0 | WriteBufferManager wb(options->db_write_buffer_size); |
7822 | 0 | VersionSet versions(dbname, &imm_db_options, mutable_db_options, file_options, |
7823 | 0 | tc.get(), &wb, &wc, nullptr /*BlockCacheTracer*/, |
7824 | 0 | nullptr /*IOTracer*/, |
7825 | 0 | /*db_id*/ "", |
7826 | 0 | /*db_session_id*/ "", options->daily_offpeak_time_utc, |
7827 | 0 | /*error_handler_*/ nullptr, /*unchanging=*/false); |
7828 | 0 | Status status; |
7829 | |
|
7830 | 0 | std::vector<ColumnFamilyDescriptor> dummy; |
7831 | 0 | ColumnFamilyDescriptor dummy_descriptor(kDefaultColumnFamilyName, |
7832 | 0 | ColumnFamilyOptions(*options)); |
7833 | 0 | dummy.push_back(dummy_descriptor); |
7834 | 0 | status = versions.Recover(dummy); |
7835 | 0 | if (!status.ok()) { |
7836 | 0 | return status; |
7837 | 0 | } |
7838 | | |
7839 | 0 | Version* current_version = |
7840 | 0 | versions.GetColumnFamilySet()->GetDefault()->current(); |
7841 | 0 | auto* vstorage = current_version->storage_info(); |
7842 | 0 | int current_levels = vstorage->num_levels(); |
7843 | |
|
7844 | 0 | if (current_levels <= new_levels) { |
7845 | 0 | return Status::OK(); |
7846 | 0 | } |
7847 | | |
7848 | | // Make sure there are file only on one level from |
7849 | | // (new_levels-1) to (current_levels-1) |
7850 | 0 | int first_nonempty_level = -1; |
7851 | 0 | int first_nonempty_level_filenum = 0; |
7852 | 0 | for (int i = new_levels - 1; i < current_levels; i++) { |
7853 | 0 | int file_num = vstorage->NumLevelFiles(i); |
7854 | 0 | if (file_num != 0) { |
7855 | 0 | if (first_nonempty_level < 0) { |
7856 | 0 | first_nonempty_level = i; |
7857 | 0 | first_nonempty_level_filenum = file_num; |
7858 | 0 | } else { |
7859 | 0 | char msg[255]; |
7860 | 0 | snprintf(msg, sizeof(msg), |
7861 | 0 | "Found at least two levels containing files: " |
7862 | 0 | "[%d:%d],[%d:%d].\n", |
7863 | 0 | first_nonempty_level, first_nonempty_level_filenum, i, |
7864 | 0 | file_num); |
7865 | 0 | return Status::InvalidArgument(msg); |
7866 | 0 | } |
7867 | 0 | } |
7868 | 0 | } |
7869 | | |
7870 | | // we need to allocate an array with the old number of levels size to |
7871 | | // avoid SIGSEGV in WriteCurrentStatetoManifest() |
7872 | | // however, all levels bigger or equal to new_levels will be empty |
7873 | 0 | std::vector<FileMetaData*>* new_files_list = |
7874 | 0 | new std::vector<FileMetaData*>[current_levels]; |
7875 | 0 | for (int i = 0; i < new_levels - 1; i++) { |
7876 | 0 | new_files_list[i] = vstorage->LevelFiles(i); |
7877 | 0 | } |
7878 | |
|
7879 | 0 | if (first_nonempty_level > 0) { |
7880 | 0 | auto& new_last_level = new_files_list[new_levels - 1]; |
7881 | |
|
7882 | 0 | new_last_level = vstorage->LevelFiles(first_nonempty_level); |
7883 | |
|
7884 | 0 | for (size_t i = 0; i < new_last_level.size(); ++i) { |
7885 | 0 | const FileMetaData* const meta = new_last_level[i]; |
7886 | 0 | assert(meta); |
7887 | |
|
7888 | 0 | const uint64_t file_number = meta->fd.GetNumber(); |
7889 | |
|
7890 | 0 | vstorage->file_locations_[file_number] = |
7891 | 0 | VersionStorageInfo::FileLocation(new_levels - 1, i); |
7892 | 0 | } |
7893 | 0 | } |
7894 | |
|
7895 | 0 | delete[] vstorage->files_; |
7896 | 0 | vstorage->files_ = new_files_list; |
7897 | 0 | vstorage->num_levels_ = new_levels; |
7898 | 0 | vstorage->ResizeCompactCursors(new_levels); |
7899 | |
|
7900 | 0 | VersionEdit ve; |
7901 | 0 | InstrumentedMutex dummy_mutex; |
7902 | 0 | InstrumentedMutexLock l(&dummy_mutex); |
7903 | 0 | return versions.LogAndApply(versions.GetColumnFamilySet()->GetDefault(), |
7904 | 0 | read_options, write_options, &ve, &dummy_mutex, |
7905 | 0 | nullptr, true); |
7906 | 0 | } |
7907 | | |
7908 | | // Get the checksum information including the checksum and checksum function |
7909 | | // name of all SST and blob files in VersionSet. Store the information in |
7910 | | // FileChecksumList which contains a map from file number to its checksum |
7911 | | // info. If DB is not running, make sure call VersionSet::Recover() to load |
7912 | | // the file metadata from Manifest to VersionSet before calling this function. |
7913 | 0 | Status VersionSet::GetLiveFilesChecksumInfo(FileChecksumList* checksum_list) { |
7914 | | // Clean the previously stored checksum information if any. |
7915 | 0 | Status s; |
7916 | 0 | if (checksum_list == nullptr) { |
7917 | 0 | s = Status::InvalidArgument("checksum_list is nullptr"); |
7918 | 0 | return s; |
7919 | 0 | } |
7920 | 0 | checksum_list->reset(); |
7921 | |
|
7922 | 0 | for (auto cfd : *column_family_set_) { |
7923 | 0 | assert(cfd); |
7924 | |
|
7925 | 0 | if (cfd->IsDropped() || !cfd->initialized()) { |
7926 | 0 | continue; |
7927 | 0 | } |
7928 | | |
7929 | 0 | const auto* current = cfd->current(); |
7930 | 0 | assert(current); |
7931 | |
|
7932 | 0 | const auto* vstorage = current->storage_info(); |
7933 | 0 | assert(vstorage); |
7934 | | |
7935 | | /* SST files */ |
7936 | 0 | for (int level = 0; level < cfd->NumberLevels(); level++) { |
7937 | 0 | const auto& level_files = vstorage->LevelFiles(level); |
7938 | |
|
7939 | 0 | for (const auto& file : level_files) { |
7940 | 0 | assert(file); |
7941 | |
|
7942 | 0 | s = checksum_list->InsertOneFileChecksum(file->fd.GetNumber(), |
7943 | 0 | file->file_checksum, |
7944 | 0 | file->file_checksum_func_name); |
7945 | 0 | if (!s.ok()) { |
7946 | 0 | return s; |
7947 | 0 | } |
7948 | 0 | } |
7949 | 0 | } |
7950 | | |
7951 | | /* Blob files */ |
7952 | 0 | const auto& blob_files = vstorage->GetBlobFiles(); |
7953 | 0 | for (const auto& meta : blob_files) { |
7954 | 0 | assert(meta); |
7955 | |
|
7956 | 0 | std::string checksum_value = meta->GetChecksumValue(); |
7957 | 0 | std::string checksum_method = meta->GetChecksumMethod(); |
7958 | 0 | assert(checksum_value.empty() == checksum_method.empty()); |
7959 | 0 | if (meta->GetChecksumMethod().empty()) { |
7960 | 0 | checksum_value = kUnknownFileChecksum; |
7961 | 0 | checksum_method = kUnknownFileChecksumFuncName; |
7962 | 0 | } |
7963 | |
|
7964 | 0 | s = checksum_list->InsertOneFileChecksum(meta->GetBlobFileNumber(), |
7965 | 0 | checksum_value, checksum_method); |
7966 | 0 | if (!s.ok()) { |
7967 | 0 | return s; |
7968 | 0 | } |
7969 | 0 | } |
7970 | 0 | } |
7971 | | |
7972 | 0 | return s; |
7973 | 0 | } |
7974 | | |
7975 | | Status VersionSet::DumpManifest( |
7976 | | Options& options, std::string& dscname, bool verbose, bool hex, bool json, |
7977 | 0 | const std::vector<ColumnFamilyDescriptor>& cf_descs) { |
7978 | 0 | assert(options.env); |
7979 | | // TODO: plumb Env::IOActivity, Env::IOPriority |
7980 | 0 | const ReadOptions read_options; |
7981 | |
|
7982 | 0 | std::vector<std::string> column_families; |
7983 | 0 | Status s = ListColumnFamiliesFromManifest( |
7984 | 0 | dscname, options.env->GetFileSystem().get(), &column_families); |
7985 | 0 | if (!s.ok()) { |
7986 | 0 | return s; |
7987 | 0 | } |
7988 | | |
7989 | | // Open the specified manifest file. |
7990 | 0 | std::unique_ptr<SequentialFileReader> file_reader; |
7991 | 0 | { |
7992 | 0 | std::unique_ptr<FSSequentialFile> file; |
7993 | 0 | const std::shared_ptr<FileSystem>& fs = options.env->GetFileSystem(); |
7994 | 0 | s = fs->NewSequentialFile( |
7995 | 0 | dscname, fs->OptimizeForManifestRead(file_options_), &file, nullptr); |
7996 | 0 | if (!s.ok()) { |
7997 | 0 | return s; |
7998 | 0 | } |
7999 | 0 | file_reader = std::make_unique<SequentialFileReader>( |
8000 | 0 | std::move(file), dscname, db_options_->log_readahead_size, io_tracer_); |
8001 | 0 | } |
8002 | | |
8003 | 0 | std::map<std::string, const ColumnFamilyDescriptor*> cf_name_to_desc; |
8004 | 0 | for (const auto& cf_desc : cf_descs) { |
8005 | 0 | cf_name_to_desc[cf_desc.name] = &cf_desc; |
8006 | 0 | } |
8007 | 0 | std::vector<ColumnFamilyDescriptor> final_cf_descs; |
8008 | 0 | for (const auto& cf : column_families) { |
8009 | 0 | const auto iter = cf_name_to_desc.find(cf); |
8010 | 0 | if (iter != cf_name_to_desc.cend()) { |
8011 | 0 | final_cf_descs.push_back(*iter->second); |
8012 | 0 | } else { |
8013 | 0 | final_cf_descs.emplace_back(cf, options); |
8014 | 0 | } |
8015 | 0 | } |
8016 | |
|
8017 | 0 | DumpManifestHandler handler(final_cf_descs, this, io_tracer_, read_options, |
8018 | 0 | verbose, hex, json); |
8019 | 0 | { |
8020 | 0 | VersionSet::LogReporter reporter; |
8021 | 0 | reporter.status = &s; |
8022 | 0 | log::Reader reader(nullptr, std::move(file_reader), &reporter, |
8023 | 0 | true /* checksum */, 0 /* log_number */); |
8024 | 0 | handler.Iterate(reader, &s); |
8025 | 0 | } |
8026 | |
|
8027 | 0 | return handler.status(); |
8028 | 0 | } |
8029 | | |
8030 | 163k | void VersionSet::MarkFileNumberUsed(uint64_t number) { |
8031 | | // only called during recovery and repair which are single threaded, so this |
8032 | | // works because there can't be concurrent calls |
8033 | 163k | if (next_file_number_.load(std::memory_order_relaxed) <= number) { |
8034 | 30.4k | next_file_number_.store(number + 1, std::memory_order_relaxed); |
8035 | 30.4k | } |
8036 | 163k | } |
8037 | | // Called only either from ::LogAndApply which is protected by mutex or during |
8038 | | // recovery which is single-threaded. |
8039 | 83.3k | void VersionSet::MarkMinLogNumberToKeep(uint64_t number) { |
8040 | 83.3k | if (min_log_number_to_keep_.load(std::memory_order_relaxed) < number) { |
8041 | 67.7k | min_log_number_to_keep_.store(number, std::memory_order_relaxed); |
8042 | 67.7k | } |
8043 | 83.3k | } |
8044 | | |
8045 | | Status VersionSet::WriteCurrentStateToManifest( |
8046 | | const WriteOptions& write_options, |
8047 | | const std::unordered_map<uint32_t, MutableCFState>& curr_state, |
8048 | | const VersionEdit& wal_additions, |
8049 | | const OptionsFileManifestState& options_file_manifest_state, |
8050 | 44.8k | log::Writer* log, IOStatus& io_s) { |
8051 | | // TODO: Break up into multiple records to reduce memory usage on recovery? |
8052 | | |
8053 | | // WARNING: This method doesn't hold a mutex!! |
8054 | | |
8055 | | // This is done without DB mutex lock held, but only within single-threaded |
8056 | | // LogAndApply. Column family manipulations can only happen within |
8057 | | // LogAndApply (the same single thread), so we're safe to iterate. |
8058 | | |
8059 | 44.8k | assert(io_s.ok()); |
8060 | 44.8k | if (db_options_->write_dbid_to_manifest) { |
8061 | 44.8k | VersionEdit edit_for_db_id; |
8062 | 44.8k | assert(!db_id_.empty()); |
8063 | 44.8k | edit_for_db_id.SetDBId(db_id_); |
8064 | 44.8k | std::string db_id_record; |
8065 | 44.8k | if (!edit_for_db_id.EncodeTo(&db_id_record)) { |
8066 | 0 | return Status::Corruption("Unable to Encode VersionEdit:" + |
8067 | 0 | edit_for_db_id.DebugString(true)); |
8068 | 0 | } |
8069 | 44.8k | io_s = log->AddRecord(write_options, db_id_record); |
8070 | 44.8k | if (!io_s.ok()) { |
8071 | 0 | return io_s; |
8072 | 0 | } |
8073 | 44.8k | } |
8074 | | |
8075 | | // Save WALs. |
8076 | 44.8k | if (!wal_additions.GetWalAdditions().empty()) { |
8077 | 0 | TEST_SYNC_POINT_CALLBACK("VersionSet::WriteCurrentStateToManifest:SaveWal", |
8078 | 0 | const_cast<VersionEdit*>(&wal_additions)); |
8079 | 0 | std::string record; |
8080 | 0 | if (!wal_additions.EncodeTo(&record)) { |
8081 | 0 | return Status::Corruption("Unable to Encode VersionEdit: " + |
8082 | 0 | wal_additions.DebugString(true)); |
8083 | 0 | } |
8084 | 0 | io_s = log->AddRecord(write_options, record); |
8085 | 0 | if (!io_s.ok()) { |
8086 | 0 | return io_s; |
8087 | 0 | } |
8088 | 0 | } |
8089 | | |
8090 | | // New manifest should rollover the WAL deletion record from previous |
8091 | | // manifest. Otherwise, when an addition record of a deleted WAL gets added |
8092 | | // to this new manifest later (which can happens in e.g, SyncWAL()), this |
8093 | | // new manifest creates an illusion that such WAL hasn't been deleted. |
8094 | 44.8k | VersionEdit wal_deletions; |
8095 | 44.8k | wal_deletions.DeleteWalsBefore(min_log_number_to_keep()); |
8096 | 44.8k | std::string wal_deletions_record; |
8097 | 44.8k | if (!wal_deletions.EncodeTo(&wal_deletions_record)) { |
8098 | 0 | return Status::Corruption("Unable to Encode VersionEdit: " + |
8099 | 0 | wal_deletions.DebugString(true)); |
8100 | 0 | } |
8101 | 44.8k | io_s = log->AddRecord(write_options, wal_deletions_record); |
8102 | 44.8k | if (!io_s.ok()) { |
8103 | 0 | return io_s; |
8104 | 0 | } |
8105 | | |
8106 | 60.5k | for (auto cfd : *column_family_set_) { |
8107 | 60.5k | assert(cfd); |
8108 | | |
8109 | 60.5k | if (cfd->IsDropped()) { |
8110 | 0 | continue; |
8111 | 0 | } |
8112 | 60.5k | assert(cfd->initialized()); |
8113 | 60.5k | { |
8114 | | // Store column family info |
8115 | 60.5k | VersionEdit edit; |
8116 | 60.5k | if (cfd->GetID() != 0) { |
8117 | | // default column family is always there, |
8118 | | // no need to explicitly write it |
8119 | 15.6k | edit.AddColumnFamily(cfd->GetName()); |
8120 | 15.6k | edit.SetColumnFamily(cfd->GetID()); |
8121 | 15.6k | } |
8122 | 60.5k | edit.SetComparatorName( |
8123 | 60.5k | cfd->internal_comparator().user_comparator()->Name()); |
8124 | 60.5k | edit.SetPersistUserDefinedTimestamps( |
8125 | 60.5k | cfd->ioptions().persist_user_defined_timestamps); |
8126 | 60.5k | std::string record; |
8127 | 60.5k | if (!edit.EncodeTo(&record)) { |
8128 | 0 | return Status::Corruption("Unable to Encode VersionEdit:" + |
8129 | 0 | edit.DebugString(true)); |
8130 | 0 | } |
8131 | 60.5k | io_s = log->AddRecord(write_options, record); |
8132 | 60.5k | if (!io_s.ok()) { |
8133 | 0 | return io_s; |
8134 | 0 | } |
8135 | 60.5k | } |
8136 | | |
8137 | 60.5k | { |
8138 | | // Save files |
8139 | 60.5k | VersionEdit edit; |
8140 | 60.5k | edit.SetColumnFamily(cfd->GetID()); |
8141 | | |
8142 | 60.5k | const auto* current = cfd->current(); |
8143 | 60.5k | assert(current); |
8144 | | |
8145 | 60.5k | const auto* vstorage = current->storage_info(); |
8146 | 60.5k | assert(vstorage); |
8147 | | |
8148 | 484k | for (int level = 0; level < cfd->NumberLevels(); level++) { |
8149 | 423k | const auto& level_files = vstorage->LevelFiles(level); |
8150 | | |
8151 | 423k | for (const auto& f : level_files) { |
8152 | 68.7k | assert(f); |
8153 | | // Preserves all persisted metadata, including file_open_metadata |
8154 | | // so live SSTs still benefit from fast_sst_open after a MANIFEST |
8155 | | // rotation. |
8156 | 68.7k | edit.AddExistingFile(level, *f, /*provenance_flags_to_set=*/0); |
8157 | 68.7k | } |
8158 | 423k | } |
8159 | | |
8160 | 60.5k | edit.SetCompactCursors(vstorage->GetCompactCursors()); |
8161 | | |
8162 | 60.5k | const auto& blob_files = vstorage->GetBlobFiles(); |
8163 | 60.5k | for (const auto& meta : blob_files) { |
8164 | 0 | assert(meta); |
8165 | |
|
8166 | 0 | const uint64_t blob_file_number = meta->GetBlobFileNumber(); |
8167 | |
|
8168 | 0 | edit.AddBlobFile(blob_file_number, meta->GetTotalBlobCount(), |
8169 | 0 | meta->GetTotalBlobBytes(), meta->GetChecksumMethod(), |
8170 | 0 | meta->GetChecksumValue()); |
8171 | 0 | if (meta->GetGarbageBlobCount() > 0) { |
8172 | 0 | edit.AddBlobFileGarbage(blob_file_number, meta->GetGarbageBlobCount(), |
8173 | 0 | meta->GetGarbageBlobBytes()); |
8174 | 0 | } |
8175 | 0 | } |
8176 | | |
8177 | 60.5k | for (const uint64_t blob_file_number : vstorage->GetDroppedBlobFiles()) { |
8178 | 0 | edit.AddDroppedBlobFile(blob_file_number); |
8179 | 0 | } |
8180 | | |
8181 | 60.5k | const auto iter = curr_state.find(cfd->GetID()); |
8182 | 60.5k | assert(iter != curr_state.end()); |
8183 | 60.5k | uint64_t log_number = iter->second.log_number; |
8184 | 60.5k | edit.SetLogNumber(log_number); |
8185 | | |
8186 | 60.5k | if (cfd->GetID() == 0) { |
8187 | | // min_log_number_to_keep is for the whole db, not for specific column |
8188 | | // family. So it does not need to be set for every column family, just |
8189 | | // need to be set once. Since default CF can never be dropped, we set |
8190 | | // the min_log to the default CF here. |
8191 | 44.8k | uint64_t min_log = min_log_number_to_keep(); |
8192 | 44.8k | if (min_log != 0) { |
8193 | 29.2k | edit.SetMinLogNumberToKeep(min_log); |
8194 | 29.2k | } |
8195 | 44.8k | } |
8196 | | |
8197 | 60.5k | const std::string& full_history_ts_low = iter->second.full_history_ts_low; |
8198 | 60.5k | if (!full_history_ts_low.empty()) { |
8199 | 0 | edit.SetFullHistoryTsLow(full_history_ts_low); |
8200 | 0 | } |
8201 | | |
8202 | 60.5k | edit.SetLastSequence(descriptor_last_sequence_); |
8203 | | |
8204 | 60.5k | const Comparator* ucmp = cfd->user_comparator(); |
8205 | 60.5k | assert(ucmp); |
8206 | 60.5k | std::string record; |
8207 | 60.5k | if (!edit.EncodeTo(&record, ucmp->timestamp_size())) { |
8208 | 0 | return Status::Corruption("Unable to Encode VersionEdit:" + |
8209 | 0 | edit.DebugString(true)); |
8210 | 0 | } |
8211 | 60.5k | io_s = log->AddRecord(write_options, record); |
8212 | 60.5k | if (!io_s.ok()) { |
8213 | 0 | return io_s; |
8214 | 0 | } |
8215 | 60.5k | } |
8216 | 60.5k | } |
8217 | | |
8218 | 44.8k | if (options_file_manifest_state.has_committed_options_file_number) { |
8219 | 0 | assert(log != nullptr); |
8220 | 0 | VersionEdit edit; |
8221 | 0 | edit.SetCommittedOptionsFileNumber( |
8222 | 0 | options_file_manifest_state.committed_options_file_number); |
8223 | 0 | std::string record; |
8224 | 0 | if (!edit.EncodeTo(&record)) { |
8225 | 0 | return Status::Corruption("Unable to Encode VersionEdit:" + |
8226 | 0 | edit.DebugString(true)); |
8227 | 0 | } |
8228 | 0 | io_s = log->AddRecord(write_options, record); |
8229 | 0 | if (!io_s.ok()) { |
8230 | 0 | return io_s; |
8231 | 0 | } |
8232 | 0 | } |
8233 | | |
8234 | | // Record the approximate compacted manifest size so it's available at |
8235 | | // recovery time for TuneMaxManifestFileSize(). This record must come last |
8236 | | // in WriteCurrentStateToManifest for an accurate size estimate. |
8237 | 44.8k | { |
8238 | | // Include a rough estimate of this record's own size (~15 bytes for the |
8239 | | // VersionEdit payload + log record header). |
8240 | 44.8k | constexpr uint64_t kEstimatedRecordOverhead = 15; |
8241 | 44.8k | VersionEdit edit; |
8242 | 44.8k | edit.SetLastCompactedManifestFileSize(log->file()->GetFileSize() + |
8243 | 44.8k | kEstimatedRecordOverhead); |
8244 | 44.8k | std::string record; |
8245 | 44.8k | if (!edit.EncodeTo(&record)) { |
8246 | 0 | return Status::Corruption("Unable to Encode VersionEdit:" + |
8247 | 0 | edit.DebugString(true)); |
8248 | 0 | } |
8249 | 44.8k | io_s = log->AddRecord(write_options, record); |
8250 | 44.8k | if (!io_s.ok()) { |
8251 | 0 | return io_s; |
8252 | 0 | } |
8253 | 44.8k | } |
8254 | | |
8255 | 44.8k | return Status::OK(); |
8256 | 44.8k | } |
8257 | | |
8258 | | // TODO(aekmekji): in CompactionJob::GenSubcompactionBoundaries(), this |
8259 | | // function is called repeatedly with consecutive pairs of slices. For example |
8260 | | // if the slice list is [a, b, c, d] this function is called with arguments |
8261 | | // (a,b) then (b,c) then (c,d). Knowing this, an optimization is possible |
8262 | | // where we avoid doing binary search for the keys b and c twice and instead |
8263 | | // somehow maintain state of where they first appear in the files. |
8264 | | std::optional<uint64_t> VersionSet::BlobSizeAccounting::GetReferencedBlobBytes( |
8265 | 0 | const FileMetaData& file) const { |
8266 | 0 | if (file.referenced_blob_bytes.has_value()) { |
8267 | 0 | return file.referenced_blob_bytes; |
8268 | 0 | } |
8269 | 0 | const auto captured = references.find(file.fd.GetNumber()); |
8270 | 0 | assert(captured != references.end()); |
8271 | 0 | return captured == references.end() ? std::nullopt : captured->second; |
8272 | 0 | } |
8273 | | |
8274 | | uint64_t VersionSet::ApproximateSize(const SizeApproximationOptions& options, |
8275 | | const ReadOptions& read_options, |
8276 | | Version* v, const Slice& start, |
8277 | | const Slice& end, int start_level, |
8278 | | int end_level, TableReaderCaller caller, |
8279 | 93.9k | BlobSizeAccounting* blob_accounting) { |
8280 | 93.9k | const auto& icmp = v->cfd_->internal_comparator(); |
8281 | | |
8282 | 93.9k | if (blob_accounting != nullptr) { |
8283 | 0 | blob_accounting->referenced_blob_bytes = 0; |
8284 | 0 | blob_accounting->unknown_sst_bytes = 0; |
8285 | 0 | } |
8286 | | |
8287 | 93.9k | const auto get_referenced_blob_bytes = |
8288 | 93.9k | [&](const FdWithKeyRange& file) -> std::optional<uint64_t> { |
8289 | 0 | assert(blob_accounting != nullptr); |
8290 | 0 | assert(file.file_metadata != nullptr); |
8291 | 0 | return blob_accounting->GetReferencedBlobBytes(*file.file_metadata); |
8292 | 0 | }; |
8293 | | |
8294 | 93.9k | auto account_file = [&](const FdWithKeyRange& file, uint64_t sst_bytes) { |
8295 | 0 | if (blob_accounting == nullptr) { |
8296 | 0 | return; |
8297 | 0 | } |
8298 | 0 | if (icmp.Compare(file.largest_key, start) <= 0 || |
8299 | 0 | icmp.Compare(file.smallest_key, end) > 0) { |
8300 | 0 | return; |
8301 | 0 | } |
8302 | 0 | const std::optional<uint64_t> file_referenced_blob_bytes = |
8303 | 0 | get_referenced_blob_bytes(file); |
8304 | 0 | if (!file_referenced_blob_bytes.has_value()) { |
8305 | 0 | blob_accounting->unknown_sst_bytes += sst_bytes; |
8306 | 0 | return; |
8307 | 0 | } |
8308 | 0 | if (sst_bytes == 0) { |
8309 | 0 | return; |
8310 | 0 | } |
8311 | 0 | const uint64_t file_size = file.fd.GetFileSize(); |
8312 | 0 | if (sst_bytes == file_size) { |
8313 | 0 | blob_accounting->referenced_blob_bytes += *file_referenced_blob_bytes; |
8314 | 0 | } else if (file_size > 0) { |
8315 | 0 | blob_accounting->referenced_blob_bytes += static_cast<uint64_t>( |
8316 | 0 | static_cast<double>(*file_referenced_blob_bytes) * |
8317 | 0 | static_cast<double>(sst_bytes) / static_cast<double>(file_size)); |
8318 | 0 | } |
8319 | 0 | }; |
8320 | | |
8321 | | // pre-condition |
8322 | 93.9k | assert(icmp.Compare(start, end) <= 0); |
8323 | | |
8324 | 93.9k | uint64_t total_full_size = 0; |
8325 | 93.9k | const auto* vstorage = v->storage_info(); |
8326 | 93.9k | const int num_non_empty_levels = vstorage->num_non_empty_levels(); |
8327 | 93.9k | end_level = (end_level == -1) ? num_non_empty_levels |
8328 | 93.9k | : std::min(end_level, num_non_empty_levels); |
8329 | 93.9k | if (end_level <= start_level) { |
8330 | 93.9k | return 0; |
8331 | 93.9k | } |
8332 | | |
8333 | | // Outline of the optimization that uses options.files_size_error_margin. |
8334 | | // When approximating the files total size that is used to store a keys |
8335 | | // range, we first sum up the sizes of the files that fully fall into the |
8336 | | // range. Then we sum up the sizes of all the files that may intersect with |
8337 | | // the range (this includes all files in L0 as well). Then, if |
8338 | | // total_intersecting_size is smaller than total_full_size * |
8339 | | // options.files_size_error_margin - we can infer that the intersecting |
8340 | | // files have a sufficiently negligible contribution to the total size, and |
8341 | | // we can approximate the storage required for the keys in range as just |
8342 | | // half of the intersecting_files_size. E.g., if the value of |
8343 | | // files_size_error_margin is 0.1, then the error of the approximation is |
8344 | | // limited to only ~10% of the total size of files that fully fall into the |
8345 | | // keys range. In such case, this helps to avoid a costly process of binary |
8346 | | // searching the intersecting files that is required only for a more precise |
8347 | | // calculation of the total size. |
8348 | | |
8349 | 0 | autovector<FdWithKeyRange*, 32> first_files; |
8350 | 0 | autovector<FdWithKeyRange*, 16> last_files; |
8351 | | |
8352 | | // scan all the levels |
8353 | 0 | for (int level = start_level; level < end_level; ++level) { |
8354 | 0 | const LevelFilesBrief& files_brief = vstorage->LevelFilesBrief(level); |
8355 | 0 | if (files_brief.num_files == 0) { |
8356 | | // empty level, skip exploration |
8357 | 0 | continue; |
8358 | 0 | } |
8359 | | |
8360 | 0 | if (level == 0) { |
8361 | | // level 0 files are not in sorted order, we need to iterate through |
8362 | | // the list to compute the total bytes that require scanning, |
8363 | | // so handle the case explicitly (similarly to first_files case) |
8364 | 0 | for (size_t i = 0; i < files_brief.num_files; i++) { |
8365 | 0 | first_files.push_back(&files_brief.files[i]); |
8366 | 0 | } |
8367 | 0 | continue; |
8368 | 0 | } |
8369 | | |
8370 | 0 | assert(level > 0); |
8371 | 0 | assert(files_brief.num_files > 0); |
8372 | | |
8373 | | // identify the file position for start key |
8374 | 0 | const int idx_start = |
8375 | 0 | FindFileInRange(icmp, files_brief, start, 0, |
8376 | 0 | static_cast<uint32_t>(files_brief.num_files - 1)); |
8377 | 0 | assert(static_cast<size_t>(idx_start) < files_brief.num_files); |
8378 | | |
8379 | | // identify the file position for end key |
8380 | 0 | int idx_end = idx_start; |
8381 | 0 | if (icmp.Compare(files_brief.files[idx_end].largest_key, end) < 0) { |
8382 | 0 | idx_end = |
8383 | 0 | FindFileInRange(icmp, files_brief, end, idx_start, |
8384 | 0 | static_cast<uint32_t>(files_brief.num_files - 1)); |
8385 | 0 | } |
8386 | 0 | assert(idx_end >= idx_start && |
8387 | 0 | static_cast<size_t>(idx_end) < files_brief.num_files); |
8388 | | |
8389 | | // scan all files from the starting index to the ending index |
8390 | | // (inferred from the sorted order) |
8391 | | |
8392 | | // first scan all the intermediate full files (excluding first and last) |
8393 | 0 | for (int i = idx_start + 1; i < idx_end; ++i) { |
8394 | 0 | uint64_t file_size = files_brief.files[i].fd.GetFileSize(); |
8395 | | // The entire file falls into the range, so we can just take its size. |
8396 | 0 | assert(file_size == ApproximateSize(read_options, v, files_brief.files[i], |
8397 | 0 | start, end, caller)); |
8398 | 0 | total_full_size += file_size; |
8399 | 0 | account_file(files_brief.files[i], file_size); |
8400 | 0 | } |
8401 | | |
8402 | | // save the first and the last files (which may be the same file), so we |
8403 | | // can scan them later. |
8404 | 0 | first_files.push_back(&files_brief.files[idx_start]); |
8405 | 0 | if (idx_start != idx_end) { |
8406 | | // we need to estimate size for both files, only if they are different |
8407 | 0 | last_files.push_back(&files_brief.files[idx_end]); |
8408 | 0 | } |
8409 | 0 | } |
8410 | | |
8411 | | // The sum of all file sizes that intersect the [start, end] keys range. |
8412 | 0 | uint64_t total_intersecting_size = 0; |
8413 | 0 | uint64_t intersecting_referenced_blob_bytes = 0; |
8414 | 0 | uint64_t intersecting_unknown_sst_bytes = 0; |
8415 | 0 | auto account_intersecting_file = [&](const FdWithKeyRange& file) { |
8416 | 0 | const uint64_t file_size = file.fd.GetFileSize(); |
8417 | 0 | total_intersecting_size += file_size; |
8418 | 0 | if (blob_accounting == nullptr) { |
8419 | 0 | return; |
8420 | 0 | } |
8421 | 0 | if (icmp.Compare(file.largest_key, start) <= 0 || |
8422 | 0 | icmp.Compare(file.smallest_key, end) > 0) { |
8423 | 0 | return; |
8424 | 0 | } |
8425 | 0 | const std::optional<uint64_t> file_referenced_blob_bytes = |
8426 | 0 | get_referenced_blob_bytes(file); |
8427 | 0 | if (file_referenced_blob_bytes.has_value()) { |
8428 | 0 | intersecting_referenced_blob_bytes += *file_referenced_blob_bytes; |
8429 | 0 | } else { |
8430 | 0 | intersecting_unknown_sst_bytes += file_size; |
8431 | 0 | } |
8432 | 0 | }; |
8433 | 0 | for (const auto* file_ptr : first_files) { |
8434 | 0 | account_intersecting_file(*file_ptr); |
8435 | 0 | } |
8436 | 0 | for (const auto* file_ptr : last_files) { |
8437 | 0 | account_intersecting_file(*file_ptr); |
8438 | 0 | } |
8439 | 0 | TEST_SYNC_POINT_CALLBACK( |
8440 | 0 | "VersionSet::ApproximateSize:IntersectingReferencedBlobBytes", |
8441 | 0 | &intersecting_referenced_blob_bytes); |
8442 | | |
8443 | | // Now scan all the first & last files at each level, and estimate their |
8444 | | // size. If the total_intersecting_size is less than X% of the |
8445 | | // total_full_size - we want to approximate the result in order to avoid the |
8446 | | // costly binary search inside ApproximateSize. We use half of file size as |
8447 | | // an approximation below. |
8448 | |
|
8449 | 0 | const double margin = options.files_size_error_margin; |
8450 | 0 | if (margin > 0 && total_intersecting_size < |
8451 | 0 | static_cast<uint64_t>(total_full_size * margin)) { |
8452 | 0 | total_full_size += total_intersecting_size / 2; |
8453 | 0 | if (blob_accounting != nullptr) { |
8454 | 0 | blob_accounting->referenced_blob_bytes += |
8455 | 0 | intersecting_referenced_blob_bytes / 2; |
8456 | 0 | blob_accounting->unknown_sst_bytes += intersecting_unknown_sst_bytes / 2; |
8457 | 0 | } |
8458 | 0 | } else { |
8459 | | // Estimate for all the first files (might also be last files), at each |
8460 | | // level |
8461 | 0 | for (const auto file_ptr : first_files) { |
8462 | 0 | const uint64_t file_size = |
8463 | 0 | ApproximateSize(read_options, v, *file_ptr, start, end, caller); |
8464 | 0 | total_full_size += file_size; |
8465 | 0 | account_file(*file_ptr, file_size); |
8466 | 0 | } |
8467 | | |
8468 | | // Estimate for all the last files, at each level |
8469 | 0 | for (const auto file_ptr : last_files) { |
8470 | | // We could use ApproximateSize here, but calling ApproximateOffsetOf |
8471 | | // directly is just more efficient. |
8472 | 0 | const uint64_t file_size = |
8473 | 0 | ApproximateOffsetOf(read_options, v, *file_ptr, end, caller); |
8474 | 0 | total_full_size += file_size; |
8475 | 0 | account_file(*file_ptr, file_size); |
8476 | 0 | } |
8477 | 0 | } |
8478 | |
|
8479 | 0 | return total_full_size; |
8480 | 93.9k | } |
8481 | | |
8482 | | uint64_t VersionSet::ApproximateOffsetOf(const ReadOptions& read_options, |
8483 | | Version* v, const FdWithKeyRange& f, |
8484 | | const Slice& key, |
8485 | 0 | TableReaderCaller caller) { |
8486 | | // pre-condition |
8487 | 0 | assert(v); |
8488 | 0 | const auto& icmp = v->cfd_->internal_comparator(); |
8489 | |
|
8490 | 0 | uint64_t result = 0; |
8491 | 0 | if (icmp.Compare(f.largest_key, key) <= 0) { |
8492 | | // Entire file is before "key", so just add the file size |
8493 | 0 | result = f.fd.GetFileSize(); |
8494 | 0 | } else if (icmp.Compare(f.smallest_key, key) > 0) { |
8495 | | // Entire file is after "key", so ignore |
8496 | 0 | result = 0; |
8497 | 0 | } else { |
8498 | | // "key" falls in the range for this table. Add the |
8499 | | // approximate offset of "key" within the table. |
8500 | 0 | TableCache* table_cache = v->cfd_->table_cache(); |
8501 | 0 | const MutableCFOptions& cf_opts = v->GetMutableCFOptions(); |
8502 | 0 | if (table_cache != nullptr) { |
8503 | 0 | result = table_cache->ApproximateOffsetOf( |
8504 | 0 | read_options, key, *f.file_metadata, caller, icmp, cf_opts); |
8505 | 0 | } |
8506 | 0 | } |
8507 | 0 | return result; |
8508 | 0 | } |
8509 | | |
8510 | | uint64_t VersionSet::ApproximateSize(const ReadOptions& read_options, |
8511 | | Version* v, const FdWithKeyRange& f, |
8512 | | const Slice& start, const Slice& end, |
8513 | 0 | TableReaderCaller caller) { |
8514 | | // pre-condition |
8515 | 0 | assert(v); |
8516 | 0 | const auto& icmp = v->cfd_->internal_comparator(); |
8517 | 0 | assert(icmp.Compare(start, end) <= 0); |
8518 | |
|
8519 | 0 | if (icmp.Compare(f.largest_key, start) <= 0 || |
8520 | 0 | icmp.Compare(f.smallest_key, end) > 0) { |
8521 | | // Entire file is before or after the start/end keys range |
8522 | 0 | return 0; |
8523 | 0 | } |
8524 | | |
8525 | 0 | if (icmp.Compare(f.smallest_key, start) >= 0) { |
8526 | | // Start of the range is before the file start - approximate by end offset |
8527 | 0 | return ApproximateOffsetOf(read_options, v, f, end, caller); |
8528 | 0 | } |
8529 | | |
8530 | 0 | if (icmp.Compare(f.largest_key, end) < 0) { |
8531 | | // End of the range is after the file end - approximate by subtracting |
8532 | | // start offset from the file size |
8533 | 0 | uint64_t start_offset = |
8534 | 0 | ApproximateOffsetOf(read_options, v, f, start, caller); |
8535 | 0 | assert(f.fd.GetFileSize() >= start_offset); |
8536 | 0 | return f.fd.GetFileSize() - start_offset; |
8537 | 0 | } |
8538 | | |
8539 | | // The interval falls entirely in the range for this file. |
8540 | 0 | TableCache* table_cache = v->cfd_->table_cache(); |
8541 | 0 | if (table_cache == nullptr) { |
8542 | 0 | return 0; |
8543 | 0 | } |
8544 | 0 | const MutableCFOptions& cf_opts = v->GetMutableCFOptions(); |
8545 | 0 | return table_cache->ApproximateSize(read_options, start, end, |
8546 | 0 | *f.file_metadata, caller, icmp, cf_opts); |
8547 | 0 | } |
8548 | | |
8549 | | void VersionSet::RemoveLiveFiles( |
8550 | | std::vector<ObsoleteFileInfo>& sst_delete_candidates, |
8551 | 23.4k | std::vector<ObsoleteBlobFileInfo>& blob_delete_candidates) const { |
8552 | 23.4k | assert(column_family_set_); |
8553 | 24.5k | for (auto cfd : *column_family_set_) { |
8554 | 24.5k | assert(cfd); |
8555 | 24.5k | if (!cfd->initialized()) { |
8556 | 0 | continue; |
8557 | 0 | } |
8558 | | |
8559 | 24.5k | auto* current = cfd->current(); |
8560 | 24.5k | bool found_current = false; |
8561 | | |
8562 | 24.5k | Version* const dummy_versions = cfd->dummy_versions(); |
8563 | 24.5k | assert(dummy_versions); |
8564 | | |
8565 | 49.3k | for (Version* v = dummy_versions->next_; v != dummy_versions; |
8566 | 24.7k | v = v->next_) { |
8567 | 24.7k | v->RemoveLiveFiles(sst_delete_candidates, blob_delete_candidates); |
8568 | 24.7k | if (v == current) { |
8569 | 24.5k | found_current = true; |
8570 | 24.5k | } |
8571 | 24.7k | } |
8572 | | |
8573 | 24.5k | if (!found_current && current != nullptr) { |
8574 | | // Should never happen unless it is a bug. |
8575 | 0 | assert(false); |
8576 | 0 | current->RemoveLiveFiles(sst_delete_candidates, blob_delete_candidates); |
8577 | 0 | } |
8578 | 24.5k | } |
8579 | 23.4k | } |
8580 | | |
8581 | | void VersionSet::AddLiveFiles(std::vector<uint64_t>* live_table_files, |
8582 | 89.7k | std::vector<uint64_t>* live_blob_files) const { |
8583 | 89.7k | assert(live_table_files); |
8584 | 89.7k | assert(live_blob_files); |
8585 | | |
8586 | | // pre-calculate space requirement |
8587 | 89.7k | size_t total_table_files = 0; |
8588 | 89.7k | size_t total_blob_files = 0; |
8589 | | |
8590 | 89.7k | assert(column_family_set_); |
8591 | 121k | for (auto cfd : *column_family_set_) { |
8592 | 121k | assert(cfd); |
8593 | | |
8594 | 121k | if (!cfd->initialized()) { |
8595 | 0 | continue; |
8596 | 0 | } |
8597 | | |
8598 | 121k | Version* const dummy_versions = cfd->dummy_versions(); |
8599 | 121k | assert(dummy_versions); |
8600 | | |
8601 | 242k | for (Version* v = dummy_versions->next_; v != dummy_versions; |
8602 | 121k | v = v->next_) { |
8603 | 121k | assert(v); |
8604 | | |
8605 | 121k | const auto* vstorage = v->storage_info(); |
8606 | 121k | assert(vstorage); |
8607 | | |
8608 | 968k | for (int level = 0; level < vstorage->num_levels(); ++level) { |
8609 | 847k | total_table_files += vstorage->LevelFiles(level).size(); |
8610 | 847k | } |
8611 | | |
8612 | 121k | total_blob_files += vstorage->GetBlobFiles().size(); |
8613 | 121k | } |
8614 | 121k | } |
8615 | | |
8616 | | // just one time extension to the right size |
8617 | 89.7k | live_table_files->reserve(live_table_files->size() + total_table_files); |
8618 | 89.7k | live_blob_files->reserve(live_blob_files->size() + total_blob_files); |
8619 | | |
8620 | 89.7k | assert(column_family_set_); |
8621 | 121k | for (auto cfd : *column_family_set_) { |
8622 | 121k | assert(cfd); |
8623 | 121k | if (!cfd->initialized()) { |
8624 | 0 | continue; |
8625 | 0 | } |
8626 | | |
8627 | 121k | auto* current = cfd->current(); |
8628 | 121k | bool found_current = false; |
8629 | | |
8630 | 121k | Version* const dummy_versions = cfd->dummy_versions(); |
8631 | 121k | assert(dummy_versions); |
8632 | | |
8633 | 242k | for (Version* v = dummy_versions->next_; v != dummy_versions; |
8634 | 121k | v = v->next_) { |
8635 | 121k | v->AddLiveFiles(live_table_files, live_blob_files); |
8636 | 121k | if (v == current) { |
8637 | 121k | found_current = true; |
8638 | 121k | } |
8639 | 121k | } |
8640 | | |
8641 | 121k | if (!found_current && current != nullptr) { |
8642 | | // Should never happen unless it is a bug. |
8643 | 0 | assert(false); |
8644 | 0 | current->AddLiveFiles(live_table_files, live_blob_files); |
8645 | 0 | } |
8646 | 121k | } |
8647 | 89.7k | } |
8648 | | |
8649 | | InternalIterator* VersionSet::MakeInputIterator( |
8650 | | const ReadOptions& read_options, const Compaction* c, |
8651 | | RangeDelAggregator* range_del_agg, |
8652 | | const FileOptions& file_options_compactions, |
8653 | | const std::optional<const Slice>& start, |
8654 | 2.77k | const std::optional<const Slice>& end, bool open_ephemeral_table_reader) { |
8655 | 2.77k | auto cfd = c->column_family_data(); |
8656 | | // Level-0 files have to be merged together. For other levels, |
8657 | | // we will make a concatenating iterator per level. |
8658 | | // TODO(opt): use concatenating iterator for level-0 if there is no overlap |
8659 | 2.77k | const size_t space = (c->level() == 0 ? c->input_levels(0)->num_files + |
8660 | 2.36k | c->num_input_levels() - 1 |
8661 | 2.77k | : c->num_input_levels()); |
8662 | 2.77k | InternalIterator** list = new InternalIterator*[space]; |
8663 | | // First item in the pair is a pointer to range tombstones. |
8664 | | // Second item is a pointer to a member of a LevelIterator, |
8665 | | // that will be initialized to where CompactionMergingIterator stores |
8666 | | // pointer to its range tombstones. This is used by LevelIterator |
8667 | | // to update pointer to range tombstones as it traverse different SST files. |
8668 | 2.77k | std::vector<std::pair<std::unique_ptr<TruncatedRangeDelIterator>, |
8669 | 2.77k | std::unique_ptr<TruncatedRangeDelIterator>**>> |
8670 | 2.77k | range_tombstones; |
8671 | 2.77k | size_t num = 0; |
8672 | 2.77k | [[maybe_unused]] size_t num_input_files = 0; |
8673 | 2.77k | const TableCacheOpenOptions table_cache_open_options = |
8674 | 2.77k | GetCompactionTableCacheOpenOptions(open_ephemeral_table_reader); |
8675 | 7.19k | for (size_t which = 0; which < c->num_input_levels(); which++) { |
8676 | 4.41k | const LevelFilesBrief* flevel = c->input_levels(which); |
8677 | 4.41k | num_input_files += flevel->num_files; |
8678 | 4.41k | if (flevel->num_files != 0) { |
8679 | 4.41k | if (c->level(which) == 0) { |
8680 | 9.52k | for (size_t i = 0; i < flevel->num_files; i++) { |
8681 | 7.15k | const FileMetaData& fmd = *flevel->files[i].file_metadata; |
8682 | 7.15k | if (start.has_value() && |
8683 | 0 | cfd->user_comparator()->CompareWithoutTimestamp( |
8684 | 0 | *start, fmd.largest.user_key()) > 0) { |
8685 | 0 | continue; |
8686 | 0 | } |
8687 | | // We should be able to filter out the case where the end key |
8688 | | // equals to the end boundary, since the end key is exclusive. |
8689 | | // We try to be extra safe here. |
8690 | 7.15k | if (end.has_value() && |
8691 | 0 | cfd->user_comparator()->CompareWithoutTimestamp( |
8692 | 0 | *end, fmd.smallest.user_key()) < 0) { |
8693 | 0 | continue; |
8694 | 0 | } |
8695 | 7.15k | std::unique_ptr<TruncatedRangeDelIterator> range_tombstone_iter = |
8696 | 7.15k | nullptr; |
8697 | 7.15k | list[num++] = cfd->table_cache()->NewIterator( |
8698 | 7.15k | read_options, file_options_compactions, |
8699 | 7.15k | cfd->internal_comparator(), fmd, range_del_agg, |
8700 | 7.15k | c->mutable_cf_options(), |
8701 | 7.15k | /*table_reader_ptr=*/nullptr, |
8702 | 7.15k | /*file_read_hist=*/nullptr, TableReaderCaller::kCompaction, |
8703 | 7.15k | /*arena=*/nullptr, |
8704 | 7.15k | /*skip_filters=*/false, |
8705 | 7.15k | /*level=*/static_cast<int>(c->level(which)), |
8706 | 7.15k | MaxFileSizeForL0MetaPin(c->mutable_cf_options()), |
8707 | 7.15k | /*smallest_compaction_key=*/nullptr, |
8708 | 7.15k | /*largest_compaction_key=*/nullptr, |
8709 | 7.15k | /*allow_unprepared_value=*/false, /*seek_target=*/nullptr, |
8710 | 7.15k | /*prefix_filtered_table=*/nullptr, |
8711 | 7.15k | /*range_del_read_seqno=*/nullptr, |
8712 | 7.15k | /*range_del_iter=*/&range_tombstone_iter, |
8713 | 7.15k | /*maybe_pin_table_handle=*/false, |
8714 | 7.15k | /*file_open_metadata=*/nullptr, table_cache_open_options); |
8715 | 7.15k | range_tombstones.emplace_back(std::move(range_tombstone_iter), |
8716 | 7.15k | nullptr); |
8717 | 7.15k | } |
8718 | 2.36k | } else { |
8719 | | // Create concatenating iterator for the files from this level |
8720 | 2.05k | std::unique_ptr<TruncatedRangeDelIterator>** tombstone_iter_ptr = |
8721 | 2.05k | nullptr; |
8722 | 2.05k | list[num++] = new LevelIterator( |
8723 | 2.05k | cfd->table_cache(), read_options, file_options_compactions, |
8724 | 2.05k | cfd->internal_comparator(), flevel, c->mutable_cf_options(), |
8725 | 2.05k | /*no per level latency histogram=*/nullptr, |
8726 | 2.05k | TableReaderCaller::kCompaction, /*skip_filters=*/false, |
8727 | 2.05k | /*level=*/static_cast<int>(c->level(which)), range_del_agg, |
8728 | 2.05k | c->boundaries(which), false, &tombstone_iter_ptr, |
8729 | 2.05k | kMaxSequenceNumber, db_options_->statistics.get(), clock_, |
8730 | 2.05k | open_ephemeral_table_reader); |
8731 | 2.05k | range_tombstones.emplace_back(nullptr, tombstone_iter_ptr); |
8732 | 2.05k | } |
8733 | 4.41k | } |
8734 | 4.41k | } |
8735 | 2.77k | TEST_SYNC_POINT_CALLBACK( |
8736 | 2.77k | "VersionSet::MakeInputIterator:NewCompactionMergingIterator", |
8737 | 2.77k | &num_input_files); |
8738 | 2.77k | assert(num <= space); |
8739 | 2.77k | InternalIterator* result = NewCompactionMergingIterator( |
8740 | 2.77k | &c->column_family_data()->internal_comparator(), list, |
8741 | 2.77k | static_cast<int>(num), range_tombstones, /*arena=*/nullptr, |
8742 | 2.77k | c->column_family_data()->internal_stats()); |
8743 | 2.77k | delete[] list; |
8744 | 2.77k | return result; |
8745 | 2.77k | } |
8746 | | |
8747 | | Status VersionSet::GetMetadataForFile(uint64_t number, int* filelevel, |
8748 | | FileMetaData** meta, |
8749 | 0 | ColumnFamilyData** cfd) { |
8750 | 0 | for (auto cfd_iter : *column_family_set_) { |
8751 | 0 | if (!cfd_iter->initialized()) { |
8752 | 0 | continue; |
8753 | 0 | } |
8754 | 0 | Version* version = cfd_iter->current(); |
8755 | 0 | const auto* vstorage = version->storage_info(); |
8756 | 0 | for (int level = 0; level < vstorage->num_levels(); level++) { |
8757 | 0 | for (const auto& file : vstorage->LevelFiles(level)) { |
8758 | 0 | if (file->fd.GetNumber() == number) { |
8759 | 0 | *meta = file; |
8760 | 0 | *filelevel = level; |
8761 | 0 | *cfd = cfd_iter; |
8762 | 0 | return Status::OK(); |
8763 | 0 | } |
8764 | 0 | } |
8765 | 0 | } |
8766 | 0 | } |
8767 | 0 | return Status::NotFound("File not present in any level"); |
8768 | 0 | } |
8769 | | |
8770 | 0 | void VersionSet::GetLiveFilesMetaData(std::vector<LiveFileMetaData>* metadata) { |
8771 | 0 | if (!metadata) { |
8772 | 0 | return; |
8773 | 0 | } |
8774 | 0 | assert(metadata); |
8775 | 0 | size_t count = 0; |
8776 | 0 | for (auto cfd : *column_family_set_) { |
8777 | 0 | if (cfd->IsDropped() || !cfd->initialized()) { |
8778 | 0 | continue; |
8779 | 0 | } |
8780 | 0 | for (int level = 0; level < cfd->NumberLevels(); level++) { |
8781 | 0 | count += cfd->current()->storage_info()->LevelFiles(level).size(); |
8782 | 0 | } |
8783 | 0 | } |
8784 | 0 | metadata->reserve(count); |
8785 | 0 | for (auto cfd : *column_family_set_) { |
8786 | 0 | if (cfd->IsDropped() || !cfd->initialized()) { |
8787 | 0 | continue; |
8788 | 0 | } |
8789 | 0 | for (int level = 0; level < cfd->NumberLevels(); level++) { |
8790 | 0 | for (const auto& file : |
8791 | 0 | cfd->current()->storage_info()->LevelFiles(level)) { |
8792 | 0 | LiveFileMetaData filemetadata; |
8793 | 0 | filemetadata.column_family_name = cfd->GetName(); |
8794 | 0 | uint32_t path_id = file->fd.GetPathId(); |
8795 | 0 | if (path_id < cfd->ioptions().cf_paths.size()) { |
8796 | 0 | filemetadata.db_path = cfd->ioptions().cf_paths[path_id].path; |
8797 | 0 | } else { |
8798 | 0 | assert(!cfd->ioptions().cf_paths.empty()); |
8799 | 0 | filemetadata.db_path = cfd->ioptions().cf_paths.back().path; |
8800 | 0 | } |
8801 | 0 | filemetadata.directory = filemetadata.db_path; |
8802 | 0 | const uint64_t file_number = file->fd.GetNumber(); |
8803 | 0 | filemetadata.name = MakeTableFileName("", file_number); |
8804 | 0 | filemetadata.relative_filename = filemetadata.name.substr(1); |
8805 | 0 | filemetadata.file_number = file_number; |
8806 | 0 | filemetadata.level = level; |
8807 | 0 | filemetadata.size = file->fd.GetFileSize(); |
8808 | 0 | filemetadata.smallestkey = file->smallest.user_key().ToString(); |
8809 | 0 | filemetadata.largestkey = file->largest.user_key().ToString(); |
8810 | 0 | filemetadata.smallest_seqno = file->fd.smallest_seqno; |
8811 | 0 | filemetadata.largest_seqno = file->fd.largest_seqno; |
8812 | 0 | filemetadata.num_reads_sampled = |
8813 | 0 | file->stats.num_reads_sampled.load(std::memory_order_relaxed); |
8814 | 0 | filemetadata.being_compacted = file->being_compacted; |
8815 | 0 | filemetadata.num_entries = file->num_entries; |
8816 | 0 | filemetadata.num_deletions = file->num_deletions; |
8817 | 0 | filemetadata.oldest_blob_file_number = file->oldest_blob_file_number; |
8818 | 0 | filemetadata.file_checksum = file->file_checksum; |
8819 | 0 | filemetadata.file_checksum_func_name = file->file_checksum_func_name; |
8820 | 0 | filemetadata.temperature = file->temperature; |
8821 | 0 | filemetadata.oldest_ancestor_time = file->TryGetOldestAncestorTime(); |
8822 | 0 | filemetadata.file_creation_time = file->TryGetFileCreationTime(); |
8823 | 0 | filemetadata.db_ownership_start_time = file->db_ownership_start_time; |
8824 | 0 | filemetadata.data_file_provenance = file->data_file_provenance; |
8825 | 0 | filemetadata.epoch_number = file->epoch_number; |
8826 | 0 | metadata->push_back(filemetadata); |
8827 | 0 | } |
8828 | 0 | } |
8829 | 0 | } |
8830 | 0 | } |
8831 | | |
8832 | | void VersionSet::GetObsoleteFiles(std::vector<ObsoleteFileInfo>* files, |
8833 | | std::vector<ObsoleteBlobFileInfo>* blob_files, |
8834 | | std::vector<std::string>* manifest_filenames, |
8835 | 113k | uint64_t min_pending_output) { |
8836 | 113k | assert(files); |
8837 | 113k | assert(blob_files); |
8838 | 113k | assert(manifest_filenames); |
8839 | 113k | assert(files->empty()); |
8840 | 113k | assert(blob_files->empty()); |
8841 | 113k | assert(manifest_filenames->empty()); |
8842 | | |
8843 | 113k | std::vector<ObsoleteFileInfo> pending_files; |
8844 | 113k | for (auto& f : obsolete_files_) { |
8845 | 11.9k | if (f.metadata->fd.GetNumber() < min_pending_output) { |
8846 | 11.9k | files->emplace_back(std::move(f)); |
8847 | 11.9k | } else { |
8848 | 0 | pending_files.emplace_back(std::move(f)); |
8849 | 0 | } |
8850 | 11.9k | } |
8851 | 113k | obsolete_files_.swap(pending_files); |
8852 | | |
8853 | 113k | std::vector<ObsoleteBlobFileInfo> pending_blob_files; |
8854 | 113k | for (auto& blob_file : obsolete_blob_files_) { |
8855 | 0 | if (blob_file.GetBlobFileNumber() < min_pending_output) { |
8856 | 0 | blob_files->emplace_back(std::move(blob_file)); |
8857 | 0 | } else { |
8858 | 0 | pending_blob_files.emplace_back(std::move(blob_file)); |
8859 | 0 | } |
8860 | 0 | } |
8861 | 113k | obsolete_blob_files_.swap(pending_blob_files); |
8862 | | |
8863 | 113k | obsolete_manifests_.swap(*manifest_filenames); |
8864 | 113k | } |
8865 | | |
8866 | 0 | uint64_t VersionSet::GetObsoleteSstFilesSize() const { |
8867 | 0 | uint64_t ret = 0; |
8868 | 0 | for (auto& f : obsolete_files_) { |
8869 | 0 | if (f.metadata != nullptr) { |
8870 | 0 | ret += f.metadata->fd.GetFileSize(); |
8871 | 0 | } |
8872 | 0 | } |
8873 | 0 | return ret; |
8874 | 0 | } |
8875 | | |
8876 | | ColumnFamilyData* VersionSet::CreateColumnFamily( |
8877 | | const ColumnFamilyOptions& cf_options, const ReadOptions& read_options, |
8878 | 87.5k | const VersionEdit* edit, bool read_only, bool options_are_sanitized) { |
8879 | 87.5k | assert(edit->IsColumnFamilyAdd()); |
8880 | | // Unchanging LSM tree implies no writes to the CF |
8881 | 87.5k | assert(!unchanging_ || read_only); |
8882 | | |
8883 | 87.5k | MutableCFOptions dummy_cf_options; |
8884 | 87.5k | Version* dummy_versions = |
8885 | 87.5k | new Version(nullptr, this, file_options_, dummy_cf_options, io_tracer_); |
8886 | | // Ref() dummy version once so that later we can call Unref() to delete it |
8887 | | // by avoiding calling "delete" explicitly (~Version is private) |
8888 | 87.5k | dummy_versions->Ref(); |
8889 | 87.5k | auto new_cfd = column_family_set_->CreateColumnFamily( |
8890 | 87.5k | edit->GetColumnFamilyName(), edit->GetColumnFamily(), dummy_versions, |
8891 | 87.5k | cf_options, read_only, options_are_sanitized); |
8892 | | |
8893 | 87.5k | Version* v = new Version(new_cfd, this, file_options_, |
8894 | 87.5k | new_cfd->GetLatestMutableCFOptions(), io_tracer_, |
8895 | 87.5k | current_version_number_++); |
8896 | | |
8897 | 87.5k | constexpr bool update_stats = false; |
8898 | | |
8899 | 87.5k | v->PrepareAppend(read_options, update_stats); |
8900 | | |
8901 | 87.5k | AppendVersion(new_cfd, v); |
8902 | | // GetLatestMutableCFOptions() is safe here without mutex since the |
8903 | | // cfd is not available to client |
8904 | 87.5k | new_cfd->CreateNewMemtable(LastSequence()); |
8905 | 87.5k | new_cfd->SetLogNumber(edit->GetLogNumber()); |
8906 | 87.5k | return new_cfd; |
8907 | 87.5k | } |
8908 | | |
8909 | 0 | uint64_t VersionSet::GetNumLiveVersions(Version* dummy_versions) { |
8910 | 0 | uint64_t count = 0; |
8911 | 0 | for (Version* v = dummy_versions->next_; v != dummy_versions; v = v->next_) { |
8912 | 0 | count++; |
8913 | 0 | } |
8914 | 0 | return count; |
8915 | 0 | } |
8916 | | |
8917 | 0 | uint64_t VersionSet::GetTotalSstFilesSize(Version* dummy_versions) { |
8918 | 0 | std::unordered_set<uint64_t> unique_files; |
8919 | 0 | uint64_t total_files_size = 0; |
8920 | 0 | for (Version* v = dummy_versions->next_; v != dummy_versions; v = v->next_) { |
8921 | 0 | VersionStorageInfo* storage_info = v->storage_info(); |
8922 | 0 | for (int level = 0; level < storage_info->num_levels_; level++) { |
8923 | 0 | for (const auto& file_meta : storage_info->LevelFiles(level)) { |
8924 | 0 | if (unique_files.find(file_meta->fd.packed_number_and_path_id) == |
8925 | 0 | unique_files.end()) { |
8926 | 0 | unique_files.insert(file_meta->fd.packed_number_and_path_id); |
8927 | 0 | total_files_size += file_meta->fd.GetFileSize(); |
8928 | 0 | } |
8929 | 0 | } |
8930 | 0 | } |
8931 | 0 | } |
8932 | 0 | return total_files_size; |
8933 | 0 | } |
8934 | | |
8935 | 0 | uint64_t VersionSet::GetTotalBlobFileSize(Version* dummy_versions) { |
8936 | 0 | std::unordered_set<uint64_t> unique_blob_files; |
8937 | |
|
8938 | 0 | uint64_t all_versions_blob_file_size = 0; |
8939 | |
|
8940 | 0 | for (auto* v = dummy_versions->next_; v != dummy_versions; v = v->next_) { |
8941 | | // iterate all the versions |
8942 | 0 | const auto* vstorage = v->storage_info(); |
8943 | 0 | assert(vstorage); |
8944 | |
|
8945 | 0 | const auto& blob_files = vstorage->GetBlobFiles(); |
8946 | |
|
8947 | 0 | for (const auto& meta : blob_files) { |
8948 | 0 | assert(meta); |
8949 | |
|
8950 | 0 | const uint64_t blob_file_number = meta->GetBlobFileNumber(); |
8951 | |
|
8952 | 0 | if (unique_blob_files.find(blob_file_number) == unique_blob_files.end()) { |
8953 | | // find Blob file that has not been counted |
8954 | 0 | unique_blob_files.insert(blob_file_number); |
8955 | 0 | all_versions_blob_file_size += meta->GetBlobFileSize(); |
8956 | 0 | } |
8957 | 0 | } |
8958 | 0 | } |
8959 | |
|
8960 | 0 | return all_versions_blob_file_size; |
8961 | 0 | } |
8962 | | |
8963 | | Status VersionSet::VerifyFileMetadata(const ReadOptions& read_options, |
8964 | | ColumnFamilyData* cfd, |
8965 | | const std::string& fpath, int level, |
8966 | 0 | const FileMetaData& meta) { |
8967 | 0 | uint64_t fsize = 0; |
8968 | 0 | Status status = fs_->GetFileSize(fpath, IOOptions(), &fsize, nullptr); |
8969 | 0 | if (status.ok()) { |
8970 | 0 | if (fsize != meta.fd.GetFileSize()) { |
8971 | 0 | status = Status::Corruption("File size mismatch: " + fpath); |
8972 | 0 | } |
8973 | 0 | } |
8974 | 0 | if (status.ok() && db_options_->verify_sst_unique_id_in_manifest) { |
8975 | 0 | assert(cfd); |
8976 | 0 | TableCache* table_cache = cfd->table_cache(); |
8977 | 0 | assert(table_cache); |
8978 | |
|
8979 | 0 | const auto& cf_opts = cfd->GetLatestMutableCFOptions(); |
8980 | 0 | size_t max_sz_for_l0_meta_pin = MaxFileSizeForL0MetaPin(cf_opts); |
8981 | |
|
8982 | 0 | const FileOptions& file_opts = file_options(); |
8983 | |
|
8984 | 0 | Version* version = cfd->current(); |
8985 | 0 | assert(version); |
8986 | 0 | VersionStorageInfo& storage_info = version->storage_info_; |
8987 | 0 | const InternalKeyComparator* icmp = storage_info.InternalComparator(); |
8988 | 0 | assert(icmp); |
8989 | |
|
8990 | 0 | InternalStats* internal_stats = cfd->internal_stats(); |
8991 | |
|
8992 | 0 | TableCache::TypedHandle* handle = nullptr; |
8993 | 0 | TableReader* table_reader = nullptr; |
8994 | 0 | status = table_cache->FindTable( |
8995 | 0 | read_options, file_opts, *icmp, meta, &handle, cf_opts, &table_reader, |
8996 | 0 | /*no_io=*/false, internal_stats->GetFileReadHist(level), false, level, |
8997 | 0 | /*prefetch_index_and_filter_in_cache*/ false, max_sz_for_l0_meta_pin, |
8998 | 0 | meta.temperature); |
8999 | 0 | if (handle) { |
9000 | 0 | table_cache->get_cache().Release(handle); |
9001 | 0 | } |
9002 | 0 | } |
9003 | 0 | return status; |
9004 | 0 | } |
9005 | | |
9006 | | ReactiveVersionSet::ReactiveVersionSet( |
9007 | | const std::string& dbname, const ImmutableDBOptions* imm_db_options, |
9008 | | const MutableDBOptions& mutable_db_options, |
9009 | | const FileOptions& _file_options, Cache* table_cache, |
9010 | | WriteBufferManager* write_buffer_manager, WriteController* write_controller, |
9011 | | const std::shared_ptr<IOTracer>& io_tracer, const std::string& db_id, |
9012 | | const std::string& db_session_id, bool defer_sst_file_opening) |
9013 | 0 | : VersionSet(dbname, imm_db_options, mutable_db_options, _file_options, |
9014 | 0 | table_cache, write_buffer_manager, write_controller, |
9015 | 0 | /*block_cache_tracer=*/nullptr, io_tracer, db_id, |
9016 | 0 | db_session_id, /*daily_offpeak_time_utc*/ "", |
9017 | 0 | /*error_handler=*/nullptr, /*unchanging=*/false), |
9018 | 0 | defer_sst_file_opening_(defer_sst_file_opening) {} |
9019 | | |
9020 | 0 | ReactiveVersionSet::~ReactiveVersionSet() = default; |
9021 | | |
9022 | | Status ReactiveVersionSet::Recover( |
9023 | | const std::vector<ColumnFamilyDescriptor>& column_families, |
9024 | | std::unique_ptr<log::FragmentBufferedReader>* manifest_reader, |
9025 | | std::unique_ptr<log::Reader::Reporter>* manifest_reporter, |
9026 | 0 | std::unique_ptr<Status>* manifest_reader_status) { |
9027 | 0 | assert(manifest_reader != nullptr); |
9028 | 0 | assert(manifest_reporter != nullptr); |
9029 | 0 | assert(manifest_reader_status != nullptr); |
9030 | |
|
9031 | 0 | manifest_reader_status->reset(new Status()); |
9032 | 0 | manifest_reporter->reset(new LogReporter()); |
9033 | 0 | static_cast_with_check<LogReporter>(manifest_reporter->get())->status = |
9034 | 0 | manifest_reader_status->get(); |
9035 | 0 | Status s = MaybeSwitchManifest(manifest_reporter->get(), manifest_reader); |
9036 | 0 | if (!s.ok()) { |
9037 | 0 | return s; |
9038 | 0 | } |
9039 | 0 | log::Reader* reader = manifest_reader->get(); |
9040 | 0 | assert(reader); |
9041 | |
|
9042 | 0 | manifest_tailer_.reset(new ManifestTailer( |
9043 | 0 | column_families, const_cast<ReactiveVersionSet*>(this), io_tracer_, |
9044 | 0 | read_options_, trust_manifest_recovery_, defer_sst_file_opening_, |
9045 | 0 | EpochNumberRequirement::kMightMissing)); |
9046 | |
|
9047 | 0 | manifest_tailer_->Iterate(*reader, manifest_reader_status->get()); |
9048 | |
|
9049 | 0 | s = manifest_tailer_->status(); |
9050 | 0 | if (s.ok()) { |
9051 | 0 | RecoverEpochNumbers(); |
9052 | 0 | } |
9053 | 0 | return s; |
9054 | 0 | } |
9055 | | |
9056 | | Status ReactiveVersionSet::ReadAndApply( |
9057 | | InstrumentedMutex* mu, |
9058 | | std::unique_ptr<log::FragmentBufferedReader>* manifest_reader, |
9059 | | Status* manifest_read_status, |
9060 | | std::unordered_set<ColumnFamilyData*>* cfds_changed, |
9061 | 0 | std::vector<std::string>* files_to_delete) { |
9062 | 0 | assert(manifest_reader != nullptr); |
9063 | 0 | assert(cfds_changed != nullptr); |
9064 | 0 | mu->AssertHeld(); |
9065 | |
|
9066 | 0 | Status s; |
9067 | 0 | log::Reader* reader = manifest_reader->get(); |
9068 | 0 | assert(reader); |
9069 | 0 | s = MaybeSwitchManifest(reader->GetReporter(), manifest_reader); |
9070 | 0 | if (!s.ok()) { |
9071 | 0 | return s; |
9072 | 0 | } |
9073 | 0 | manifest_tailer_->Iterate(*(manifest_reader->get()), manifest_read_status); |
9074 | 0 | s = manifest_tailer_->status(); |
9075 | 0 | if (s.ok()) { |
9076 | 0 | *cfds_changed = std::move(manifest_tailer_->GetUpdatedColumnFamilies()); |
9077 | 0 | } |
9078 | 0 | if (files_to_delete) { |
9079 | 0 | *files_to_delete = manifest_tailer_->GetAndClearIntermediateFiles(); |
9080 | 0 | } |
9081 | |
|
9082 | 0 | return s; |
9083 | 0 | } |
9084 | | |
9085 | | uint64_t ReactiveVersionSet::GetInstalledVersionLogNumber( |
9086 | 0 | uint32_t cf_id) const { |
9087 | 0 | assert(manifest_tailer_ != nullptr); |
9088 | 0 | return manifest_tailer_->GetInstalledVersionLogNumber(cf_id); |
9089 | 0 | } |
9090 | | |
9091 | | Status ReactiveVersionSet::MaybeSwitchManifest( |
9092 | | log::Reader::Reporter* reporter, |
9093 | 0 | std::unique_ptr<log::FragmentBufferedReader>* manifest_reader) { |
9094 | 0 | assert(manifest_reader != nullptr); |
9095 | 0 | Status s; |
9096 | 0 | std::string manifest_path; |
9097 | 0 | s = GetCurrentManifestPath(dbname_, fs_.get(), /*is_retry=*/false, |
9098 | 0 | &manifest_path, &manifest_file_number_); |
9099 | 0 | if (!s.ok()) { |
9100 | 0 | return s; |
9101 | 0 | } |
9102 | 0 | std::unique_ptr<FSSequentialFile> manifest_file; |
9103 | 0 | if (manifest_reader->get() != nullptr && |
9104 | 0 | manifest_reader->get()->file()->file_name() == manifest_path) { |
9105 | | // CURRENT points to the same MANIFEST as before, no need to switch |
9106 | | // MANIFEST. |
9107 | 0 | return s; |
9108 | 0 | } |
9109 | 0 | assert(nullptr == manifest_reader->get() || |
9110 | 0 | manifest_reader->get()->file()->file_name() != manifest_path); |
9111 | 0 | s = fs_->FileExists(manifest_path, IOOptions(), nullptr); |
9112 | 0 | if (s.IsNotFound()) { |
9113 | 0 | return Status::TryAgain( |
9114 | 0 | "The primary may have switched to a new MANIFEST and deleted the old " |
9115 | 0 | "one."); |
9116 | 0 | } else if (!s.ok()) { |
9117 | 0 | return s; |
9118 | 0 | } |
9119 | 0 | TEST_SYNC_POINT( |
9120 | 0 | "ReactiveVersionSet::MaybeSwitchManifest:" |
9121 | 0 | "AfterGetCurrentManifestPath:0"); |
9122 | 0 | TEST_SYNC_POINT( |
9123 | 0 | "ReactiveVersionSet::MaybeSwitchManifest:" |
9124 | 0 | "AfterGetCurrentManifestPath:1"); |
9125 | | // The primary can also delete the MANIFEST while the secondary is reading |
9126 | | // it. This is OK on POSIX. For other file systems, maybe create a hard link |
9127 | | // to MANIFEST. The hard link should be cleaned up later by the secondary. |
9128 | 0 | s = fs_->NewSequentialFile(manifest_path, |
9129 | 0 | fs_->OptimizeForManifestRead(file_options_), |
9130 | 0 | &manifest_file, nullptr); |
9131 | 0 | std::unique_ptr<SequentialFileReader> manifest_file_reader; |
9132 | 0 | if (s.ok()) { |
9133 | 0 | manifest_file_reader.reset(new SequentialFileReader( |
9134 | 0 | std::move(manifest_file), manifest_path, |
9135 | 0 | db_options_->log_readahead_size, io_tracer_, db_options_->listeners)); |
9136 | 0 | manifest_reader->reset(new log::FragmentBufferedReader( |
9137 | 0 | nullptr, std::move(manifest_file_reader), reporter, true /* checksum */, |
9138 | 0 | 0 /* log_number */)); |
9139 | 0 | ROCKS_LOG_INFO(db_options_->info_log, "Switched to new manifest: %s\n", |
9140 | 0 | manifest_path.c_str()); |
9141 | 0 | if (manifest_tailer_) { |
9142 | 0 | manifest_tailer_->PrepareToReadNewManifest(); |
9143 | 0 | } |
9144 | 0 | } else if (s.IsPathNotFound()) { |
9145 | | // This can happen if the primary switches to a new MANIFEST after the |
9146 | | // secondary reads the CURRENT file but before the secondary actually |
9147 | | // tries to open the MANIFEST. |
9148 | 0 | s = Status::TryAgain( |
9149 | 0 | "The primary may have switched to a new MANIFEST and deleted the old " |
9150 | 0 | "one."); |
9151 | 0 | } |
9152 | 0 | return s; |
9153 | 0 | } |
9154 | | |
9155 | | #ifndef NDEBUG |
9156 | | uint64_t ReactiveVersionSet::TEST_read_edits_in_atomic_group() const { |
9157 | | assert(manifest_tailer_); |
9158 | | return manifest_tailer_->GetReadBuffer().TEST_read_edits_in_atomic_group(); |
9159 | | } |
9160 | | #endif // !NDEBUG |
9161 | | |
9162 | 0 | std::vector<VersionEdit>& ReactiveVersionSet::replay_buffer() { |
9163 | | assert(manifest_tailer_); |
9164 | 0 | return manifest_tailer_->GetReadBuffer().replay_buffer(); |
9165 | 0 | } |
9166 | | |
9167 | | } // namespace ROCKSDB_NAMESPACE |
9168 | | |
9169 | | // Generate the regular and coroutine versions of the functions defined in |
9170 | | // version_set_sync_and_async.h by including it twice. Included here, after the |
9171 | | // namespace and all definitions in this file, so the generated bodies see the |
9172 | | // file-local types and helpers they use. The macros expand differently based on |
9173 | | // whether WITH_COROUTINES or WITHOUT_COROUTINES is defined. |
9174 | | // clang-format off |
9175 | | #define WITHOUT_COROUTINES |
9176 | | #include "db/version_set_sync_and_async.h" |
9177 | | #undef WITHOUT_COROUTINES |
9178 | | #define WITH_COROUTINES |
9179 | | #include "db/version_set_sync_and_async.h" |
9180 | | #undef WITH_COROUTINES |
9181 | | // clang-format on |