/src/rocksdb/db/compaction/compaction_job.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/compaction/compaction_job.h" |
11 | | |
12 | | #include <algorithm> |
13 | | #include <cinttypes> |
14 | | #include <condition_variable> |
15 | | #include <exception> |
16 | | #include <memory> |
17 | | #include <mutex> |
18 | | #include <optional> |
19 | | #include <set> |
20 | | #include <utility> |
21 | | #include <vector> |
22 | | |
23 | | #include "db/blob/blob_counting_iterator.h" |
24 | | #include "db/blob/blob_file_addition.h" |
25 | | #include "db/blob/blob_file_builder.h" |
26 | | #include "db/blob/missing_blob_repair_iterator.h" |
27 | | #include "db/builder.h" |
28 | | #include "db/compaction/clipping_iterator.h" |
29 | | #include "db/compaction/compaction_state.h" |
30 | | #include "db/db_impl/db_impl.h" |
31 | | #include "db/dbformat.h" |
32 | | #include "db/error_handler.h" |
33 | | #include "db/event_helpers.h" |
34 | | #include "db/history_trimming_iterator.h" |
35 | | #include "db/log_writer.h" |
36 | | #include "db/merge_helper.h" |
37 | | #include "db/range_del_aggregator.h" |
38 | | #include "db/version_edit.h" |
39 | | #include "db/version_set.h" |
40 | | #include "file/file_util.h" |
41 | | #include "file/filename.h" |
42 | | #include "file/read_write_util.h" |
43 | | #include "file/sst_file_manager_impl.h" |
44 | | #include "file/writable_file_writer.h" |
45 | | #include "logging/log_buffer.h" |
46 | | #include "logging/logging.h" |
47 | | #include "monitoring/iostats_context_imp.h" |
48 | | #include "monitoring/thread_status_util.h" |
49 | | #include "options/configurable_helper.h" |
50 | | #include "options/options_helper.h" |
51 | | #include "port/port.h" |
52 | | #include "rocksdb/db.h" |
53 | | #include "rocksdb/env.h" |
54 | | #include "rocksdb/options.h" |
55 | | #include "rocksdb/statistics.h" |
56 | | #include "rocksdb/status.h" |
57 | | #include "rocksdb/table.h" |
58 | | #include "rocksdb/utilities/options_type.h" |
59 | | #include "table/format.h" |
60 | | #include "table/merging_iterator.h" |
61 | | #include "table/meta_blocks.h" |
62 | | #include "table/table_builder.h" |
63 | | #include "table/unique_id_impl.h" |
64 | | #include "test_util/sync_point.h" |
65 | | #include "util/defer.h" |
66 | | #include "util/hash_containers.h" |
67 | | #include "util/stop_watch.h" |
68 | | |
69 | | namespace ROCKSDB_NAMESPACE { |
70 | | |
71 | | namespace { |
72 | | |
73 | | class SubcompactionCompletion { |
74 | | public: |
75 | 0 | SubcompactionCompletion() = default; |
76 | | SubcompactionCompletion(const SubcompactionCompletion&) = delete; |
77 | | SubcompactionCompletion& operator=(const SubcompactionCompletion&) = delete; |
78 | | SubcompactionCompletion(SubcompactionCompletion&&) = delete; |
79 | | SubcompactionCompletion& operator=(SubcompactionCompletion&&) = delete; |
80 | | |
81 | | class Guard { |
82 | | public: |
83 | | explicit Guard(SubcompactionCompletion& completion) |
84 | 0 | : completion_(&completion) {} |
85 | | |
86 | 0 | ~Guard() { |
87 | 0 | if (completion_ != nullptr) { |
88 | 0 | completion_->Complete(); |
89 | 0 | } |
90 | 0 | } |
91 | | |
92 | | Guard(const Guard&) = delete; |
93 | | Guard& operator=(const Guard&) = delete; |
94 | | Guard(Guard&&) = delete; |
95 | | Guard& operator=(Guard&&) = delete; |
96 | | |
97 | 0 | void Release() { completion_ = nullptr; } |
98 | | |
99 | | private: |
100 | | SubcompactionCompletion* completion_; |
101 | | }; |
102 | | |
103 | 0 | void Add() { |
104 | 0 | std::lock_guard<std::mutex> lock(mutex_); |
105 | 0 | ++remaining_; |
106 | 0 | } |
107 | | |
108 | 0 | ~SubcompactionCompletion() { |
109 | 0 | std::unique_lock<std::mutex> lock(mutex_); |
110 | 0 | TEST_SYNC_POINT("CompactionJob::RunSubcompactions:Wait"); |
111 | 0 | cv_.wait(lock, [&] { return remaining_ == 0; }); |
112 | 0 | } |
113 | | |
114 | | private: |
115 | 0 | void Complete() { |
116 | 0 | std::lock_guard<std::mutex> lock(mutex_); |
117 | 0 | assert(remaining_ > 0); |
118 | 0 | --remaining_; |
119 | 0 | cv_.notify_one(); |
120 | 0 | } |
121 | | |
122 | | std::mutex mutex_; |
123 | | std::condition_variable cv_; |
124 | | size_t remaining_ = 0; |
125 | | }; |
126 | | |
127 | | } // namespace |
128 | | |
129 | 2.77k | const char* GetCompactionReasonString(CompactionReason compaction_reason) { |
130 | 2.77k | switch (compaction_reason) { |
131 | 0 | case CompactionReason::kUnknown: |
132 | 0 | return "Unknown"; |
133 | 1.15k | case CompactionReason::kLevelL0FilesNum: |
134 | 1.15k | return "LevelL0FilesNum"; |
135 | 0 | case CompactionReason::kLevelMaxLevelSize: |
136 | 0 | return "LevelMaxLevelSize"; |
137 | 0 | case CompactionReason::kUniversalSizeAmplification: |
138 | 0 | return "UniversalSizeAmplification"; |
139 | 0 | case CompactionReason::kUniversalSizeRatio: |
140 | 0 | return "UniversalSizeRatio"; |
141 | 0 | case CompactionReason::kUniversalSortedRunNum: |
142 | 0 | return "UniversalSortedRunNum"; |
143 | 0 | case CompactionReason::kFIFOMaxSize: |
144 | 0 | return "FIFOMaxSize"; |
145 | 0 | case CompactionReason::kFIFOReduceNumFiles: |
146 | 0 | return "FIFOReduceNumFiles"; |
147 | 0 | case CompactionReason::kFIFOTtl: |
148 | 0 | return "FIFOTtl"; |
149 | 840 | case CompactionReason::kManualCompaction: |
150 | 840 | return "ManualCompaction"; |
151 | 0 | case CompactionReason::kFilesMarkedForCompaction: |
152 | 0 | return "FilesMarkedForCompaction"; |
153 | 776 | case CompactionReason::kBottommostFiles: |
154 | 776 | return "BottommostFiles"; |
155 | 0 | case CompactionReason::kTtl: |
156 | 0 | return "Ttl"; |
157 | 0 | case CompactionReason::kFlush: |
158 | 0 | return "Flush"; |
159 | 0 | case CompactionReason::kExternalSstIngestion: |
160 | 0 | return "ExternalSstIngestion"; |
161 | 0 | case CompactionReason::kPeriodicCompaction: |
162 | 0 | return "PeriodicCompaction"; |
163 | 0 | case CompactionReason::kChangeTemperature: |
164 | 0 | return "ChangeTemperature"; |
165 | 0 | case CompactionReason::kForcedBlobGC: |
166 | 0 | return "ForcedBlobGC"; |
167 | 0 | case CompactionReason::kRoundRobinTtl: |
168 | 0 | return "RoundRobinTtl"; |
169 | 0 | case CompactionReason::kRefitLevel: |
170 | 0 | return "RefitLevel"; |
171 | 0 | case CompactionReason::kReadTriggered: |
172 | 0 | return "ReadTriggered"; |
173 | 0 | case CompactionReason::kNumOfReasons: |
174 | | // fall through |
175 | 0 | default: |
176 | 0 | assert(false); |
177 | 0 | return "Invalid"; |
178 | 2.77k | } |
179 | 2.77k | } |
180 | | |
181 | | const char* GetCompactionProximalOutputRangeTypeString( |
182 | 0 | Compaction::ProximalOutputRangeType range_type) { |
183 | 0 | switch (range_type) { |
184 | 0 | case Compaction::ProximalOutputRangeType::kNotSupported: |
185 | 0 | return "NotSupported"; |
186 | 0 | case Compaction::ProximalOutputRangeType::kFullRange: |
187 | 0 | return "FullRange"; |
188 | 0 | case Compaction::ProximalOutputRangeType::kNonLastRange: |
189 | 0 | return "NonLastRange"; |
190 | 0 | case Compaction::ProximalOutputRangeType::kDisabled: |
191 | 0 | return "Disabled"; |
192 | 0 | default: |
193 | 0 | assert(false); |
194 | 0 | return "Invalid"; |
195 | 0 | } |
196 | 0 | } |
197 | | |
198 | | // Static constant for compaction abort flag - always false, used for |
199 | | // compaction service jobs that don't support abort signaling |
200 | | const std::atomic<int> CompactionJob::kCompactionAbortedFalse{0}; |
201 | | |
202 | | CompactionJob::CompactionJob( |
203 | | int job_id, Compaction* compaction, const ImmutableDBOptions& db_options, |
204 | | const MutableDBOptions& mutable_db_options, const FileOptions& file_options, |
205 | | VersionSet* versions, const std::atomic<bool>* shutting_down, |
206 | | LogBuffer* log_buffer, FSDirectory* db_directory, |
207 | | FSDirectory* output_directory, FSDirectory* blob_output_directory, |
208 | | Statistics* stats, InstrumentedMutex* db_mutex, |
209 | | ErrorHandler* db_error_handler, JobContext* job_context, |
210 | | std::shared_ptr<Cache> table_cache, EventLogger* event_logger, |
211 | | bool paranoid_file_checks, bool measure_io_stats, const std::string& dbname, |
212 | | CompactionJobStats* compaction_job_stats, Env::Priority thread_pri, |
213 | | const std::shared_ptr<IOTracer>& io_tracer, |
214 | | const std::atomic<bool>& manual_compaction_canceled, |
215 | | const std::atomic<int>& compaction_aborted, const std::string& db_id, |
216 | | const std::string& db_session_id, std::string full_history_ts_low, |
217 | | std::string trim_ts, BlobFileCompletionCallback* blob_callback, |
218 | | int* bg_compaction_scheduled, int* bg_bottom_compaction_scheduled, |
219 | | std::atomic<int>* num_running_remote_compactions) |
220 | 2.77k | : compact_(new CompactionState(compaction)), |
221 | 2.77k | internal_stats_(compaction->compaction_reason(), 1), |
222 | 2.77k | db_options_(db_options), |
223 | 2.77k | mutable_db_options_copy_(mutable_db_options), |
224 | 2.77k | log_buffer_(log_buffer), |
225 | 2.77k | output_directory_(output_directory), |
226 | 2.77k | stats_(stats), |
227 | 2.77k | bottommost_level_(false), |
228 | 2.77k | write_hint_(Env::WLTH_NOT_SET), |
229 | 2.77k | job_stats_(compaction_job_stats), |
230 | 2.77k | job_id_(job_id), |
231 | 2.77k | dbname_(dbname), |
232 | 2.77k | db_id_(db_id), |
233 | 2.77k | db_session_id_(db_session_id), |
234 | 2.77k | file_options_(file_options), |
235 | 2.77k | env_(db_options.env), |
236 | 2.77k | io_tracer_(io_tracer), |
237 | 2.77k | fs_(db_options.fs, io_tracer), |
238 | | file_options_for_read_( |
239 | 2.77k | fs_->OptimizeForCompactionTableRead(file_options, db_options_)), |
240 | 2.77k | file_options_for_compaction_input_read_(file_options_for_read_), |
241 | 2.77k | versions_(versions), |
242 | 2.77k | shutting_down_(shutting_down), |
243 | 2.77k | manual_compaction_canceled_(manual_compaction_canceled), |
244 | 2.77k | compaction_aborted_(compaction_aborted), |
245 | 2.77k | db_directory_(db_directory), |
246 | 2.77k | blob_output_directory_(blob_output_directory), |
247 | 2.77k | db_mutex_(db_mutex), |
248 | 2.77k | db_error_handler_(db_error_handler), |
249 | | // job_context cannot be nullptr, but we will assert later in the body of |
250 | | // the constructor. |
251 | 2.77k | earliest_snapshot_(job_context |
252 | 2.77k | ? job_context->GetEarliestSnapshotSequence() |
253 | 2.77k | : kMaxSequenceNumber), |
254 | 2.77k | job_context_(job_context), |
255 | 2.77k | table_cache_(std::move(table_cache)), |
256 | 2.77k | event_logger_(event_logger), |
257 | 2.77k | paranoid_file_checks_(paranoid_file_checks), |
258 | 2.77k | measure_io_stats_(measure_io_stats), |
259 | 2.77k | thread_pri_(thread_pri), |
260 | 2.77k | full_history_ts_low_(std::move(full_history_ts_low)), |
261 | 2.77k | trim_ts_(std::move(trim_ts)), |
262 | 2.77k | blob_callback_(blob_callback), |
263 | 2.77k | extra_num_subcompaction_threads_reserved_(0), |
264 | 2.77k | bg_compaction_scheduled_(bg_compaction_scheduled), |
265 | 2.77k | bg_bottom_compaction_scheduled_(bg_bottom_compaction_scheduled), |
266 | 2.77k | num_running_remote_compactions_(num_running_remote_compactions) { |
267 | 2.77k | assert(job_stats_ != nullptr); |
268 | 2.77k | assert(log_buffer_ != nullptr); |
269 | 2.77k | assert(job_context); |
270 | 2.77k | assert(job_context->snapshot_context_initialized); |
271 | | |
272 | 2.77k | if (db_options_.use_direct_io_for_compaction_reads && |
273 | 0 | !db_options_.use_direct_reads) { |
274 | 0 | file_options_for_compaction_input_read_.use_direct_reads = true; |
275 | 0 | } |
276 | | |
277 | 2.77k | ReportStartedCompaction(compaction); |
278 | 2.77k | InitializeThreadStatus(); |
279 | 2.77k | } |
280 | | |
281 | 2.77k | CompactionJob::~CompactionJob() { |
282 | 2.77k | assert(compact_ == nullptr); |
283 | 2.77k | ThreadStatusUtil::ResetThreadStatus(); |
284 | 2.77k | } |
285 | | |
286 | 2.77k | void CompactionJob::InitializeThreadStatus() { |
287 | 2.77k | auto* compaction = compact_->compaction; |
288 | 2.77k | ThreadStatusUtil::SetEnableTracking(db_options_.enable_thread_tracking); |
289 | 2.77k | ThreadStatusUtil::SetColumnFamily(compaction->column_family_data()); |
290 | 2.77k | ThreadStatusUtil::SetThreadOperationProperty(ThreadStatus::COMPACTION_JOB_ID, |
291 | 2.77k | job_id_); |
292 | | |
293 | 2.77k | ThreadStatusUtil::SetThreadOperationProperty( |
294 | 2.77k | ThreadStatus::COMPACTION_INPUT_OUTPUT_LEVEL, |
295 | 2.77k | (static_cast<uint64_t>(compact_->compaction->start_level()) << 32) + |
296 | 2.77k | compact_->compaction->output_level()); |
297 | | |
298 | | // In the current design, a CompactionJob is always created |
299 | | // for non-trivial compaction. |
300 | 2.77k | assert(compaction->IsTrivialMove() == false || |
301 | 2.77k | compaction->is_manual_compaction() == true); |
302 | | |
303 | 2.77k | ThreadStatusUtil::SetThreadOperationProperty( |
304 | 2.77k | ThreadStatus::COMPACTION_PROP_FLAGS, |
305 | 2.77k | compaction->is_manual_compaction() + |
306 | 2.77k | (compaction->deletion_compaction() << 1)); |
307 | 2.77k | ThreadStatusUtil::SetThreadOperationProperty( |
308 | 2.77k | ThreadStatus::COMPACTION_TOTAL_INPUT_BYTES, |
309 | 2.77k | compaction->CalculateTotalInputSize()); |
310 | | |
311 | 2.77k | IOSTATS_RESET(bytes_written); |
312 | 2.77k | IOSTATS_RESET(bytes_read); |
313 | 2.77k | ThreadStatusUtil::SetThreadOperationProperty( |
314 | 2.77k | ThreadStatus::COMPACTION_BYTES_WRITTEN, 0); |
315 | 2.77k | ThreadStatusUtil::SetThreadOperationProperty( |
316 | 2.77k | ThreadStatus::COMPACTION_BYTES_READ, 0); |
317 | | |
318 | | // Set the thread operation after operation properties |
319 | | // to ensure GetThreadList() can always show them all together. |
320 | 2.77k | ThreadStatusUtil::SetThreadOperation(ThreadStatus::OP_COMPACTION); |
321 | 2.77k | } |
322 | | |
323 | 2.77k | void CompactionJob::ReportStartedCompaction(Compaction* compaction) { |
324 | 2.77k | job_stats_->is_manual_compaction = compaction->is_manual_compaction(); |
325 | 2.77k | job_stats_->is_full_compaction = compaction->is_full_compaction(); |
326 | | // populate compaction stats num_input_files and total_num_of_bytes |
327 | 2.77k | size_t num_input_files = 0; |
328 | 2.77k | for (int input_level = 0; |
329 | 7.19k | input_level < static_cast<int>(compaction->num_input_levels()); |
330 | 4.41k | ++input_level) { |
331 | 4.41k | const LevelFilesBrief* flevel = compaction->input_levels(input_level); |
332 | 4.41k | num_input_files += flevel->num_files; |
333 | 4.41k | } |
334 | 2.77k | job_stats_->CompactionJobStats::num_input_files = num_input_files; |
335 | 2.77k | job_stats_->total_input_bytes = compaction->CalculateTotalInputSize(); |
336 | 2.77k | } |
337 | | |
338 | | void CompactionJob::Prepare( |
339 | | std::optional<std::pair<std::optional<Slice>, std::optional<Slice>>> |
340 | | known_single_subcompact, |
341 | | const CompactionProgress& compaction_progress, |
342 | 2.77k | log::Writer* compaction_progress_writer) { |
343 | 2.77k | db_mutex_->AssertHeld(); |
344 | 2.77k | AutoThreadOperationStageUpdater stage_updater( |
345 | 2.77k | ThreadStatus::STAGE_COMPACTION_PREPARE); |
346 | | |
347 | | // Generate file_levels_ for compaction before making Iterator |
348 | 2.77k | auto* c = compact_->compaction; |
349 | 2.77k | [[maybe_unused]] ColumnFamilyData* cfd = c->column_family_data(); |
350 | 2.77k | assert(cfd != nullptr); |
351 | 2.77k | const VersionStorageInfo* storage_info = c->input_version()->storage_info(); |
352 | 2.77k | assert(storage_info); |
353 | 2.77k | assert(storage_info->NumLevelFiles(compact_->compaction->level()) > 0); |
354 | | |
355 | 2.77k | write_hint_ = storage_info->CalculateSSTWriteHint( |
356 | 2.77k | c->output_level(), db_options_.calculate_sst_write_lifetime_hint_set); |
357 | 2.77k | bottommost_level_ = c->bottommost_level(); |
358 | | |
359 | 2.77k | if (!known_single_subcompact.has_value() && c->ShouldFormSubcompactions()) { |
360 | 0 | StopWatch sw(db_options_.clock, stats_, SUBCOMPACTION_SETUP_TIME); |
361 | 0 | GenSubcompactionBoundaries(); |
362 | 0 | } |
363 | 2.77k | if (boundaries_.size() >= 1) { |
364 | 0 | assert(!known_single_subcompact.has_value()); |
365 | 0 | for (size_t i = 0; i <= boundaries_.size(); i++) { |
366 | 0 | compact_->sub_compact_states.emplace_back( |
367 | 0 | c, (i != 0) ? std::optional<Slice>(boundaries_[i - 1]) : std::nullopt, |
368 | 0 | (i != boundaries_.size()) ? std::optional<Slice>(boundaries_[i]) |
369 | 0 | : std::nullopt, |
370 | 0 | static_cast<uint32_t>(i)); |
371 | | // assert to validate that boundaries don't have same user keys (without |
372 | | // timestamp part). |
373 | 0 | assert(i == 0 || i == boundaries_.size() || |
374 | 0 | cfd->user_comparator()->CompareWithoutTimestamp( |
375 | 0 | boundaries_[i - 1], boundaries_[i]) < 0); |
376 | 0 | } |
377 | 0 | RecordInHistogram(stats_, NUM_SUBCOMPACTIONS_SCHEDULED, |
378 | 0 | compact_->sub_compact_states.size()); |
379 | 2.77k | } else { |
380 | 2.77k | std::optional<Slice> start_key; |
381 | 2.77k | std::optional<Slice> end_key; |
382 | 2.77k | if (known_single_subcompact.has_value()) { |
383 | 0 | start_key = known_single_subcompact.value().first; |
384 | 0 | end_key = known_single_subcompact.value().second; |
385 | 2.77k | } else { |
386 | 2.77k | assert(!start_key.has_value() && !end_key.has_value()); |
387 | 2.77k | } |
388 | 2.77k | compact_->sub_compact_states.emplace_back(c, start_key, end_key, |
389 | 2.77k | /*sub_job_id*/ 0); |
390 | 2.77k | } |
391 | | |
392 | 2.77k | MaybeAssignCompactionProgressAndWriter(compaction_progress, |
393 | 2.77k | compaction_progress_writer); |
394 | | |
395 | | // collect all seqno->time information from the input files which will be used |
396 | | // to encode seqno->time to the output files. |
397 | 2.77k | SequenceNumber preserve_time_min_seqno = kMaxSequenceNumber; |
398 | 2.77k | SequenceNumber preclude_last_level_min_seqno = kMaxSequenceNumber; |
399 | 2.77k | uint64_t preserve_time_duration = |
400 | 2.77k | MinAndMaxPreserveSeconds(c->mutable_cf_options()).max_preserve_seconds; |
401 | | |
402 | 2.77k | if (preserve_time_duration > 0) { |
403 | 0 | const ReadOptions read_options(Env::IOActivity::kCompaction); |
404 | | // Setup seqno_to_time_mapping_ with relevant time range. |
405 | 0 | seqno_to_time_mapping_.SetMaxTimeSpan(preserve_time_duration); |
406 | 0 | for (const auto& each_level : *c->inputs()) { |
407 | 0 | for (const auto& fmd : each_level.files) { |
408 | 0 | std::shared_ptr<const TableProperties> tp; |
409 | 0 | Status s = c->input_version()->GetTableProperties(read_options, &tp, |
410 | 0 | fmd, nullptr); |
411 | 0 | if (s.ok()) { |
412 | 0 | s = seqno_to_time_mapping_.DecodeFrom(tp->seqno_to_time_mapping); |
413 | 0 | } |
414 | 0 | if (!s.ok()) { |
415 | 0 | ROCKS_LOG_WARN( |
416 | 0 | db_options_.info_log, |
417 | 0 | "Problem reading or processing seqno-to-time mapping: %s", |
418 | 0 | s.ToString().c_str()); |
419 | 0 | } |
420 | 0 | } |
421 | 0 | } |
422 | |
|
423 | 0 | int64_t _current_time = 0; |
424 | 0 | Status s = db_options_.clock->GetCurrentTime(&_current_time); |
425 | 0 | if (!s.ok()) { |
426 | 0 | ROCKS_LOG_WARN(db_options_.info_log, |
427 | 0 | "Failed to get current time in compaction: Status: %s", |
428 | 0 | s.ToString().c_str()); |
429 | | // preserve all time information |
430 | 0 | preserve_time_min_seqno = 0; |
431 | 0 | preclude_last_level_min_seqno = 0; |
432 | 0 | seqno_to_time_mapping_.Enforce(); |
433 | 0 | } else { |
434 | 0 | seqno_to_time_mapping_.Enforce(_current_time); |
435 | 0 | seqno_to_time_mapping_.GetCurrentTieringCutoffSeqnos( |
436 | 0 | static_cast<uint64_t>(_current_time), |
437 | 0 | c->mutable_cf_options().preserve_internal_time_seconds, |
438 | 0 | c->mutable_cf_options().preclude_last_level_data_seconds, |
439 | 0 | &preserve_time_min_seqno, &preclude_last_level_min_seqno); |
440 | 0 | } |
441 | | // For accuracy of the GetProximalSeqnoBeforeTime queries above, we only |
442 | | // limit the capacity after them. |
443 | | // Here If we set capacity to the per-SST limit, we could be throwing away |
444 | | // fidelity when a compaction output file has a narrower seqno range than |
445 | | // all the inputs. If we only limit capacity for each compaction output, we |
446 | | // could be doing a lot of unnecessary recomputation in a large compaction |
447 | | // (up to quadratic in number of files). Thus, we do soemthing in the |
448 | | // middle: enforce a resonably large constant size limit substantially |
449 | | // larger than kMaxSeqnoTimePairsPerSST. |
450 | 0 | seqno_to_time_mapping_.SetCapacity(kMaxSeqnoToTimeEntries); |
451 | 0 | } |
452 | | #ifndef NDEBUG |
453 | | assert(preserve_time_min_seqno <= preclude_last_level_min_seqno); |
454 | | TEST_SYNC_POINT_CALLBACK( |
455 | | "CompactionJob::PrepareTimes():preclude_last_level_min_seqno", |
456 | | static_cast<void*>(&preclude_last_level_min_seqno)); |
457 | | // Restore the invariant asserted above, in case it was broken under the |
458 | | // callback |
459 | | preserve_time_min_seqno = |
460 | | std::min(preclude_last_level_min_seqno, preserve_time_min_seqno); |
461 | | #endif |
462 | | |
463 | | // For a bottommost-file compaction, which exists only because the marker |
464 | | // (VersionStorageInfo::ComputeBottommostFilesMarkedForCompaction) decided the |
465 | | // file's largest sequence number could be zeroed, honor that same |
466 | | // version-carried preserve boundary here. The marker reads DBImpl's live, |
467 | | // DB-wide seqno->time mapping, whereas the value computed above is derived |
468 | | // only from the selected file's persisted mapping, which can be sparse or |
469 | | // empty (e.g. a file written before preserve/preclude was enabled). Taking |
470 | | // the max ensures this compaction can zero at least the sequence numbers the |
471 | | // marker considered zeroable, so a marked file makes progress instead of |
472 | | // being re-marked forever (infinite compaction loop). Other compaction |
473 | | // reasons keep the per-input-file boundary so their seqno-zeroing behavior is |
474 | | // unchanged. When preserve/preclude is inactive the version value is |
475 | | // kMaxSequenceNumber, making this a no-op. A remote compaction worker does |
476 | | // not know the reason or the live mapping; it gets this boundary from the |
477 | | // primary in CompactionServiceInput. |
478 | 2.77k | if (c->compaction_reason() == CompactionReason::kBottommostFiles) { |
479 | 776 | preserve_time_min_seqno_floor_ = storage_info->GetPreserveTimeMinSeqno(); |
480 | 776 | } |
481 | 2.77k | preserve_time_min_seqno = |
482 | 2.77k | std::max(preserve_time_min_seqno, preserve_time_min_seqno_floor_); |
483 | | |
484 | | // Preserve sequence numbers for preserved write times and snapshots, though |
485 | | // the specific sequence number of the earliest snapshot can be zeroed. |
486 | 2.77k | preserve_seqno_after_ = |
487 | 2.77k | std::max(preserve_time_min_seqno, SequenceNumber{1}) - 1; |
488 | 2.77k | preserve_seqno_after_ = std::min(preserve_seqno_after_, earliest_snapshot_); |
489 | | // If using preclude feature, also preclude snapshots from last level, just |
490 | | // because they are heuristically more likely to be accessed than non-snapshot |
491 | | // data. |
492 | 2.77k | if (preclude_last_level_min_seqno < kMaxSequenceNumber && |
493 | 0 | earliest_snapshot_ < preclude_last_level_min_seqno) { |
494 | 0 | preclude_last_level_min_seqno = earliest_snapshot_; |
495 | 0 | } |
496 | | // Now combine what we would like to preclude from last level with what we |
497 | | // can safely support without dangerously moving data back up the LSM tree, |
498 | | // to get the final seqno threshold for proximal vs. last. In particular, |
499 | | // when the reserved output key range for the proximal level does not |
500 | | // include the entire last level input key range, we need to keep entries |
501 | | // already in the last level there. (Even allowing within-range entries to |
502 | | // move back up could cause problems with range tombstones. Perhaps it |
503 | | // would be better in some rare cases to keep entries in the last level |
504 | | // one-by-one rather than based on sequence number, but that would add extra |
505 | | // tracking and complexity to CompactionIterator that is probably not |
506 | | // worthwhile overall. Correctness is also more clear when splitting by |
507 | | // seqno threshold.) |
508 | 2.77k | proximal_after_seqno_ = std::max(preclude_last_level_min_seqno, |
509 | 2.77k | c->GetKeepInLastLevelThroughSeqno()); |
510 | | |
511 | 2.77k | options_file_number_ = versions_->options_file_number(); |
512 | | |
513 | | // For remote compaction hardening: capture the current MANIFEST position as a |
514 | | // floor for the worker's recovery. manifest_file_size() is a physical byte |
515 | | // offset that VersionSet::ProcessManifestWrites records under the DB mutex |
516 | | // only after the whole version-edit batch is written and sync'd, so it is a |
517 | | // durable, complete-batch offset -- never mid-record or mid-atomic-group -- |
518 | | // at or past the compaction's (already committed) input version. |
519 | 2.77k | if (mutable_db_options_copy_.remote_compaction_manifest_floor) { |
520 | 2.77k | min_manifest_file_number_ = versions_->manifest_file_number(); |
521 | 2.77k | min_manifest_file_size_ = versions_->manifest_file_size(); |
522 | 2.77k | } |
523 | 2.77k | } |
524 | | |
525 | | void CompactionJob::MaybeAssignCompactionProgressAndWriter( |
526 | | const CompactionProgress& compaction_progress, |
527 | 2.77k | log::Writer* compaction_progress_writer) { |
528 | | // LIMITATION: Only supports resuming single subcompaction for now |
529 | 2.77k | if (compact_->sub_compact_states.size() != 1) { |
530 | 0 | return; |
531 | 0 | } |
532 | | |
533 | 2.77k | if (!compaction_progress.empty()) { |
534 | 0 | assert(compaction_progress.size() == 1); |
535 | 0 | SubcompactionState* sub_compact = &compact_->sub_compact_states[0]; |
536 | 0 | const SubcompactionProgress& subcompaction_progress = |
537 | 0 | compaction_progress[0]; |
538 | 0 | sub_compact->SetSubcompactionProgress(subcompaction_progress); |
539 | 0 | } |
540 | | |
541 | 2.77k | compaction_progress_writer_ = compaction_progress_writer; |
542 | 2.77k | } |
543 | | |
544 | 0 | uint64_t CompactionJob::GetSubcompactionsLimit() { |
545 | 0 | return extra_num_subcompaction_threads_reserved_ + |
546 | 0 | std::max( |
547 | 0 | std::uint64_t(1), |
548 | 0 | static_cast<uint64_t>(compact_->compaction->max_subcompactions())); |
549 | 0 | } |
550 | | |
551 | | void CompactionJob::AcquireSubcompactionResources( |
552 | 0 | int num_extra_required_subcompactions) { |
553 | 0 | if (remote_compaction_executor_) { |
554 | 0 | return; |
555 | 0 | } |
556 | 0 | TEST_SYNC_POINT("CompactionJob::AcquireSubcompactionResources:0"); |
557 | 0 | TEST_SYNC_POINT("CompactionJob::AcquireSubcompactionResources:1"); |
558 | 0 | int max_db_compactions = |
559 | 0 | DBImpl::GetBGJobLimits( |
560 | 0 | mutable_db_options_copy_.max_background_flushes, |
561 | 0 | mutable_db_options_copy_.max_background_compactions, |
562 | 0 | mutable_db_options_copy_.max_background_jobs, |
563 | 0 | versions_->GetColumnFamilySet() |
564 | 0 | ->write_controller() |
565 | 0 | ->NeedSpeedupCompaction()) |
566 | 0 | .max_compactions; |
567 | 0 | InstrumentedMutexLock l(db_mutex_); |
568 | | // Apply min function first since We need to compute the extra subcompaction |
569 | | // against compaction limits. And then try to reserve threads for extra |
570 | | // subcompactions. The actual number of reserved threads could be less than |
571 | | // the desired number. |
572 | 0 | int available_bg_compactions_against_db_limit = |
573 | 0 | std::max(max_db_compactions - *bg_compaction_scheduled_ - |
574 | 0 | *bg_bottom_compaction_scheduled_, |
575 | 0 | 0); |
576 | | // Reservation only supports backgrdoun threads of which the priority is |
577 | | // between BOTTOM and HIGH. Need to degrade the priority to HIGH if the |
578 | | // origin thread_pri_ is higher than that. Similar to ReleaseThreads(). |
579 | 0 | extra_num_subcompaction_threads_reserved_ = |
580 | 0 | env_->ReserveThreads(std::min(num_extra_required_subcompactions, |
581 | 0 | available_bg_compactions_against_db_limit), |
582 | 0 | std::min(thread_pri_, Env::Priority::HIGH)); |
583 | | |
584 | | // Update bg_compaction_scheduled_ or bg_bottom_compaction_scheduled_ |
585 | | // depending on if this compaction has the bottommost priority |
586 | 0 | if (thread_pri_ == Env::Priority::BOTTOM) { |
587 | 0 | *bg_bottom_compaction_scheduled_ += |
588 | 0 | extra_num_subcompaction_threads_reserved_; |
589 | 0 | } else { |
590 | 0 | *bg_compaction_scheduled_ += extra_num_subcompaction_threads_reserved_; |
591 | 0 | } |
592 | 0 | } |
593 | | |
594 | 0 | void CompactionJob::ShrinkSubcompactionResources(uint64_t num_extra_resources) { |
595 | | // Do nothing when we have zero resources to shrink |
596 | 0 | if (num_extra_resources == 0) { |
597 | 0 | return; |
598 | 0 | } |
599 | 0 | db_mutex_->Lock(); |
600 | | // We cannot release threads more than what we reserved before |
601 | 0 | int extra_num_subcompaction_threads_released = env_->ReleaseThreads( |
602 | 0 | (int)num_extra_resources, std::min(thread_pri_, Env::Priority::HIGH)); |
603 | | // Update the number of reserved threads and the number of background |
604 | | // scheduled compactions for this compaction job |
605 | 0 | extra_num_subcompaction_threads_reserved_ -= |
606 | 0 | extra_num_subcompaction_threads_released; |
607 | | // TODO (zichen): design a test case with new subcompaction partitioning |
608 | | // when the number of actual partitions is less than the number of planned |
609 | | // partitions |
610 | 0 | assert(extra_num_subcompaction_threads_released == (int)num_extra_resources); |
611 | | // Update bg_compaction_scheduled_ or bg_bottom_compaction_scheduled_ |
612 | | // depending on if this compaction has the bottommost priority |
613 | 0 | if (thread_pri_ == Env::Priority::BOTTOM) { |
614 | 0 | *bg_bottom_compaction_scheduled_ -= |
615 | 0 | extra_num_subcompaction_threads_released; |
616 | 0 | } else { |
617 | 0 | *bg_compaction_scheduled_ -= extra_num_subcompaction_threads_released; |
618 | 0 | } |
619 | 0 | db_mutex_->Unlock(); |
620 | 0 | TEST_SYNC_POINT("CompactionJob::ShrinkSubcompactionResources:0"); |
621 | 0 | } |
622 | | |
623 | 2.77k | void CompactionJob::ReleaseSubcompactionResources() { |
624 | 2.77k | if (extra_num_subcompaction_threads_reserved_ == 0) { |
625 | 2.77k | return; |
626 | 2.77k | } |
627 | 0 | { |
628 | 0 | InstrumentedMutexLock l(db_mutex_); |
629 | | // The number of reserved threads becomes larger than 0 only if the |
630 | | // compaction prioity is round robin and there is no sufficient |
631 | | // sub-compactions available |
632 | | |
633 | | // The scheduled compaction must be no less than 1 + extra number |
634 | | // subcompactions using acquired resources since this compaction job has not |
635 | | // finished yet |
636 | 0 | assert(*bg_bottom_compaction_scheduled_ >= |
637 | 0 | 1 + extra_num_subcompaction_threads_reserved_ || |
638 | 0 | *bg_compaction_scheduled_ >= |
639 | 0 | 1 + extra_num_subcompaction_threads_reserved_); |
640 | 0 | } |
641 | 0 | ShrinkSubcompactionResources(extra_num_subcompaction_threads_reserved_); |
642 | 0 | } |
643 | | |
644 | 0 | void CompactionJob::GenSubcompactionBoundaries() { |
645 | | // The goal is to find some boundary keys so that we can evenly partition |
646 | | // the compaction input data into max_subcompactions ranges. |
647 | | // For every input file, we ask TableReader to estimate 128 anchor points |
648 | | // that evenly partition the input file into 128 ranges and the range |
649 | | // sizes. This can be calculated by scanning index blocks of the file. |
650 | | // Once we have the anchor points for all the input files, we merge them |
651 | | // together and try to find keys dividing ranges evenly. |
652 | | // For example, if we have two input files, and each returns following |
653 | | // ranges: |
654 | | // File1: (a1, 1000), (b1, 1200), (c1, 1100) |
655 | | // File2: (a2, 1100), (b2, 1000), (c2, 1000) |
656 | | // We total sort the keys to following: |
657 | | // (a1, 1000), (a2, 1100), (b1, 1200), (b2, 1000), (c1, 1100), (c2, 1000) |
658 | | // We calculate the total size by adding up all ranges' size, which is 6400. |
659 | | // If we would like to partition into 2 subcompactions, the target of the |
660 | | // range size is 3200. Based on the size, we take "b1" as the partition key |
661 | | // since the first three ranges would hit 3200. |
662 | | // |
663 | | // Note that the ranges are actually overlapping. For example, in the example |
664 | | // above, the range ending with "b1" is overlapping with the range ending with |
665 | | // "b2". So the size 1000+1100+1200 is an underestimation of data size up to |
666 | | // "b1". In extreme cases where we only compact N L0 files, a range can |
667 | | // overlap with N-1 other ranges. Since we requested a relatively large number |
668 | | // (128) of ranges from each input files, even N range overlapping would |
669 | | // cause relatively small inaccuracy. |
670 | 0 | ReadOptions read_options(Env::IOActivity::kCompaction); |
671 | 0 | read_options.rate_limiter_priority = GetRateLimiterPriority(); |
672 | 0 | auto* c = compact_->compaction; |
673 | 0 | if (c->mutable_cf_options().table_factory->Name() == |
674 | 0 | TableFactory::kPlainTableName()) { |
675 | 0 | return; |
676 | 0 | } |
677 | | |
678 | 0 | if (c->max_subcompactions() <= 1 && |
679 | 0 | !(c->immutable_options().compaction_pri == kRoundRobin && |
680 | 0 | c->immutable_options().compaction_style == kCompactionStyleLevel)) { |
681 | 0 | return; |
682 | 0 | } |
683 | 0 | auto* cfd = c->column_family_data(); |
684 | 0 | const Comparator* cfd_comparator = cfd->user_comparator(); |
685 | 0 | const InternalKeyComparator& icomp = cfd->internal_comparator(); |
686 | |
|
687 | 0 | auto* v = compact_->compaction->input_version(); |
688 | 0 | int base_level = v->storage_info()->base_level(); |
689 | 0 | InstrumentedMutexUnlock unlock_guard(db_mutex_); |
690 | |
|
691 | 0 | uint64_t total_size = 0; |
692 | 0 | std::vector<TableReader::Anchor> all_anchors; |
693 | 0 | int start_lvl = c->start_level(); |
694 | 0 | int out_lvl = c->output_level(); |
695 | |
|
696 | 0 | for (size_t lvl_idx = 0; lvl_idx < c->num_input_levels(); lvl_idx++) { |
697 | 0 | int lvl = c->level(lvl_idx); |
698 | 0 | if (lvl >= start_lvl && lvl <= out_lvl) { |
699 | 0 | const LevelFilesBrief* flevel = c->input_levels(lvl_idx); |
700 | 0 | size_t num_files = flevel->num_files; |
701 | |
|
702 | 0 | if (num_files == 0) { |
703 | 0 | continue; |
704 | 0 | } |
705 | | |
706 | 0 | for (size_t i = 0; i < num_files; i++) { |
707 | 0 | FileMetaData* f = flevel->files[i].file_metadata; |
708 | 0 | std::vector<TableReader::Anchor> my_anchors; |
709 | 0 | Status s = cfd->table_cache()->ApproximateKeyAnchors( |
710 | 0 | read_options, icomp, *f, c->mutable_cf_options(), my_anchors); |
711 | 0 | if (!s.ok() || my_anchors.empty()) { |
712 | 0 | my_anchors.emplace_back(f->largest.user_key(), f->fd.GetFileSize()); |
713 | 0 | } |
714 | 0 | for (auto& ac : my_anchors) { |
715 | | // Can be optimize to avoid this loop. |
716 | 0 | total_size += ac.range_size; |
717 | 0 | } |
718 | |
|
719 | 0 | all_anchors.insert(all_anchors.end(), my_anchors.begin(), |
720 | 0 | my_anchors.end()); |
721 | 0 | } |
722 | 0 | } |
723 | 0 | } |
724 | | // Here we total sort all the anchor points across all files and go through |
725 | | // them in the sorted order to find partitioning boundaries. |
726 | | // Not the most efficient implementation. A much more efficient algorithm |
727 | | // probably exists. But they are more complex. If performance turns out to |
728 | | // be a problem, we can optimize. |
729 | 0 | std::sort( |
730 | 0 | all_anchors.begin(), all_anchors.end(), |
731 | 0 | [cfd_comparator](TableReader::Anchor& a, TableReader::Anchor& b) -> bool { |
732 | 0 | return cfd_comparator->CompareWithoutTimestamp(a.user_key, b.user_key) < |
733 | 0 | 0; |
734 | 0 | }); |
735 | | |
736 | | // Remove duplicated entries from boundaries. |
737 | 0 | all_anchors.erase( |
738 | 0 | std::unique(all_anchors.begin(), all_anchors.end(), |
739 | 0 | [cfd_comparator](TableReader::Anchor& a, |
740 | 0 | TableReader::Anchor& b) -> bool { |
741 | 0 | return cfd_comparator->CompareWithoutTimestamp( |
742 | 0 | a.user_key, b.user_key) == 0; |
743 | 0 | }), |
744 | 0 | all_anchors.end()); |
745 | | |
746 | | // Get the number of planned subcompactions, may update reserve threads |
747 | | // and update extra_num_subcompaction_threads_reserved_ for round-robin |
748 | 0 | uint64_t num_planned_subcompactions; |
749 | 0 | if (c->immutable_options().compaction_pri == kRoundRobin && |
750 | 0 | c->immutable_options().compaction_style == kCompactionStyleLevel) { |
751 | | // For round-robin compaction prioity, we need to employ more |
752 | | // subcompactions (may exceed the max_subcompaction limit). The extra |
753 | | // subcompactions will be executed using reserved threads and taken into |
754 | | // account bg_compaction_scheduled or bg_bottom_compaction_scheduled. |
755 | | |
756 | | // Initialized by the number of input files |
757 | 0 | num_planned_subcompactions = static_cast<uint64_t>(c->num_input_files(0)); |
758 | 0 | uint64_t max_subcompactions_limit = GetSubcompactionsLimit(); |
759 | 0 | if (max_subcompactions_limit < num_planned_subcompactions) { |
760 | | // Assert two pointers are not empty so that we can use extra |
761 | | // subcompactions against db compaction limits |
762 | 0 | assert(bg_bottom_compaction_scheduled_ != nullptr); |
763 | 0 | assert(bg_compaction_scheduled_ != nullptr); |
764 | | // Reserve resources when max_subcompaction is not sufficient |
765 | 0 | AcquireSubcompactionResources( |
766 | 0 | (int)(num_planned_subcompactions - max_subcompactions_limit)); |
767 | | // Subcompactions limit changes after acquiring additional resources. |
768 | | // Need to call GetSubcompactionsLimit() again to update the number |
769 | | // of planned subcompactions |
770 | 0 | num_planned_subcompactions = |
771 | 0 | std::min(num_planned_subcompactions, GetSubcompactionsLimit()); |
772 | 0 | } else { |
773 | 0 | num_planned_subcompactions = max_subcompactions_limit; |
774 | 0 | } |
775 | 0 | } else { |
776 | 0 | num_planned_subcompactions = GetSubcompactionsLimit(); |
777 | 0 | } |
778 | |
|
779 | 0 | TEST_SYNC_POINT_CALLBACK("CompactionJob::GenSubcompactionBoundaries:0", |
780 | 0 | &num_planned_subcompactions); |
781 | 0 | if (num_planned_subcompactions == 1) { |
782 | 0 | return; |
783 | 0 | } |
784 | | |
785 | | // Group the ranges into subcompactions |
786 | 0 | uint64_t target_range_size = std::max( |
787 | 0 | total_size / num_planned_subcompactions, |
788 | 0 | MaxFileSizeForLevel( |
789 | 0 | c->mutable_cf_options(), out_lvl, |
790 | 0 | c->immutable_options().compaction_style, base_level, |
791 | 0 | c->immutable_options().level_compaction_dynamic_level_bytes)); |
792 | |
|
793 | 0 | if (target_range_size >= total_size) { |
794 | 0 | return; |
795 | 0 | } |
796 | | |
797 | 0 | uint64_t next_threshold = target_range_size; |
798 | 0 | uint64_t cumulative_size = 0; |
799 | 0 | uint64_t num_actual_subcompactions = 1U; |
800 | 0 | for (TableReader::Anchor& anchor : all_anchors) { |
801 | 0 | cumulative_size += anchor.range_size; |
802 | 0 | if (cumulative_size > next_threshold) { |
803 | 0 | next_threshold += target_range_size; |
804 | 0 | num_actual_subcompactions++; |
805 | 0 | boundaries_.push_back(anchor.user_key); |
806 | 0 | } |
807 | 0 | if (num_actual_subcompactions == num_planned_subcompactions) { |
808 | 0 | break; |
809 | 0 | } |
810 | 0 | } |
811 | 0 | TEST_SYNC_POINT_CALLBACK("CompactionJob::GenSubcompactionBoundaries:1", |
812 | 0 | &num_actual_subcompactions); |
813 | | // Shrink extra subcompactions resources when extra resrouces are acquired |
814 | 0 | ShrinkSubcompactionResources( |
815 | 0 | std::min((int)(num_planned_subcompactions - num_actual_subcompactions), |
816 | 0 | extra_num_subcompaction_threads_reserved_)); |
817 | 0 | } |
818 | | |
819 | 2.77k | void CompactionJob::InitializeCompactionRun() { |
820 | 2.77k | AutoThreadOperationStageUpdater stage_updater( |
821 | 2.77k | ThreadStatus::STAGE_COMPACTION_RUN); |
822 | 2.77k | TEST_SYNC_POINT("CompactionJob::Run():Start"); |
823 | 2.77k | log_buffer_->FlushBufferToLog(); |
824 | 2.77k | LogCompaction(); |
825 | 2.77k | } |
826 | | |
827 | 2.77k | void CompactionJob::RunSubcompactions() { |
828 | 2.77k | TEST_SYNC_POINT("CompactionJob::RunSubcompactions:BeforeStart"); |
829 | 2.77k | const size_t num_threads = compact_->sub_compact_states.size(); |
830 | 2.77k | assert(num_threads > 0); |
831 | 2.77k | compact_->compaction->GetOrInitInputTableProperties(); |
832 | | |
833 | 2.77k | if (remote_compaction_executor_) { |
834 | 0 | const auto run_subcompaction = [this](SubcompactionState* sub_compact) { |
835 | 0 | try { |
836 | 0 | ProcessKeyValueCompaction(sub_compact); |
837 | 0 | } catch (...) { |
838 | 0 | sub_compact->status = Status::Aborted(); |
839 | 0 | } |
840 | 0 | }; |
841 | 0 | SubcompactionCompletion completion; |
842 | 0 | for (size_t i = 1; i < num_threads; ++i) { |
843 | 0 | try { |
844 | 0 | completion.Add(); |
845 | 0 | SubcompactionCompletion::Guard submission(completion); |
846 | 0 | remote_compaction_executor_([&, i] { |
847 | 0 | SubcompactionCompletion::Guard completed(completion); |
848 | 0 | run_subcompaction(&compact_->sub_compact_states[i]); |
849 | 0 | }); |
850 | 0 | submission.Release(); |
851 | 0 | } catch (...) { |
852 | 0 | compact_->sub_compact_states[0].status = Status::Aborted(); |
853 | 0 | for (size_t j = i; j < num_threads; ++j) { |
854 | 0 | compact_->sub_compact_states[j].status = Status::Aborted(); |
855 | 0 | } |
856 | 0 | break; |
857 | 0 | } |
858 | 0 | } |
859 | 0 | if (compact_->sub_compact_states[0].status.ok()) { |
860 | 0 | run_subcompaction(compact_->sub_compact_states.data()); |
861 | 0 | } |
862 | 2.77k | } else { |
863 | | // Launch a thread for each of subcompactions 1...num_threads-1 |
864 | 2.77k | std::vector<port::Thread> thread_pool; |
865 | 2.77k | thread_pool.reserve(num_threads - 1); |
866 | 2.77k | for (size_t i = 1; i < compact_->sub_compact_states.size(); i++) { |
867 | 0 | thread_pool.emplace_back(&CompactionJob::ProcessKeyValueCompaction, this, |
868 | 0 | &compact_->sub_compact_states[i]); |
869 | 0 | } |
870 | | |
871 | | // Always schedule the first subcompaction (whether or not there are also |
872 | | // others) in the current thread to be efficient with resources |
873 | 2.77k | ProcessKeyValueCompaction(compact_->sub_compact_states.data()); |
874 | | |
875 | | // Wait for all other threads (if there are any) to finish execution |
876 | 2.77k | for (auto& thread : thread_pool) { |
877 | 0 | thread.join(); |
878 | 0 | } |
879 | 2.77k | } |
880 | 2.77k | RecordRemoteCompactionInputBlobFileCount(); |
881 | 2.77k | RemoveEmptyOutputs(); |
882 | | |
883 | 2.77k | ReleaseSubcompactionResources(); |
884 | 2.77k | TEST_SYNC_POINT("CompactionJob::ReleaseSubcompactionResources"); |
885 | 2.77k | } |
886 | | |
887 | 2.77k | void CompactionJob::RecordRemoteCompactionInputBlobFileCount() { |
888 | 2.77k | const uint64_t oldest_blob_file_number = |
889 | 2.77k | remote_compaction_min_blob_file_number_.load(std::memory_order_relaxed); |
890 | 2.77k | if (oldest_blob_file_number == std::numeric_limits<uint64_t>::max()) { |
891 | 2.77k | return; |
892 | 2.77k | } |
893 | | |
894 | 0 | uint64_t blob_file_count = 0; |
895 | 0 | const VersionStorageInfo* const storage_info = |
896 | 0 | compact_->compaction->input_version()->storage_info(); |
897 | 0 | for (const auto& blob_file : storage_info->GetBlobFiles()) { |
898 | 0 | if (blob_file->GetBlobFileNumber() >= oldest_blob_file_number) { |
899 | 0 | ++blob_file_count; |
900 | 0 | } |
901 | 0 | } |
902 | 0 | RecordTick(stats_, REMOTE_COMPACT_INPUT_BLOB_FILE_COUNT, blob_file_count); |
903 | 0 | } |
904 | | |
905 | 2.77k | void CompactionJob::UpdateTimingStats(uint64_t start_micros) { |
906 | 2.77k | internal_stats_.SetMicros(db_options_.clock->NowMicros() - start_micros); |
907 | | |
908 | 2.77k | for (auto& state : compact_->sub_compact_states) { |
909 | 2.77k | internal_stats_.AddCpuMicros(state.compaction_job_stats.cpu_micros); |
910 | 2.77k | } |
911 | | |
912 | 2.77k | RecordTimeToHistogram(stats_, COMPACTION_TIME, |
913 | 2.77k | internal_stats_.output_level_stats.micros); |
914 | 2.77k | RecordTimeToHistogram(stats_, COMPACTION_CPU_TIME, |
915 | 2.77k | internal_stats_.output_level_stats.cpu_micros); |
916 | 2.77k | } |
917 | | |
918 | 2.77k | void CompactionJob::RemoveEmptyOutputs() { |
919 | 2.77k | for (auto& state : compact_->sub_compact_states) { |
920 | 2.77k | state.RemoveLastEmptyOutput(); |
921 | 2.77k | } |
922 | 2.77k | } |
923 | | |
924 | 0 | void CompactionJob::CleanupAbortedSubcompactions() { |
925 | 0 | ColumnFamilyData* cfd = compact_->compaction->column_family_data(); |
926 | |
|
927 | 0 | uint64_t total_sst_files_deleted = 0; |
928 | 0 | uint64_t total_blob_files_deleted = 0; |
929 | | |
930 | | // Track the first file deletion error to report at the end |
931 | 0 | Status first_error; |
932 | 0 | int deletion_errors = 0; |
933 | | |
934 | | // Mark all subcompactions as aborted and delete their output files |
935 | 0 | for (auto& sub_compact : compact_->sub_compact_states) { |
936 | | // Mark this subcompaction as aborted |
937 | 0 | sub_compact.status = |
938 | 0 | Status::Incomplete(Status::SubCode::kCompactionAborted); |
939 | | |
940 | | // Delete all files (SST and blob) tracked during compaction. |
941 | | // GetOutputFilePaths() contains ALL file paths created, including |
942 | | // in-progress files that may have been removed from outputs_ or |
943 | | // blob_file_additions_. |
944 | 0 | for (const bool is_proximal_level : {false, true}) { |
945 | 0 | if (is_proximal_level && |
946 | 0 | !compact_->compaction->SupportsPerKeyPlacement()) { |
947 | 0 | continue; |
948 | 0 | } |
949 | 0 | for (const std::string& file_path : |
950 | 0 | sub_compact.Outputs(is_proximal_level)->GetOutputFilePaths()) { |
951 | 0 | Status s = env_->DeleteFile(file_path); |
952 | 0 | if (s.ok()) { |
953 | | // Count SST vs blob files by checking extension |
954 | 0 | if (file_path.find(".sst") != std::string::npos) { |
955 | 0 | total_sst_files_deleted++; |
956 | 0 | } else if (file_path.find(".blob") != std::string::npos) { |
957 | 0 | total_blob_files_deleted++; |
958 | 0 | } |
959 | 0 | } else if (!s.IsNotFound()) { |
960 | 0 | if (first_error.ok()) { |
961 | 0 | first_error = s; |
962 | 0 | } |
963 | 0 | deletion_errors++; |
964 | 0 | } |
965 | 0 | } |
966 | 0 | } |
967 | 0 | sub_compact.CleanupOutputs(); |
968 | 0 | } |
969 | |
|
970 | 0 | if (stats_) { |
971 | 0 | RecordTick(stats_, COMPACTION_ABORTED); |
972 | 0 | } |
973 | |
|
974 | 0 | ROCKS_LOG_INFO(db_options_.info_log, |
975 | 0 | "[%s] [JOB %d] Compaction aborted: deleted %" PRIu64 |
976 | 0 | " SST files and %" PRIu64 " blob files", |
977 | 0 | cfd->GetName().c_str(), job_id_, total_sst_files_deleted, |
978 | 0 | total_blob_files_deleted); |
979 | |
|
980 | 0 | if (!first_error.ok()) { |
981 | 0 | ROCKS_LOG_ERROR(db_options_.info_log, |
982 | 0 | "[%s] [JOB %d] Cleanup completed with %d file deletion " |
983 | 0 | "errors. First error: %s", |
984 | 0 | cfd->GetName().c_str(), job_id_, deletion_errors, |
985 | 0 | first_error.ToString().c_str()); |
986 | 0 | } |
987 | 0 | } |
988 | | |
989 | 2.07k | bool CompactionJob::HasNewBlobFiles() const { |
990 | 2.07k | for (const auto& state : compact_->sub_compact_states) { |
991 | 2.07k | if (state.Current().HasBlobFileAdditions()) { |
992 | 0 | return true; |
993 | 0 | } |
994 | 2.07k | } |
995 | 2.07k | return false; |
996 | 2.07k | } |
997 | | |
998 | 2.77k | Status CompactionJob::CollectSubcompactionErrors() { |
999 | 2.77k | Status status; |
1000 | 2.77k | IOStatus io_s; |
1001 | | |
1002 | 2.77k | for (const auto& state : compact_->sub_compact_states) { |
1003 | 2.77k | if (!state.status.ok()) { |
1004 | 700 | status = state.status; |
1005 | 700 | io_s = state.io_status; |
1006 | 700 | break; |
1007 | 700 | } |
1008 | 2.77k | } |
1009 | | |
1010 | 2.77k | if (io_status_.ok()) { |
1011 | 2.77k | io_status_ = io_s; |
1012 | 2.77k | } |
1013 | | |
1014 | 2.77k | return status; |
1015 | 2.77k | } |
1016 | | |
1017 | 2.07k | Status CompactionJob::SyncOutputDirectories() { |
1018 | 2.07k | Status status; |
1019 | 2.07k | IOStatus io_s; |
1020 | 2.07k | constexpr IODebugContext* dbg = nullptr; |
1021 | 2.07k | const bool wrote_new_blob_files = HasNewBlobFiles(); |
1022 | 2.07k | if (output_directory_) { |
1023 | 2.07k | io_s = output_directory_->FsyncWithDirOptions( |
1024 | 2.07k | IOOptions(), dbg, |
1025 | 2.07k | DirFsyncOptions(DirFsyncOptions::FsyncReason::kNewFileSynced)); |
1026 | 2.07k | } |
1027 | | |
1028 | 2.07k | if (io_s.ok() && wrote_new_blob_files && blob_output_directory_ && |
1029 | 0 | blob_output_directory_ != output_directory_) { |
1030 | 0 | io_s = blob_output_directory_->FsyncWithDirOptions( |
1031 | 0 | IOOptions(), dbg, |
1032 | 0 | DirFsyncOptions(DirFsyncOptions::FsyncReason::kNewFileSynced)); |
1033 | 0 | } |
1034 | | |
1035 | 2.07k | if (io_status_.ok()) { |
1036 | 2.07k | io_status_ = io_s; |
1037 | 2.07k | } |
1038 | 2.07k | if (status.ok()) { |
1039 | 2.07k | status = io_s; |
1040 | 2.07k | } |
1041 | | |
1042 | 2.07k | return status; |
1043 | 2.07k | } |
1044 | | |
1045 | 2.07k | Status CompactionJob::VerifyOutputFiles() { |
1046 | 2.07k | Status status; |
1047 | 2.07k | std::vector<port::Thread> thread_pool; |
1048 | 2.07k | ColumnFamilyData* cfd = compact_->compaction->column_family_data(); |
1049 | 2.07k | VerifyOutputFlags verify_output_flags = |
1050 | 2.07k | compact_->compaction->mutable_cf_options().verify_output_flags; |
1051 | | |
1052 | | // For backward compatibility |
1053 | 2.07k | if (paranoid_file_checks_) { |
1054 | 0 | verify_output_flags |= VerifyOutputFlags::kVerifyIteration; |
1055 | 0 | verify_output_flags |= VerifyOutputFlags::kEnableForLocalCompaction; |
1056 | 0 | verify_output_flags |= VerifyOutputFlags::kEnableForRemoteCompaction; |
1057 | 0 | } |
1058 | | |
1059 | 2.07k | auto verify_table = [&](SubcompactionState& subcompaction_state) { |
1060 | | // Collect file open metadata during verification when fast_sst_open |
1061 | | // is enabled, keyed by file number. |
1062 | 2.07k | UnorderedMap<uint64_t, std::string> file_open_metadata_map; |
1063 | | |
1064 | 2.07k | for (const auto& output_file : subcompaction_state.GetOutputs()) { |
1065 | | // Verify that the table is usable |
1066 | | // We set for_compaction to false and don't |
1067 | | // OptimizeForCompactionTableRead here because this is a special case |
1068 | | // after we finish the table building No matter whether |
1069 | | // use_direct_io_for_flush_and_compaction is true, we will regard this |
1070 | | // verification as user reads since the goal is to cache it here for |
1071 | | // further user reads |
1072 | 1.21k | ReadOptions verification_read_options(Env::IOActivity::kCompaction); |
1073 | 1.21k | verification_read_options.verify_checksums = true; |
1074 | 1.21k | verification_read_options.readahead_size = |
1075 | 1.21k | file_options_for_read_.compaction_readahead_size; |
1076 | | |
1077 | 1.21k | std::unique_ptr<TableReader> table_reader_guard; |
1078 | 1.21k | TableReader* table_reader_ptr = table_reader_guard.get(); |
1079 | 1.21k | verification_read_options.rate_limiter_priority = |
1080 | 1.21k | GetRateLimiterPriority(); |
1081 | 1.21k | std::string file_open_metadata; |
1082 | 1.21k | std::string* file_open_metadata_ptr = |
1083 | 1.21k | mutable_db_options_copy_.fast_sst_open ? &file_open_metadata |
1084 | 1.21k | : nullptr; |
1085 | 1.21k | InternalIterator* iter = cfd->table_cache()->NewIterator( |
1086 | 1.21k | verification_read_options, file_options_, cfd->internal_comparator(), |
1087 | 1.21k | output_file.meta, |
1088 | 1.21k | /*range_del_agg=*/nullptr, compact_->compaction->mutable_cf_options(), |
1089 | 1.21k | /*table_reader_ptr=*/&table_reader_ptr, |
1090 | 1.21k | cfd->internal_stats()->GetFileReadHist( |
1091 | 1.21k | compact_->compaction->output_level()), |
1092 | 1.21k | TableReaderCaller::kCompactionRefill, /*arena=*/nullptr, |
1093 | 1.21k | /*skip_filters=*/false, compact_->compaction->output_level(), |
1094 | 1.21k | MaxFileSizeForL0MetaPin(compact_->compaction->mutable_cf_options()), |
1095 | 1.21k | /*smallest_compaction_key=*/nullptr, |
1096 | 1.21k | /*largest_compaction_key=*/nullptr, |
1097 | 1.21k | /*allow_unprepared_value=*/false, /*seek_target=*/nullptr, |
1098 | 1.21k | /*prefix_filtered_table=*/nullptr, |
1099 | 1.21k | /*range_del_read_seqno=*/nullptr, |
1100 | 1.21k | /*range_del_iter=*/nullptr, |
1101 | 1.21k | /*maybe_pin_table_handle=*/false, file_open_metadata_ptr); |
1102 | 1.21k | auto s = iter->status(); |
1103 | 1.21k | if (s.ok()) { |
1104 | | // Check for remote/local compaction and verify_output_flags flags |
1105 | 1.21k | const bool should_verify = |
1106 | 1.21k | (subcompaction_state.compaction_job_stats.is_remote_compaction && |
1107 | 0 | !!(verify_output_flags & |
1108 | 0 | VerifyOutputFlags::kEnableForRemoteCompaction)) || |
1109 | 1.21k | (!subcompaction_state.compaction_job_stats.is_remote_compaction && |
1110 | 1.21k | !!(verify_output_flags & |
1111 | 1.21k | VerifyOutputFlags::kEnableForLocalCompaction)); |
1112 | | |
1113 | 1.21k | if (should_verify) { |
1114 | 0 | const bool should_verify_block_checksum = |
1115 | 0 | !!(verify_output_flags & VerifyOutputFlags::kVerifyBlockChecksum); |
1116 | 0 | const bool should_verify_iteration = |
1117 | 0 | !!(verify_output_flags & VerifyOutputFlags::kVerifyIteration); |
1118 | 0 | const bool should_verify_file_checksum = |
1119 | 0 | !!(verify_output_flags & |
1120 | 0 | VerifyOutputFlags::kVerifyFileChecksum) && |
1121 | 0 | db_options_.file_checksum_gen_factory != nullptr && |
1122 | 0 | output_file.meta.file_checksum != kUnknownFileChecksum; |
1123 | 0 | if (should_verify_block_checksum) { |
1124 | 0 | assert(table_reader_ptr != nullptr); |
1125 | | // If verifying iteration as well, verify meta blocks here only to |
1126 | | // avoid redundant checks on data blocks |
1127 | 0 | s = table_reader_ptr->VerifyChecksum( |
1128 | 0 | verification_read_options, TableReaderCaller::kCompaction, |
1129 | 0 | /*meta_blocks_only=*/should_verify_iteration); |
1130 | 0 | } |
1131 | 0 | if (s.ok() && should_verify_iteration) { |
1132 | 0 | OutputValidator validator(cfd->internal_comparator(), |
1133 | 0 | /*_enable_hash=*/true); |
1134 | 0 | for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { |
1135 | 0 | s = validator.Add(iter->key(), iter->value()); |
1136 | 0 | if (!s.ok()) { |
1137 | 0 | break; |
1138 | 0 | } |
1139 | 0 | } |
1140 | 0 | if (s.ok()) { |
1141 | 0 | s = iter->status(); |
1142 | 0 | } |
1143 | 0 | if (s.ok() && !validator.CompareValidator(output_file.validator)) { |
1144 | 0 | s = Status::Corruption( |
1145 | 0 | "Key-value checksum of compaction output doesn't match what " |
1146 | 0 | "was computed when written"); |
1147 | 0 | } |
1148 | 0 | } |
1149 | 0 | if (s.ok() && should_verify_file_checksum) { |
1150 | 0 | std::string file_checksum; |
1151 | 0 | std::string file_checksum_func_name; |
1152 | 0 | std::string fname = |
1153 | 0 | GetTableFileName(output_file.meta.fd.GetNumber()); |
1154 | 0 | s = GenerateOneFileChecksum( |
1155 | 0 | fs_.get(), fname, db_options_.file_checksum_gen_factory.get(), |
1156 | 0 | output_file.meta.file_checksum_func_name, &file_checksum, |
1157 | 0 | &file_checksum_func_name, |
1158 | 0 | verification_read_options.readahead_size, |
1159 | 0 | db_options_.allow_mmap_reads, io_tracer_, |
1160 | 0 | db_options_.rate_limiter.get(), verification_read_options, |
1161 | 0 | stats_, db_options_.clock, file_options_for_read_); |
1162 | 0 | if (s.ok() && file_checksum != output_file.meta.file_checksum) { |
1163 | 0 | s = Status::Corruption( |
1164 | 0 | "File checksum mismatch for compaction output file " + fname); |
1165 | 0 | } |
1166 | 0 | } |
1167 | 0 | } |
1168 | 1.21k | } |
1169 | | |
1170 | 1.21k | delete iter; |
1171 | | |
1172 | 1.21k | if (!s.ok()) { |
1173 | 0 | subcompaction_state.status = s; |
1174 | 0 | break; |
1175 | 0 | } |
1176 | | |
1177 | 1.21k | if (!file_open_metadata.empty()) { |
1178 | 0 | file_open_metadata_map[output_file.meta.fd.GetNumber()] = |
1179 | 0 | std::move(file_open_metadata); |
1180 | 0 | } |
1181 | 1.21k | } |
1182 | | |
1183 | | // Apply collected file open metadata to mutable outputs |
1184 | 2.07k | if (!file_open_metadata_map.empty()) { |
1185 | 0 | auto apply_metadata = |
1186 | 0 | [&file_open_metadata_map]( |
1187 | 0 | std::vector<CompactionOutputs::Output>& outputs) { |
1188 | 0 | for (auto& output : outputs) { |
1189 | 0 | auto it = file_open_metadata_map.find(output.meta.fd.GetNumber()); |
1190 | 0 | if (it != file_open_metadata_map.end()) { |
1191 | 0 | output.meta.file_open_metadata = std::move(it->second); |
1192 | 0 | } |
1193 | 0 | } |
1194 | 0 | }; |
1195 | 0 | apply_metadata(subcompaction_state.GetMutableCompactionOutputs()); |
1196 | 0 | apply_metadata(subcompaction_state.GetMutableProximalOutputs()); |
1197 | 0 | } |
1198 | 2.07k | }; |
1199 | 2.07k | for (size_t i = 1; i < compact_->sub_compact_states.size(); i++) { |
1200 | 0 | thread_pool.emplace_back(verify_table, |
1201 | 0 | std::ref(compact_->sub_compact_states[i])); |
1202 | 0 | } |
1203 | 2.07k | verify_table(compact_->sub_compact_states[0]); |
1204 | 2.07k | for (auto& thread : thread_pool) { |
1205 | 0 | thread.join(); |
1206 | 0 | } |
1207 | | |
1208 | 2.07k | for (const auto& state : compact_->sub_compact_states) { |
1209 | 2.07k | if (!state.status.ok()) { |
1210 | 0 | status = state.status; |
1211 | 0 | break; |
1212 | 0 | } |
1213 | 2.07k | } |
1214 | | |
1215 | 2.07k | return status; |
1216 | 2.07k | } |
1217 | | |
1218 | 2.07k | void CompactionJob::SetOutputTableProperties() { |
1219 | 2.07k | for (const auto& state : compact_->sub_compact_states) { |
1220 | 2.07k | for (const auto& output : state.GetOutputs()) { |
1221 | 1.21k | auto fn = |
1222 | 1.21k | TableFileName(state.compaction->immutable_options().cf_paths, |
1223 | 1.21k | output.meta.fd.GetNumber(), output.meta.fd.GetPathId()); |
1224 | 1.21k | compact_->compaction->SetOutputTableProperties(fn, |
1225 | 1.21k | output.table_properties); |
1226 | 1.21k | } |
1227 | 2.07k | } |
1228 | 2.07k | } |
1229 | | |
1230 | 2.77k | void CompactionJob::AggregateSubcompactionOutputAndJobStats() { |
1231 | | // Before the compaction starts, is_remote_compaction was set to true if |
1232 | | // compaction_service is set. We now know whether each sub_compaction was |
1233 | | // done remotely or not. Reset is_remote_compaction back to false and allow |
1234 | | // AggregateCompactionStats() to set the right value. |
1235 | 2.77k | job_stats_->is_remote_compaction = false; |
1236 | | |
1237 | | // Finish up all bookkeeping to unify the subcompaction results. |
1238 | 2.77k | compact_->AggregateCompactionStats(internal_stats_, *job_stats_); |
1239 | 2.77k | } |
1240 | | |
1241 | | Status CompactionJob::VerifyCompactionRecordCounts( |
1242 | 2.07k | bool stats_built_from_input_table_prop, uint64_t num_input_range_del) { |
1243 | 2.07k | Status status; |
1244 | 2.07k | if (stats_built_from_input_table_prop && |
1245 | 2.07k | job_stats_->has_accurate_num_input_records) { |
1246 | 2.07k | status = VerifyInputRecordCount(num_input_range_del); |
1247 | 2.07k | if (!status.ok()) { |
1248 | 0 | return status; |
1249 | 0 | } |
1250 | 2.07k | } |
1251 | | |
1252 | 2.07k | const auto& mutable_cf_options = compact_->compaction->mutable_cf_options(); |
1253 | 2.07k | if ((mutable_cf_options.table_factory->IsInstanceOf( |
1254 | 2.07k | TableFactory::kBlockBasedTableName()) || |
1255 | 0 | mutable_cf_options.table_factory->IsInstanceOf( |
1256 | 2.07k | TableFactory::kPlainTableName()))) { |
1257 | 2.07k | status = VerifyOutputRecordCount(); |
1258 | 2.07k | if (!status.ok()) { |
1259 | 0 | return status; |
1260 | 0 | } |
1261 | 2.07k | } |
1262 | 2.07k | return status; |
1263 | 2.07k | } |
1264 | | |
1265 | | void CompactionJob::FinalizeCompactionRun( |
1266 | | const Status& input_status, bool stats_built_from_input_table_prop, |
1267 | 2.77k | uint64_t num_input_range_del) { |
1268 | 2.77k | if (stats_built_from_input_table_prop) { |
1269 | 2.77k | UpdateCompactionJobInputStatsFromInternalStats(internal_stats_, |
1270 | 2.77k | num_input_range_del); |
1271 | 2.77k | } |
1272 | 2.77k | UpdateCompactionJobOutputStatsFromInternalStats(input_status, |
1273 | 2.77k | internal_stats_); |
1274 | 2.77k | RecordCompactionIOStats(); |
1275 | | |
1276 | 2.77k | LogFlush(db_options_.info_log); |
1277 | 2.77k | TEST_SYNC_POINT("CompactionJob::Run():End"); |
1278 | 2.77k | compact_->status = input_status; |
1279 | 2.77k | TEST_SYNC_POINT_CALLBACK("CompactionJob::Run():EndStatusSet", |
1280 | 2.77k | const_cast<Status*>(&input_status)); |
1281 | 2.77k | } |
1282 | | |
1283 | 2.77k | Status CompactionJob::Run() { |
1284 | 2.77k | InitializeCompactionRun(); |
1285 | | |
1286 | 2.77k | const uint64_t start_micros = db_options_.clock->NowMicros(); |
1287 | | |
1288 | 2.77k | RunSubcompactions(); |
1289 | | |
1290 | 2.77k | UpdateTimingStats(start_micros); |
1291 | | |
1292 | 2.77k | TEST_SYNC_POINT("CompactionJob::Run:BeforeVerify"); |
1293 | | |
1294 | 2.77k | Status status = CollectSubcompactionErrors(); |
1295 | | |
1296 | | // If compaction was aborted or manually paused, clean up any output files |
1297 | | // from completed subcompactions to prevent orphaned files on disk. |
1298 | | // Skip cleanup for resumable compaction (when progress writer is set) |
1299 | | // because the output files are needed for resumption. |
1300 | 2.77k | if ((status.IsCompactionAborted() || status.IsManualCompactionPaused()) && |
1301 | 0 | compaction_progress_writer_ == nullptr) { |
1302 | 0 | CleanupAbortedSubcompactions(); |
1303 | 0 | } |
1304 | | |
1305 | 2.77k | if (status.ok()) { |
1306 | 2.07k | status = SyncOutputDirectories(); |
1307 | 2.07k | } |
1308 | | |
1309 | 2.77k | if (status.ok()) { |
1310 | 2.07k | status = VerifyOutputFiles(); |
1311 | 2.07k | } |
1312 | | |
1313 | 2.77k | TEST_SYNC_POINT_CALLBACK("CompactionJob::Run():AfterVerifyOutputFiles", |
1314 | 2.77k | &status); |
1315 | | |
1316 | 2.77k | if (status.ok()) { |
1317 | 2.07k | SetOutputTableProperties(); |
1318 | 2.07k | } |
1319 | | |
1320 | 2.77k | AggregateSubcompactionOutputAndJobStats(); |
1321 | | |
1322 | 2.77k | uint64_t num_input_range_del = 0; |
1323 | 2.77k | bool stats_built_from_input_table_prop = |
1324 | 2.77k | UpdateInternalStatsFromInputFiles(&num_input_range_del); |
1325 | | |
1326 | 2.77k | if (status.ok()) { |
1327 | 2.07k | status = VerifyCompactionRecordCounts(stats_built_from_input_table_prop, |
1328 | 2.07k | num_input_range_del); |
1329 | 2.07k | } |
1330 | | |
1331 | 2.77k | FinalizeCompactionRun(status, stats_built_from_input_table_prop, |
1332 | 2.77k | num_input_range_del); |
1333 | | |
1334 | 2.77k | return status; |
1335 | 2.77k | } |
1336 | | |
1337 | 2.77k | Status CompactionJob::Install(bool* compaction_released) { |
1338 | 2.77k | assert(compact_); |
1339 | | |
1340 | 2.77k | AutoThreadOperationStageUpdater stage_updater( |
1341 | 2.77k | ThreadStatus::STAGE_COMPACTION_INSTALL); |
1342 | 2.77k | db_mutex_->AssertHeld(); |
1343 | 2.77k | Status status = compact_->status; |
1344 | | |
1345 | 2.77k | ColumnFamilyData* cfd = compact_->compaction->column_family_data(); |
1346 | 2.77k | assert(cfd); |
1347 | | |
1348 | 2.77k | int output_level = compact_->compaction->output_level(); |
1349 | 2.77k | cfd->internal_stats()->AddCompactionStats(output_level, thread_pri_, |
1350 | 2.77k | internal_stats_); |
1351 | | |
1352 | 2.77k | if (status.ok()) { |
1353 | 2.07k | status = InstallCompactionResults(compaction_released); |
1354 | 2.07k | } |
1355 | 2.77k | if (!versions_->io_status().ok()) { |
1356 | 0 | io_status_ = versions_->io_status(); |
1357 | 0 | } |
1358 | | |
1359 | 2.77k | VersionStorageInfo::LevelSummaryStorage tmp; |
1360 | 2.77k | auto vstorage = cfd->current()->storage_info(); |
1361 | 2.77k | const auto& stats = internal_stats_.output_level_stats; |
1362 | | |
1363 | 2.77k | double read_write_amp = 0.0; |
1364 | 2.77k | double write_amp = 0.0; |
1365 | 2.77k | double bytes_read_per_sec = 0; |
1366 | 2.77k | double bytes_written_per_sec = 0; |
1367 | | |
1368 | 2.77k | const uint64_t bytes_read_non_output_and_blob = |
1369 | 2.77k | stats.bytes_read_non_output_levels + stats.bytes_read_blob; |
1370 | 2.77k | const uint64_t bytes_read_all = |
1371 | 2.77k | stats.bytes_read_output_level + bytes_read_non_output_and_blob; |
1372 | 2.77k | const uint64_t bytes_written_all = |
1373 | 2.77k | stats.bytes_written + stats.bytes_written_blob; |
1374 | | |
1375 | 2.77k | if (bytes_read_non_output_and_blob > 0) { |
1376 | 1.99k | read_write_amp = (bytes_written_all + bytes_read_all) / |
1377 | 1.99k | static_cast<double>(bytes_read_non_output_and_blob); |
1378 | 1.99k | write_amp = |
1379 | 1.99k | bytes_written_all / static_cast<double>(bytes_read_non_output_and_blob); |
1380 | 1.99k | } |
1381 | 2.77k | if (stats.micros > 0) { |
1382 | 2.77k | bytes_read_per_sec = bytes_read_all / static_cast<double>(stats.micros); |
1383 | 2.77k | bytes_written_per_sec = |
1384 | 2.77k | bytes_written_all / static_cast<double>(stats.micros); |
1385 | 2.77k | } |
1386 | | |
1387 | 2.77k | const std::string& column_family_name = cfd->GetName(); |
1388 | | |
1389 | 2.77k | constexpr double kMB = 1048576.0; |
1390 | | |
1391 | 2.77k | ROCKS_LOG_BUFFER( |
1392 | 2.77k | log_buffer_, |
1393 | 2.77k | "[%s] compacted to: %s, MB/sec: %.1f rd, %.1f wr, level %d, " |
1394 | 2.77k | "files in(%d, %d) filtered(%d, %d) out(%d +%d blob) " |
1395 | 2.77k | "MB in(%.1f, %.1f +%.1f blob) filtered(%.1f, %.1f) out(%.1f +%.1f blob), " |
1396 | 2.77k | "read-write-amplify(%.1f) write-amplify(%.1f) %s, records in: %" PRIu64 |
1397 | 2.77k | ", records dropped: %" PRIu64 " output_compression: %s\n", |
1398 | 2.77k | column_family_name.c_str(), vstorage->LevelSummary(&tmp), |
1399 | 2.77k | bytes_read_per_sec, bytes_written_per_sec, |
1400 | 2.77k | compact_->compaction->output_level(), |
1401 | 2.77k | stats.num_input_files_in_non_output_levels, |
1402 | 2.77k | stats.num_input_files_in_output_level, |
1403 | 2.77k | stats.num_filtered_input_files_in_non_output_levels, |
1404 | 2.77k | stats.num_filtered_input_files_in_output_level, stats.num_output_files, |
1405 | 2.77k | stats.num_output_files_blob, stats.bytes_read_non_output_levels / kMB, |
1406 | 2.77k | stats.bytes_read_output_level / kMB, stats.bytes_read_blob / kMB, |
1407 | 2.77k | stats.bytes_skipped_non_output_levels / kMB, |
1408 | 2.77k | stats.bytes_skipped_output_level / kMB, stats.bytes_written / kMB, |
1409 | 2.77k | stats.bytes_written_blob / kMB, read_write_amp, write_amp, |
1410 | 2.77k | status.ToString().c_str(), stats.num_input_records, |
1411 | 2.77k | stats.num_dropped_records, |
1412 | 2.77k | CompressionTypeToString(compact_->compaction->output_compression()) |
1413 | 2.77k | .c_str()); |
1414 | | |
1415 | 2.77k | const auto& blob_files = vstorage->GetBlobFiles(); |
1416 | 2.77k | if (!blob_files.empty()) { |
1417 | 0 | assert(blob_files.front()); |
1418 | 0 | assert(blob_files.back()); |
1419 | |
|
1420 | 0 | ROCKS_LOG_BUFFER( |
1421 | 0 | log_buffer_, |
1422 | 0 | "[%s] Blob file summary: head=%" PRIu64 ", tail=%" PRIu64 "\n", |
1423 | 0 | column_family_name.c_str(), blob_files.front()->GetBlobFileNumber(), |
1424 | 0 | blob_files.back()->GetBlobFileNumber()); |
1425 | 0 | } |
1426 | | |
1427 | 2.77k | if (internal_stats_.has_proximal_level_output) { |
1428 | 0 | ROCKS_LOG_BUFFER(log_buffer_, |
1429 | 0 | "[%s] has Proximal Level output: %" PRIu64 |
1430 | 0 | ", level %d, number of files: %" PRIu64 |
1431 | 0 | ", number of records: %" PRIu64, |
1432 | 0 | column_family_name.c_str(), |
1433 | 0 | internal_stats_.proximal_level_stats.bytes_written, |
1434 | 0 | compact_->compaction->GetProximalLevel(), |
1435 | 0 | internal_stats_.proximal_level_stats.num_output_files, |
1436 | 0 | internal_stats_.proximal_level_stats.num_output_records); |
1437 | 0 | } |
1438 | | |
1439 | 2.77k | TEST_SYNC_POINT_CALLBACK( |
1440 | 2.77k | "CompactionJob::Install:AfterUpdateCompactionJobStats", job_stats_); |
1441 | | |
1442 | 2.77k | auto stream = event_logger_->LogToBuffer(log_buffer_, 8192); |
1443 | 2.77k | stream << "job" << job_id_ << "event" << "compaction_finished" |
1444 | 2.77k | << "compaction_time_micros" << stats.micros |
1445 | 2.77k | << "compaction_time_cpu_micros" << stats.cpu_micros << "output_level" |
1446 | 2.77k | << compact_->compaction->output_level() << "num_output_files" |
1447 | 2.77k | << stats.num_output_files << "total_output_size" |
1448 | 2.77k | << stats.bytes_written; |
1449 | | |
1450 | 2.77k | if (stats.num_output_files_blob > 0) { |
1451 | 0 | stream << "num_blob_output_files" << stats.num_output_files_blob |
1452 | 0 | << "total_blob_output_size" << stats.bytes_written_blob; |
1453 | 0 | } |
1454 | | |
1455 | 2.77k | stream << "num_input_records" << stats.num_input_records |
1456 | 2.77k | << "num_output_records" << stats.num_output_records |
1457 | 2.77k | << "num_subcompactions" << compact_->sub_compact_states.size() |
1458 | 2.77k | << "output_compression" |
1459 | 2.77k | << CompressionTypeToString(compact_->compaction->output_compression()); |
1460 | | |
1461 | 2.77k | stream << "num_single_delete_mismatches" |
1462 | 2.77k | << job_stats_->num_single_del_mismatch; |
1463 | 2.77k | stream << "num_single_delete_fallthrough" |
1464 | 2.77k | << job_stats_->num_single_del_fallthru; |
1465 | | |
1466 | 2.77k | if (measure_io_stats_) { |
1467 | 0 | stream << "file_write_nanos" << job_stats_->file_write_nanos; |
1468 | 0 | stream << "file_range_sync_nanos" << job_stats_->file_range_sync_nanos; |
1469 | 0 | stream << "file_fsync_nanos" << job_stats_->file_fsync_nanos; |
1470 | 0 | stream << "file_prepare_write_nanos" |
1471 | 0 | << job_stats_->file_prepare_write_nanos; |
1472 | 0 | } |
1473 | | |
1474 | 2.77k | stream << "lsm_state"; |
1475 | 2.77k | stream.StartArray(); |
1476 | 22.1k | for (int level = 0; level < vstorage->num_levels(); ++level) { |
1477 | 19.4k | stream << vstorage->NumLevelFiles(level); |
1478 | 19.4k | } |
1479 | 2.77k | stream.EndArray(); |
1480 | | |
1481 | 2.77k | if (!blob_files.empty()) { |
1482 | 0 | assert(blob_files.front()); |
1483 | 0 | stream << "blob_file_head" << blob_files.front()->GetBlobFileNumber(); |
1484 | |
|
1485 | 0 | assert(blob_files.back()); |
1486 | 0 | stream << "blob_file_tail" << blob_files.back()->GetBlobFileNumber(); |
1487 | 0 | } |
1488 | | |
1489 | 2.77k | if (internal_stats_.has_proximal_level_output) { |
1490 | 0 | InternalStats::CompactionStats& pl_stats = |
1491 | 0 | internal_stats_.proximal_level_stats; |
1492 | 0 | stream << "proximal_level_num_output_files" << pl_stats.num_output_files; |
1493 | 0 | stream << "proximal_level_bytes_written" << pl_stats.bytes_written; |
1494 | 0 | stream << "proximal_level_num_output_records" |
1495 | 0 | << pl_stats.num_output_records; |
1496 | 0 | stream << "proximal_level_num_output_files_blob" |
1497 | 0 | << pl_stats.num_output_files_blob; |
1498 | 0 | stream << "proximal_level_bytes_written_blob" |
1499 | 0 | << pl_stats.bytes_written_blob; |
1500 | 0 | } |
1501 | | |
1502 | | // Propagate Install failure to compact_->status so that |
1503 | | // CleanupCompaction() -> SubcompactionState::Cleanup() sees the failure and |
1504 | | // calls ReleaseObsolete on output files' table cache entries. Without this, |
1505 | | // if Run() succeeds but InstallCompactionResults() fails, Cleanup would see |
1506 | | // overall_status = OK and skip ReleaseObsolete, leaking entries for output |
1507 | | // files that were never installed into any Version. |
1508 | 2.77k | if (!status.ok() && compact_->status.ok()) { |
1509 | 0 | compact_->status = status; |
1510 | 0 | } |
1511 | | |
1512 | 2.77k | CleanupCompaction(); |
1513 | 2.77k | return status; |
1514 | 2.77k | } |
1515 | | |
1516 | | void CompactionJob::NotifyOnSubcompactionBegin( |
1517 | 2.77k | SubcompactionState* sub_compact) { |
1518 | 2.77k | Compaction* c = compact_->compaction; |
1519 | | |
1520 | 2.77k | if (db_options_.listeners.empty()) { |
1521 | 2.77k | return; |
1522 | 2.77k | } |
1523 | 0 | if (shutting_down_->load(std::memory_order_acquire)) { |
1524 | 0 | return; |
1525 | 0 | } |
1526 | 0 | if (c->is_manual_compaction() && |
1527 | 0 | manual_compaction_canceled_.load(std::memory_order_acquire)) { |
1528 | 0 | return; |
1529 | 0 | } |
1530 | | |
1531 | 0 | sub_compact->notify_on_subcompaction_completion = true; |
1532 | |
|
1533 | 0 | SubcompactionJobInfo info{}; |
1534 | 0 | sub_compact->BuildSubcompactionJobInfo(info); |
1535 | 0 | info.job_id = static_cast<int>(job_id_); |
1536 | 0 | info.separate_remote_compaction_pool_selected = |
1537 | 0 | compact_->compaction->separate_remote_compaction_pool_selected(); |
1538 | 0 | info.thread_id = env_->GetThreadID(); |
1539 | |
|
1540 | 0 | for (const auto& listener : db_options_.listeners) { |
1541 | 0 | listener->OnSubcompactionBegin(info); |
1542 | 0 | } |
1543 | 0 | info.status.PermitUncheckedError(); |
1544 | 0 | } |
1545 | | |
1546 | | void CompactionJob::NotifyOnSubcompactionCompleted( |
1547 | 2.77k | SubcompactionState* sub_compact) { |
1548 | 2.77k | if (db_options_.listeners.empty()) { |
1549 | 2.77k | return; |
1550 | 2.77k | } |
1551 | 0 | if (shutting_down_->load(std::memory_order_acquire)) { |
1552 | 0 | return; |
1553 | 0 | } |
1554 | | |
1555 | 0 | if (sub_compact->notify_on_subcompaction_completion == false) { |
1556 | 0 | return; |
1557 | 0 | } |
1558 | 0 | sub_compact->notify_on_subcompaction_completion = false; |
1559 | |
|
1560 | 0 | SubcompactionJobInfo info{}; |
1561 | 0 | sub_compact->BuildSubcompactionJobInfo(info); |
1562 | 0 | info.job_id = static_cast<int>(job_id_); |
1563 | 0 | info.separate_remote_compaction_pool_selected = |
1564 | 0 | compact_->compaction->separate_remote_compaction_pool_selected(); |
1565 | 0 | info.thread_id = env_->GetThreadID(); |
1566 | |
|
1567 | 0 | for (const auto& listener : db_options_.listeners) { |
1568 | 0 | listener->OnSubcompactionCompleted(info); |
1569 | 0 | } |
1570 | 0 | } |
1571 | | |
1572 | 2.77k | bool CompactionJob::ShouldUseLocalCompaction(SubcompactionState* sub_compact) { |
1573 | 2.77k | if (sub_compact->compaction->RepairsMissingBlobReferences()) { |
1574 | | // Remote workers do not receive skip_missing_blob_files, and result |
1575 | | // validation rejects garbage updates for blob files absent from the |
1576 | | // primary's input Version, which repair produces. Keep repair local until |
1577 | | // the service supports this policy. |
1578 | 0 | sub_compact->compaction_job_stats.is_remote_compaction = false; |
1579 | 0 | return true; |
1580 | 0 | } |
1581 | 2.77k | if (db_options_.compaction_service) { |
1582 | 0 | const ImmutableCFOptions& options = |
1583 | 0 | sub_compact->compaction->column_family_data()->ioptions(); |
1584 | 0 | const CompactionFilterFactory* factory = nullptr; |
1585 | 0 | if (options.compaction_filter == nullptr && |
1586 | 0 | options.compaction_filter_factory && |
1587 | 0 | options.compaction_filter_factory->ShouldFilterTableFileCreation( |
1588 | 0 | TableFileCreationReason::kCompaction)) { |
1589 | 0 | factory = options.compaction_filter_factory.get(); |
1590 | 0 | } |
1591 | 0 | CompactionFilter::MergeOrder order; |
1592 | 0 | Status s = GetCompactionFilterMergeOrder( |
1593 | 0 | options.compaction_filter, factory, |
1594 | 0 | TableFileCreationReason::kCompaction, &order); |
1595 | 0 | if (!s.ok()) { |
1596 | 0 | sub_compact->status = s; |
1597 | 0 | return false; |
1598 | 0 | } |
1599 | 0 | if (order == CompactionFilter::MergeOrder::kMergeBeforeFilter && |
1600 | 0 | !db_options_.compaction_service->SupportsMergeBeforeFilter()) { |
1601 | 0 | TEST_SYNC_POINT( |
1602 | 0 | "CompactionJob::ShouldUseLocalCompaction:MergeBeforeFilterLocal"); |
1603 | 0 | sub_compact->compaction_job_stats.is_remote_compaction = false; |
1604 | 0 | return true; |
1605 | 0 | } |
1606 | 0 | CompactionServiceJobStatus comp_status = |
1607 | 0 | ProcessKeyValueCompactionWithCompactionService(sub_compact, order); |
1608 | 0 | if (comp_status != CompactionServiceJobStatus::kUseLocal) { |
1609 | 0 | return false; |
1610 | 0 | } |
1611 | | // fallback to local compaction |
1612 | 0 | assert(comp_status == CompactionServiceJobStatus::kUseLocal); |
1613 | 0 | sub_compact->compaction_job_stats.is_remote_compaction = false; |
1614 | 0 | } |
1615 | 2.77k | return true; |
1616 | 2.77k | } |
1617 | | |
1618 | 2.77k | CompactionJob::CompactionIOStatsSnapshot CompactionJob::InitializeIOStats() { |
1619 | 2.77k | CompactionIOStatsSnapshot io_stats; |
1620 | | |
1621 | 2.77k | if (measure_io_stats_) { |
1622 | 0 | io_stats.prev_perf_level = GetPerfLevel(); |
1623 | 0 | SetPerfLevel(PerfLevel::kEnableTimeAndCPUTimeExceptForMutex); |
1624 | 0 | io_stats.prev_write_nanos = IOSTATS(write_nanos); |
1625 | 0 | io_stats.prev_fsync_nanos = IOSTATS(fsync_nanos); |
1626 | 0 | io_stats.prev_range_sync_nanos = IOSTATS(range_sync_nanos); |
1627 | 0 | io_stats.prev_prepare_write_nanos = IOSTATS(prepare_write_nanos); |
1628 | 0 | io_stats.prev_cpu_write_nanos = IOSTATS(cpu_write_nanos); |
1629 | 0 | io_stats.prev_cpu_read_nanos = IOSTATS(cpu_read_nanos); |
1630 | 0 | } |
1631 | | |
1632 | 2.77k | return io_stats; |
1633 | 2.77k | } |
1634 | | |
1635 | | Status CompactionJob::SetupAndValidateCompactionFilter( |
1636 | | SubcompactionState* sub_compact, |
1637 | | const CompactionFilter* configured_compaction_filter, |
1638 | | const CompactionFilter*& compaction_filter, |
1639 | | std::unique_ptr<CompactionFilter>& compaction_filter_from_factory, |
1640 | 2.77k | CompactionFilter::MergeOrder& merge_order) { |
1641 | 2.77k | compaction_filter = configured_compaction_filter; |
1642 | | |
1643 | 2.77k | if (compaction_filter == nullptr) { |
1644 | 2.77k | compaction_filter_from_factory = |
1645 | 2.77k | sub_compact->compaction->CreateCompactionFilter(); |
1646 | 2.77k | compaction_filter = compaction_filter_from_factory.get(); |
1647 | 2.77k | } |
1648 | | |
1649 | 2.77k | const CompactionFilterFactory* factory = |
1650 | 2.77k | configured_compaction_filter == nullptr |
1651 | 2.77k | ? sub_compact->compaction->column_family_data() |
1652 | 2.77k | ->ioptions() |
1653 | 2.77k | .compaction_filter_factory.get() |
1654 | 2.77k | : nullptr; |
1655 | 2.77k | Status s = GetCompactionFilterMergeOrder(compaction_filter, factory, |
1656 | 2.77k | TableFileCreationReason::kCompaction, |
1657 | 2.77k | &merge_order); |
1658 | 2.77k | if (!s.ok()) { |
1659 | 0 | return s; |
1660 | 0 | } |
1661 | 2.77k | if (compaction_filter == nullptr) { |
1662 | 2.77k | merge_order = CompactionFilter::MergeOrder::kFilterBeforeMerge; |
1663 | 2.77k | } |
1664 | 2.77k | if (compaction_filter && |
1665 | 0 | merge_order == CompactionFilter::MergeOrder::kMergeBeforeFilter && |
1666 | 0 | (job_context_->snapshot_checker != nullptr || |
1667 | 0 | sub_compact->compaction->column_family_data() |
1668 | 0 | ->user_comparator() |
1669 | 0 | ->timestamp_size() != 0 || |
1670 | 0 | compaction_filter->IsStackedBlobDbInternalCompactionFilter())) { |
1671 | 0 | return Status::NotSupported( |
1672 | 0 | "Merge-before-filter does not support SnapshotChecker, timestamps, " |
1673 | 0 | "or stacked BlobDB"); |
1674 | 0 | } |
1675 | | |
1676 | 2.77k | if (compaction_filter != nullptr && !compaction_filter->IgnoreSnapshots()) { |
1677 | 0 | return Status::NotSupported( |
1678 | 0 | "CompactionFilter::IgnoreSnapshots() = false is not supported " |
1679 | 0 | "anymore."); |
1680 | 0 | } |
1681 | | |
1682 | 2.77k | return ValidateCompactionFilterMergeOrder(compaction_filter, merge_order); |
1683 | 2.77k | } |
1684 | | |
1685 | | void CompactionJob::InitializeReadOptionsAndBoundaries( |
1686 | | const size_t ts_sz, ReadOptions& read_options, |
1687 | 2.77k | SubcompactionKeyBoundaries& boundaries) { |
1688 | 2.77k | read_options.verify_checksums = true; |
1689 | 2.77k | read_options.fill_cache = false; |
1690 | 2.77k | read_options.rate_limiter_priority = GetRateLimiterPriority(); |
1691 | 2.77k | read_options.io_activity = Env::IOActivity::kCompaction; |
1692 | | // Compaction iterators shouldn't be confined to a single prefix. |
1693 | | // Compactions use Seek() for |
1694 | | // (a) concurrent compactions, |
1695 | | // (b) CompactionFilter::Decision::kRemoveAndSkipUntil. |
1696 | 2.77k | read_options.total_order_seek = true; |
1697 | | |
1698 | | // Remove the timestamps from boundaries because boundaries created in |
1699 | | // GenSubcompactionBoundaries doesn't strip away the timestamp. |
1700 | 2.77k | if (boundaries.start.has_value()) { |
1701 | 0 | read_options.iterate_lower_bound = &(*boundaries.start); |
1702 | 0 | if (ts_sz > 0) { |
1703 | 0 | boundaries.start_without_ts = |
1704 | 0 | StripTimestampFromUserKey(*boundaries.start, ts_sz); |
1705 | 0 | read_options.iterate_lower_bound = &(*boundaries.start_without_ts); |
1706 | 0 | } |
1707 | 0 | } |
1708 | 2.77k | if (boundaries.end.has_value()) { |
1709 | 0 | read_options.iterate_upper_bound = &(*boundaries.end); |
1710 | 0 | if (ts_sz > 0) { |
1711 | 0 | boundaries.end_without_ts = |
1712 | 0 | StripTimestampFromUserKey(*boundaries.end, ts_sz); |
1713 | 0 | read_options.iterate_upper_bound = &(*boundaries.end_without_ts); |
1714 | 0 | } |
1715 | 0 | } |
1716 | | |
1717 | 2.77k | if (ts_sz > 0) { |
1718 | 0 | if (ts_sz <= strlen(boundaries.kMaxTs)) { |
1719 | 0 | boundaries.ts_slice = Slice(boundaries.kMaxTs, ts_sz); |
1720 | 0 | } else { |
1721 | 0 | boundaries.max_ts = std::string(ts_sz, '\xff'); |
1722 | 0 | boundaries.ts_slice = Slice(boundaries.max_ts); |
1723 | 0 | } |
1724 | 0 | } |
1725 | 2.77k | if (boundaries.start.has_value()) { |
1726 | 0 | boundaries.start_ikey.SetInternalKey(*boundaries.start, kMaxSequenceNumber, |
1727 | 0 | kValueTypeForSeek); |
1728 | 0 | if (ts_sz > 0) { |
1729 | 0 | boundaries.start_ikey.UpdateInternalKey( |
1730 | 0 | kMaxSequenceNumber, kValueTypeForSeek, &boundaries.ts_slice); |
1731 | 0 | } |
1732 | 0 | boundaries.start_internal_key = boundaries.start_ikey.GetInternalKey(); |
1733 | 0 | boundaries.start_user_key = boundaries.start_ikey.GetUserKey(); |
1734 | 0 | } |
1735 | 2.77k | if (boundaries.end.has_value()) { |
1736 | 0 | boundaries.end_ikey.SetInternalKey(*boundaries.end, kMaxSequenceNumber, |
1737 | 0 | kValueTypeForSeek); |
1738 | 0 | if (ts_sz > 0) { |
1739 | 0 | boundaries.end_ikey.UpdateInternalKey( |
1740 | 0 | kMaxSequenceNumber, kValueTypeForSeek, &boundaries.ts_slice); |
1741 | 0 | } |
1742 | 0 | boundaries.end_internal_key = boundaries.end_ikey.GetInternalKey(); |
1743 | 0 | boundaries.end_user_key = boundaries.end_ikey.GetUserKey(); |
1744 | 0 | } |
1745 | 2.77k | } |
1746 | | |
1747 | | InternalIterator* CompactionJob::CreateInputIterator( |
1748 | | SubcompactionState* sub_compact, ColumnFamilyData* cfd, |
1749 | | SubcompactionInternalIterators& iterators, |
1750 | 2.77k | SubcompactionKeyBoundaries& boundaries, ReadOptions& read_options) { |
1751 | 2.77k | const size_t ts_sz = cfd->user_comparator()->timestamp_size(); |
1752 | 2.77k | InitializeReadOptionsAndBoundaries(ts_sz, read_options, boundaries); |
1753 | | |
1754 | | // This is assigned after creation of SubcompactionState to simplify that |
1755 | | // creation across both CompactionJob and CompactionServiceCompactionJob |
1756 | 2.77k | sub_compact->AssignRangeDelAggregator( |
1757 | 2.77k | std::make_unique<CompactionRangeDelAggregator>( |
1758 | 2.77k | &cfd->internal_comparator(), job_context_->snapshot_seqs, |
1759 | 2.77k | &full_history_ts_low_, &trim_ts_)); |
1760 | | |
1761 | | // Although the v2 aggregator is what the level iterator(s) know about, |
1762 | | // the AddTombstones calls will be propagated down to the v1 aggregator. |
1763 | 2.77k | const bool open_ephemeral_table_reader = |
1764 | 2.77k | db_options_.use_direct_io_for_compaction_reads && |
1765 | 0 | !db_options_.use_direct_reads; |
1766 | 2.77k | FileOptions& input_file_options = |
1767 | 2.77k | open_ephemeral_table_reader ? file_options_for_compaction_input_read_ |
1768 | 2.77k | : file_options_for_read_; |
1769 | 2.77k | TEST_SYNC_POINT_CALLBACK( |
1770 | 2.77k | "CompactionJob::CreateInputIterator:InputFileOptions", |
1771 | 2.77k | &input_file_options); |
1772 | 2.77k | iterators.raw_input = |
1773 | 2.77k | std::unique_ptr<InternalIterator>(versions_->MakeInputIterator( |
1774 | 2.77k | read_options, sub_compact->compaction, sub_compact->RangeDelAgg(), |
1775 | 2.77k | input_file_options, boundaries.start, boundaries.end, |
1776 | 2.77k | open_ephemeral_table_reader)); |
1777 | 2.77k | InternalIterator* input = iterators.raw_input.get(); |
1778 | | |
1779 | 2.77k | if (boundaries.start.has_value() || boundaries.end.has_value()) { |
1780 | 0 | iterators.clip = std::make_unique<ClippingIterator>( |
1781 | 0 | iterators.raw_input.get(), |
1782 | 0 | boundaries.start.has_value() ? &boundaries.start_internal_key : nullptr, |
1783 | 0 | boundaries.end.has_value() ? &boundaries.end_internal_key : nullptr, |
1784 | 0 | &cfd->internal_comparator()); |
1785 | 0 | input = iterators.clip.get(); |
1786 | 0 | } |
1787 | | |
1788 | 2.77k | if (sub_compact->compaction->DoesInputReferenceBlobFiles() || |
1789 | 2.77k | sub_compact->compaction->RepairsMissingBlobReferences()) { |
1790 | 0 | BlobGarbageMeter* meter = sub_compact->Current().GetBlobGarbageMeter(); |
1791 | 0 | if (meter == nullptr) { |
1792 | 0 | meter = sub_compact->Current().CreateBlobGarbageMeter(); |
1793 | 0 | } |
1794 | 0 | iterators.blob_counter = std::make_unique<BlobCountingIterator>( |
1795 | 0 | input, meter, compaction_progress_writer_ != nullptr); |
1796 | 0 | input = iterators.blob_counter.get(); |
1797 | 0 | } |
1798 | | |
1799 | 2.77k | if (ts_sz > 0 && !trim_ts_.empty()) { |
1800 | 0 | iterators.trim_history_iter = std::make_unique<HistoryTrimmingIterator>( |
1801 | 0 | input, cfd->user_comparator(), trim_ts_); |
1802 | 0 | input = iterators.trim_history_iter.get(); |
1803 | 0 | } |
1804 | | |
1805 | 2.77k | if (sub_compact->compaction->RepairsMissingBlobReferences()) { |
1806 | 0 | iterators.missing_blob_repair = std::make_unique<MissingBlobRepairIterator>( |
1807 | 0 | input, sub_compact->compaction->input_version()->storage_info(), ts_sz, |
1808 | | /*only_dropped_blob_files=*/ |
1809 | 0 | !sub_compact->compaction->skip_missing_blob_files()); |
1810 | 0 | input = iterators.missing_blob_repair.get(); |
1811 | 0 | } |
1812 | | |
1813 | 2.77k | return input; |
1814 | 2.77k | } |
1815 | | |
1816 | | void CompactionJob::CreateBlobFileBuilder( |
1817 | | SubcompactionState* sub_compact, ColumnFamilyData* cfd, |
1818 | | std::unique_ptr<BlobFileBuilder>& blob_file_builder, |
1819 | 2.77k | const WriteOptions& write_options) { |
1820 | 2.77k | const auto& mutable_cf_options = |
1821 | 2.77k | sub_compact->compaction->mutable_cf_options(); |
1822 | | |
1823 | | // TODO: BlobDB to support output_to_proximal_level compaction, which needs |
1824 | | // 2 builders, so may need to move to `CompactionOutputs` |
1825 | 2.77k | if (mutable_cf_options.enable_blob_files && |
1826 | 0 | sub_compact->compaction->output_level() >= |
1827 | 0 | mutable_cf_options.blob_file_starting_level) { |
1828 | 0 | blob_file_builder = std::make_unique<BlobFileBuilder>( |
1829 | 0 | [this]() { return NewBlobFileNumber(); }, fs_.get(), |
1830 | 0 | &sub_compact->compaction->immutable_options(), &mutable_cf_options, |
1831 | 0 | &file_options_, &write_options, db_id_, db_session_id_, job_id_, |
1832 | 0 | cfd->GetID(), cfd->GetName(), write_hint_, io_tracer_, blob_callback_, |
1833 | 0 | BlobFileCreationReason::kCompaction, GetBlobOutputPath(), |
1834 | 0 | sub_compact->Current().GetOutputFilePathsPtr(), |
1835 | 0 | sub_compact->Current().GetBlobFileAdditionsPtr()); |
1836 | 2.77k | } else { |
1837 | 2.77k | blob_file_builder = nullptr; |
1838 | 2.77k | } |
1839 | 2.77k | } |
1840 | | |
1841 | | std::unique_ptr<CompactionIterator> CompactionJob::CreateCompactionIterator( |
1842 | | SubcompactionState* sub_compact, ColumnFamilyData* cfd, |
1843 | | InternalIterator* input, const CompactionFilter* compaction_filter, |
1844 | | MergeHelper& merge, std::unique_ptr<BlobFileBuilder>& blob_file_builder, |
1845 | 2.77k | const WriteOptions& write_options) { |
1846 | 2.77k | CreateBlobFileBuilder(sub_compact, cfd, blob_file_builder, write_options); |
1847 | | |
1848 | 2.77k | const std::string* const full_history_ts_low = |
1849 | 2.77k | full_history_ts_low_.empty() ? nullptr : &full_history_ts_low_; |
1850 | 2.77k | assert(job_context_); |
1851 | | |
1852 | 2.77k | return std::make_unique<CompactionIterator>( |
1853 | 2.77k | input, cfd->user_comparator(), &merge, versions_->LastSequence(), |
1854 | 2.77k | &(job_context_->snapshot_seqs), earliest_snapshot_, |
1855 | 2.77k | job_context_->earliest_write_conflict_snapshot, |
1856 | 2.77k | job_context_->GetJobSnapshotSequence(), job_context_->snapshot_checker, |
1857 | 2.77k | env_, ShouldReportDetailedTime(env_, stats_), sub_compact->RangeDelAgg(), |
1858 | 2.77k | blob_file_builder.get(), db_options_.allow_data_in_errors, |
1859 | 2.77k | db_options_.enforce_single_del_contracts, manual_compaction_canceled_, |
1860 | 2.77k | sub_compact->compaction->DoesInputReferenceBlobFiles() || |
1861 | 2.77k | sub_compact->compaction |
1862 | 2.77k | ->RepairsMissingBlobReferences() /* must_count_input_entries */, |
1863 | 2.77k | sub_compact->compaction, compaction_filter, shutting_down_, |
1864 | 2.77k | db_options_.info_log, full_history_ts_low, preserve_seqno_after_, |
1865 | 2.77k | nullptr /* input_version */, Env::IOActivity::kCompaction, |
1866 | 2.77k | GetBlobGarbageCollectionCutoffFileNumber()); |
1867 | 2.77k | } |
1868 | | |
1869 | | std::pair<CompactionFileOpenFunc, CompactionFileCloseFunc> |
1870 | | CompactionJob::CreateFileHandlers(SubcompactionState* sub_compact, |
1871 | | SubcompactionKeyBoundaries& boundaries, |
1872 | | BlobFileBuilder* blob_file_builder, |
1873 | 2.77k | const BlobCountingIterator* blob_counter) { |
1874 | 2.77k | const CompactionFileOpenFunc open_file_func = |
1875 | 2.77k | [this, sub_compact](CompactionOutputs& outputs) { |
1876 | 1.69k | return this->OpenCompactionOutputFile(sub_compact, outputs); |
1877 | 1.69k | }; |
1878 | | |
1879 | 2.77k | const Slice* start_user_key = |
1880 | 2.77k | sub_compact->start.has_value() ? &boundaries.start_user_key : nullptr; |
1881 | 2.77k | const Slice* end_user_key = |
1882 | 2.77k | sub_compact->end.has_value() ? &boundaries.end_user_key : nullptr; |
1883 | | |
1884 | 2.77k | const CompactionFileCloseFunc close_file_func = |
1885 | 2.77k | [this, sub_compact, start_user_key, end_user_key, blob_file_builder, |
1886 | 2.77k | blob_counter](const Status& status, |
1887 | 2.77k | const ParsedInternalKey& prev_iter_output_internal_key, |
1888 | 2.77k | const Slice& next_table_min_key, |
1889 | 2.77k | const CompactionIterator* c_iter, bool post_prepare_retry, |
1890 | 2.77k | CompactionOutputs& outputs) { |
1891 | 1.69k | return this->FinishCompactionOutputFile( |
1892 | 1.69k | status, prev_iter_output_internal_key, next_table_min_key, |
1893 | 1.69k | start_user_key, end_user_key, c_iter, post_prepare_retry, |
1894 | 1.69k | sub_compact, blob_file_builder, blob_counter, outputs); |
1895 | 1.69k | }; |
1896 | | |
1897 | 2.77k | return {open_file_func, close_file_func}; |
1898 | 2.77k | } |
1899 | | |
1900 | | Status CompactionJob::ProcessKeyValue( |
1901 | | SubcompactionState* sub_compact, ColumnFamilyData* cfd, |
1902 | | CompactionIterator* c_iter, const CompactionFileOpenFunc& open_file_func, |
1903 | | const CompactionFileCloseFunc& close_file_func, |
1904 | 2.77k | bool defer_blob_output_preparation, uint64_t& prev_cpu_micros) { |
1905 | | // Cron interval for periodic operations: stats update, abort check, |
1906 | | // and sync points. Uses 1024 (power of 2) for efficient bitwise check. |
1907 | 2.77k | const uint64_t kCronEveryMask = (1 << 10) - 1; |
1908 | 2.77k | [[maybe_unused]] const std::optional<const Slice> end = sub_compact->end; |
1909 | | |
1910 | | // Check for abort signal before starting key processing. The DB-wide |
1911 | | // (AbortAllCompactions) and per-CF (AbortCompactions) counters compose here; |
1912 | | // either aborts this job. See DB::AbortCompactions() in db.h for how the |
1913 | | // abort knobs relate to each other and to the other compaction controls. |
1914 | 2.77k | if (compaction_aborted_.load(std::memory_order_acquire) > 0 || |
1915 | 2.77k | cfd->compaction_aborted() > 0) { |
1916 | 0 | return Status::Incomplete(Status::SubCode::kCompactionAborted); |
1917 | 0 | } |
1918 | | |
1919 | 2.77k | Status status; |
1920 | 2.77k | IterKey prev_iter_output_key; |
1921 | 2.77k | ParsedInternalKey prev_iter_output_internal_key; |
1922 | | |
1923 | 2.77k | TEST_SYNC_POINT_CALLBACK( |
1924 | 2.77k | "CompactionJob::ProcessKeyValueCompaction()::Processing", |
1925 | 2.77k | static_cast<void*>(const_cast<Compaction*>(sub_compact->compaction))); |
1926 | | |
1927 | 4.66k | while (status.ok() && !cfd->IsDropped() && c_iter->Valid() && |
1928 | 1.89k | c_iter->status().ok()) { |
1929 | 1.89k | assert(!end.has_value() || |
1930 | 1.89k | cfd->user_comparator()->Compare(c_iter->user_key(), *end) < 0); |
1931 | | |
1932 | 1.89k | const uint64_t num_records = c_iter->iter_stats().num_input_records; |
1933 | | |
1934 | | // Periodic cron operations: stats update, abort check. |
1935 | 1.89k | if ((num_records & kCronEveryMask) == kCronEveryMask) { |
1936 | | // Check for abort signal periodically |
1937 | 0 | if (compaction_aborted_.load(std::memory_order_acquire) > 0 || |
1938 | 0 | cfd->compaction_aborted() > 0) { |
1939 | 0 | status = Status::Incomplete(Status::SubCode::kCompactionAborted); |
1940 | 0 | break; |
1941 | 0 | } |
1942 | | |
1943 | 0 | UpdateSubcompactionJobStatsIncrementally( |
1944 | 0 | c_iter, &sub_compact->compaction_job_stats, |
1945 | 0 | db_options_.clock->CPUMicros(), prev_cpu_micros); |
1946 | 0 | } |
1947 | | |
1948 | 1.89k | const auto& ikey = c_iter->ikey(); |
1949 | 1.89k | bool use_proximal_output = ikey.sequence > proximal_after_seqno_; |
1950 | | |
1951 | | #ifndef NDEBUG |
1952 | | if (sub_compact->compaction->SupportsPerKeyPlacement()) { |
1953 | | PerKeyPlacementContext context(sub_compact->compaction->output_level(), |
1954 | | ikey.user_key, c_iter->value(), |
1955 | | ikey.sequence, use_proximal_output); |
1956 | | TEST_SYNC_POINT_CALLBACK("CompactionIterator::PrepareOutput.context", |
1957 | | &context); |
1958 | | if (use_proximal_output) { |
1959 | | // Verify that entries sent to the proximal level are within the |
1960 | | // allowed range (because the input key range of the last level could |
1961 | | // be larger than the allowed output key range of the proximal |
1962 | | // level). This check uses user keys (ignores sequence numbers) because |
1963 | | // compaction boundaries are a "clean cut" between user keys (see |
1964 | | // CompactionPicker::ExpandInputsToCleanCut()), which is especially |
1965 | | // important when preferred sequence numbers has been swapped in for |
1966 | | // kTypeValuePreferredSeqno / TimedPut. |
1967 | | sub_compact->compaction->TEST_AssertWithinProximalLevelOutputRange( |
1968 | | c_iter->user_key()); |
1969 | | } |
1970 | | } else { |
1971 | | assert(proximal_after_seqno_ == kMaxSequenceNumber); |
1972 | | assert(!use_proximal_output); |
1973 | | } |
1974 | | #endif // NDEBUG |
1975 | | |
1976 | | // Add current compaction_iterator key to target compaction output, if the |
1977 | | // output file needs to be close or open, it will call the `open_file_func` |
1978 | | // and `close_file_func`. |
1979 | | // TODO: it would be better to have the compaction file open/close moved |
1980 | | // into `CompactionOutputs` which has the output file information. |
1981 | 1.89k | status = sub_compact->AddToOutput(*c_iter, use_proximal_output, |
1982 | 1.89k | open_file_func, close_file_func, |
1983 | 1.89k | prev_iter_output_internal_key); |
1984 | 1.89k | if (!status.ok()) { |
1985 | 0 | break; |
1986 | 0 | } |
1987 | 1.89k | TEST_SYNC_POINT_CALLBACK("CompactionJob::Run():PausingManualCompaction:2", |
1988 | 1.89k | static_cast<void*>(const_cast<std::atomic<bool>*>( |
1989 | 1.89k | &manual_compaction_canceled_))); |
1990 | | |
1991 | 1.89k | prev_iter_output_key.SetInternalKey(c_iter->key(), |
1992 | 1.89k | &prev_iter_output_internal_key); |
1993 | 1.89k | prev_iter_output_internal_key.sequence = ikey.sequence; |
1994 | 1.89k | prev_iter_output_internal_key.type = ikey.type; |
1995 | 1.89k | if (defer_blob_output_preparation) { |
1996 | 0 | c_iter->NextWithoutPreparingOutput(); |
1997 | 1.89k | } else { |
1998 | 1.89k | c_iter->Next(); |
1999 | 1.89k | } |
2000 | | |
2001 | | #ifndef NDEBUG |
2002 | | bool stop = false; |
2003 | | TEST_SYNC_POINT_CALLBACK("CompactionJob::ProcessKeyValueCompaction()::stop", |
2004 | | static_cast<void*>(&stop)); |
2005 | | if (stop) { |
2006 | | break; |
2007 | | } |
2008 | | #endif // NDEBUG |
2009 | 1.89k | } |
2010 | | |
2011 | 2.77k | return status; |
2012 | 2.77k | } |
2013 | | |
2014 | | void CompactionJob::UpdateSubcompactionJobStatsIncrementally( |
2015 | | CompactionIterator* c_iter, CompactionJobStats* compaction_job_stats, |
2016 | 2.77k | uint64_t cur_cpu_micros, uint64_t& prev_cpu_micros) { |
2017 | 2.77k | RecordDroppedKeys(c_iter->iter_stats(), compaction_job_stats); |
2018 | 2.77k | c_iter->ResetRecordCounts(); |
2019 | 2.77k | RecordCompactionIOStats(); |
2020 | | |
2021 | 2.77k | assert(cur_cpu_micros >= prev_cpu_micros); |
2022 | 2.77k | RecordTick(stats_, COMPACTION_CPU_TOTAL_TIME, |
2023 | 2.77k | cur_cpu_micros - prev_cpu_micros); |
2024 | 2.77k | prev_cpu_micros = cur_cpu_micros; |
2025 | 2.77k | } |
2026 | | |
2027 | | void CompactionJob::FinalizeSubcompactionJobStats( |
2028 | | SubcompactionState* sub_compact, CompactionIterator* c_iter, |
2029 | | uint64_t start_cpu_micros, uint64_t prev_cpu_micros, |
2030 | 2.77k | const CompactionIOStatsSnapshot& io_stats) { |
2031 | 2.77k | const CompactionIterationStats& c_iter_stats = c_iter->iter_stats(); |
2032 | | |
2033 | 2.77k | assert(!sub_compact->compaction->DoesInputReferenceBlobFiles() || |
2034 | 2.77k | c_iter->HasNumInputEntryScanned()); |
2035 | 2.77k | sub_compact->compaction_job_stats.has_accurate_num_input_records &= |
2036 | 2.77k | c_iter->HasNumInputEntryScanned(); |
2037 | 2.77k | sub_compact->compaction_job_stats.num_input_records += |
2038 | 2.77k | c_iter->NumInputEntryScanned(); |
2039 | 2.77k | sub_compact->compaction_job_stats.num_blobs_read = |
2040 | 2.77k | c_iter_stats.num_blobs_read; |
2041 | 2.77k | sub_compact->compaction_job_stats.total_blob_bytes_read = |
2042 | 2.77k | c_iter_stats.total_blob_bytes_read; |
2043 | 2.77k | sub_compact->compaction_job_stats.num_input_deletion_records = |
2044 | 2.77k | c_iter_stats.num_input_deletion_records; |
2045 | 2.77k | sub_compact->compaction_job_stats.num_corrupt_keys = |
2046 | 2.77k | c_iter_stats.num_input_corrupt_records; |
2047 | 2.77k | sub_compact->compaction_job_stats.num_single_del_fallthru = |
2048 | 2.77k | c_iter_stats.num_single_del_fallthru; |
2049 | 2.77k | sub_compact->compaction_job_stats.num_single_del_mismatch = |
2050 | 2.77k | c_iter_stats.num_single_del_mismatch; |
2051 | 2.77k | sub_compact->compaction_job_stats.total_input_raw_key_bytes += |
2052 | 2.77k | c_iter_stats.total_input_raw_key_bytes; |
2053 | 2.77k | sub_compact->compaction_job_stats.total_input_raw_value_bytes += |
2054 | 2.77k | c_iter_stats.total_input_raw_value_bytes; |
2055 | | |
2056 | 2.77k | RecordTick(stats_, FILTER_OPERATION_TOTAL_TIME, |
2057 | 2.77k | c_iter_stats.total_filter_time); |
2058 | | |
2059 | 2.77k | if (c_iter_stats.num_blobs_relocated > 0) { |
2060 | 0 | RecordTick(stats_, BLOB_DB_GC_NUM_KEYS_RELOCATED, |
2061 | 0 | c_iter_stats.num_blobs_relocated); |
2062 | 0 | } |
2063 | 2.77k | if (c_iter_stats.total_blob_bytes_relocated > 0) { |
2064 | 0 | RecordTick(stats_, BLOB_DB_GC_BYTES_RELOCATED, |
2065 | 0 | c_iter_stats.total_blob_bytes_relocated); |
2066 | 0 | } |
2067 | | |
2068 | 2.77k | uint64_t cur_cpu_micros = db_options_.clock->CPUMicros(); |
2069 | | |
2070 | | // Record final compaction statistics including dropped keys, I/O stats, |
2071 | | // and CPU time delta from the last periodic measurement |
2072 | 2.77k | UpdateSubcompactionJobStatsIncrementally(c_iter, |
2073 | 2.77k | &sub_compact->compaction_job_stats, |
2074 | 2.77k | cur_cpu_micros, prev_cpu_micros); |
2075 | | |
2076 | | // Finalize timing and I/O statistics |
2077 | 2.77k | sub_compact->compaction_job_stats.cpu_micros = |
2078 | 2.77k | cur_cpu_micros - start_cpu_micros + sub_compact->GetWorkerCPUMicros(); |
2079 | | |
2080 | 2.77k | if (measure_io_stats_) { |
2081 | 0 | sub_compact->compaction_job_stats.file_write_nanos += |
2082 | 0 | IOSTATS(write_nanos) - io_stats.prev_write_nanos; |
2083 | 0 | sub_compact->compaction_job_stats.file_fsync_nanos += |
2084 | 0 | IOSTATS(fsync_nanos) - io_stats.prev_fsync_nanos; |
2085 | 0 | sub_compact->compaction_job_stats.file_range_sync_nanos += |
2086 | 0 | IOSTATS(range_sync_nanos) - io_stats.prev_range_sync_nanos; |
2087 | 0 | sub_compact->compaction_job_stats.file_prepare_write_nanos += |
2088 | 0 | IOSTATS(prepare_write_nanos) - io_stats.prev_prepare_write_nanos; |
2089 | 0 | sub_compact->compaction_job_stats.cpu_micros -= |
2090 | 0 | (IOSTATS(cpu_write_nanos) - io_stats.prev_cpu_write_nanos + |
2091 | 0 | IOSTATS(cpu_read_nanos) - io_stats.prev_cpu_read_nanos) / |
2092 | 0 | 1000; |
2093 | 0 | if (io_stats.prev_perf_level != |
2094 | 0 | PerfLevel::kEnableTimeAndCPUTimeExceptForMutex) { |
2095 | 0 | SetPerfLevel(io_stats.prev_perf_level); |
2096 | 0 | } |
2097 | 0 | } |
2098 | 2.77k | } |
2099 | | |
2100 | | Status CompactionJob::FinalizeProcessKeyValueStatus( |
2101 | | ColumnFamilyData* cfd, InternalIterator* input_iter, |
2102 | 2.77k | CompactionIterator* c_iter, Status status) { |
2103 | 2.77k | if (status.ok() && cfd->IsDropped()) { |
2104 | 0 | status = |
2105 | 0 | Status::ColumnFamilyDropped("Column family dropped during compaction"); |
2106 | 0 | } |
2107 | 2.77k | if (status.ok() && shutting_down_->load(std::memory_order_relaxed)) { |
2108 | 700 | status = Status::ShutdownInProgress("Database shutdown"); |
2109 | 700 | } |
2110 | 2.77k | if (status.ok() && |
2111 | 2.07k | (manual_compaction_canceled_.load(std::memory_order_relaxed))) { |
2112 | 0 | status = Status::Incomplete(Status::SubCode::kManualCompactionPaused); |
2113 | 0 | } |
2114 | 2.77k | if (status.ok()) { |
2115 | 2.07k | status = input_iter->status(); |
2116 | 2.07k | } |
2117 | 2.77k | if (status.ok()) { |
2118 | 2.07k | status = c_iter->status(); |
2119 | 2.07k | } |
2120 | | |
2121 | 2.77k | return status; |
2122 | 2.77k | } |
2123 | | |
2124 | | Status CompactionJob::CleanupCompactionFiles( |
2125 | | SubcompactionState* sub_compact, const Status& status, |
2126 | | const CompactionFileOpenFunc& open_file_func, |
2127 | 2.77k | const CompactionFileCloseFunc& close_file_func) { |
2128 | | // Call FinishCompactionOutputFile() even if status is not ok: it needs to |
2129 | | // close the output files. Open file function is also passed, in case there's |
2130 | | // only range-dels, no file was opened, to save the range-dels, it need to |
2131 | | // create a new output file. |
2132 | 2.77k | return sub_compact->CloseCompactionFiles(status, open_file_func, |
2133 | 2.77k | close_file_func); |
2134 | 2.77k | } |
2135 | | |
2136 | | Status CompactionJob::FinalizeBlobFiles(SubcompactionState* sub_compact, |
2137 | | BlobFileBuilder* blob_file_builder, |
2138 | 2.77k | Status status) { |
2139 | 2.77k | if (blob_file_builder) { |
2140 | 0 | if (status.ok()) { |
2141 | 0 | status = blob_file_builder->Finish(); |
2142 | 0 | } else { |
2143 | 0 | blob_file_builder->Abandon(status); |
2144 | 0 | } |
2145 | 0 | sub_compact->Current().UpdateBlobStats(); |
2146 | 0 | } |
2147 | | |
2148 | 2.77k | return status; |
2149 | 2.77k | } |
2150 | | |
2151 | 2.77k | void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) { |
2152 | 2.77k | TEST_SYNC_POINT("CompactionJob::ProcessKeyValueCompaction:Start"); |
2153 | 2.77k | assert(sub_compact); |
2154 | 2.77k | assert(sub_compact->compaction); |
2155 | | |
2156 | 2.77k | if (!ShouldUseLocalCompaction(sub_compact)) { |
2157 | 0 | return; |
2158 | 0 | } |
2159 | 2.77k | if (local_compaction_executor_) { |
2160 | 0 | Status status = local_compaction_executor_( |
2161 | 0 | [this, sub_compact] { |
2162 | 0 | if (thread_pri_ != Env::Priority::USER) { |
2163 | 0 | InitializeThreadStatus(); |
2164 | 0 | } |
2165 | 0 | Defer reset_thread_status([this] { |
2166 | 0 | if (thread_pri_ != Env::Priority::USER) { |
2167 | 0 | ThreadStatusUtil::ResetThreadStatus(); |
2168 | 0 | } |
2169 | 0 | }); |
2170 | 0 | try { |
2171 | 0 | ProcessLocalKeyValueCompaction(sub_compact); |
2172 | 0 | return; |
2173 | 0 | } catch (const std::exception& e) { |
2174 | 0 | sub_compact->status = |
2175 | 0 | Status::Aborted("Local subcompaction exception", e.what()); |
2176 | 0 | } catch (...) { |
2177 | 0 | sub_compact->status = |
2178 | 0 | Status::Aborted("Unknown local subcompaction exception"); |
2179 | 0 | } |
2180 | 0 | sub_compact->CleanupOutputs(); |
2181 | 0 | NotifyOnSubcompactionCompleted(sub_compact); |
2182 | 0 | }, |
2183 | 0 | thread_pri_); |
2184 | 0 | if (!status.ok()) { |
2185 | 0 | sub_compact->status = status; |
2186 | 0 | } |
2187 | 2.77k | } else { |
2188 | 2.77k | ProcessLocalKeyValueCompaction(sub_compact); |
2189 | 2.77k | } |
2190 | 2.77k | } |
2191 | | |
2192 | | void CompactionJob::ProcessLocalKeyValueCompaction( |
2193 | 2.77k | SubcompactionState* sub_compact) { |
2194 | 2.77k | AutoThreadOperationStageUpdater stage_updater( |
2195 | 2.77k | ThreadStatus::STAGE_COMPACTION_PROCESS_KV); |
2196 | | |
2197 | 2.77k | const uint64_t start_cpu_micros = db_options_.clock->CPUMicros(); |
2198 | 2.77k | uint64_t prev_cpu_micros = start_cpu_micros; |
2199 | 2.77k | const CompactionIOStatsSnapshot io_stats = InitializeIOStats(); |
2200 | 2.77k | ColumnFamilyData* cfd = sub_compact->compaction->column_family_data(); |
2201 | 2.77k | const CompactionFilter* compaction_filter; |
2202 | 2.77k | std::unique_ptr<CompactionFilter> compaction_filter_from_factory = nullptr; |
2203 | 2.77k | CompactionFilter::MergeOrder merge_order; |
2204 | 2.77k | Status filter_status = SetupAndValidateCompactionFilter( |
2205 | 2.77k | sub_compact, cfd->ioptions().compaction_filter, compaction_filter, |
2206 | 2.77k | compaction_filter_from_factory, merge_order); |
2207 | 2.77k | if (!filter_status.ok()) { |
2208 | 0 | sub_compact->status = filter_status; |
2209 | 0 | return; |
2210 | 0 | } |
2211 | | |
2212 | 2.77k | NotifyOnSubcompactionBegin(sub_compact); |
2213 | | |
2214 | 2.77k | SubcompactionKeyBoundaries boundaries(sub_compact->start, sub_compact->end); |
2215 | 2.77k | SubcompactionInternalIterators iterators; |
2216 | 2.77k | ReadOptions read_options; |
2217 | 2.77k | const WriteOptions write_options(Env::IOPriority::IO_LOW, |
2218 | 2.77k | Env::IOActivity::kCompaction); |
2219 | | |
2220 | 2.77k | InternalIterator* input_iter = CreateInputIterator( |
2221 | 2.77k | sub_compact, cfd, iterators, boundaries, read_options); |
2222 | | |
2223 | 2.77k | assert(input_iter); |
2224 | | |
2225 | 2.77k | Status status = |
2226 | 2.77k | MaybeResumeSubcompactionProgressOnInputIterator(sub_compact, input_iter); |
2227 | | |
2228 | 2.77k | if (status.IsNotFound()) { |
2229 | 2.77k | input_iter->SeekToFirst(); |
2230 | 2.77k | } else if (!status.ok()) { |
2231 | 0 | sub_compact->status = status; |
2232 | 0 | NotifyOnSubcompactionCompleted(sub_compact); |
2233 | 0 | return; |
2234 | 0 | } |
2235 | | |
2236 | 2.77k | MergeHelper merge( |
2237 | 2.77k | env_, cfd->user_comparator(), cfd->ioptions().merge_operator.get(), |
2238 | 2.77k | compaction_filter, merge_order, db_options_.info_log.get(), |
2239 | 2.77k | false /* internal key corruption is expected */, |
2240 | 2.77k | job_context_->GetLatestSnapshotSequence(), job_context_->snapshot_checker, |
2241 | 2.77k | compact_->compaction->level(), db_options_.stats); |
2242 | 2.77k | std::unique_ptr<BlobFileBuilder> blob_file_builder; |
2243 | | |
2244 | 2.77k | auto c_iter = |
2245 | 2.77k | CreateCompactionIterator(sub_compact, cfd, input_iter, compaction_filter, |
2246 | 2.77k | merge, blob_file_builder, write_options); |
2247 | 2.77k | assert(c_iter); |
2248 | 2.77k | c_iter->SetMissingBlobRepairIterator(iterators.missing_blob_repair.get()); |
2249 | 2.77k | const bool defer_blob_output_preparation = |
2250 | 2.77k | compaction_progress_writer_ != nullptr && |
2251 | 0 | !sub_compact->compaction->SupportsPerKeyPlacement() && |
2252 | 0 | (blob_file_builder != nullptr || |
2253 | 0 | sub_compact->Current().GetBlobGarbageMeter() != nullptr); |
2254 | 2.77k | if (defer_blob_output_preparation) { |
2255 | 0 | c_iter->SeekToFirstWithoutPreparingOutput(); |
2256 | 2.77k | } else { |
2257 | 2.77k | c_iter->SeekToFirst(); |
2258 | 2.77k | } |
2259 | | |
2260 | 2.77k | TEST_SYNC_POINT("CompactionJob::Run():Inprogress"); |
2261 | 2.77k | TEST_SYNC_POINT_CALLBACK("CompactionJob::Run():PausingManualCompaction:1", |
2262 | 2.77k | static_cast<void*>(const_cast<std::atomic<bool>*>( |
2263 | 2.77k | &manual_compaction_canceled_))); |
2264 | | |
2265 | 2.77k | auto [open_file_func, close_file_func] = |
2266 | 2.77k | CreateFileHandlers(sub_compact, boundaries, blob_file_builder.get(), |
2267 | 2.77k | iterators.blob_counter.get()); |
2268 | | |
2269 | 2.77k | status = ProcessKeyValue(sub_compact, cfd, c_iter.get(), open_file_func, |
2270 | 2.77k | close_file_func, defer_blob_output_preparation, |
2271 | 2.77k | prev_cpu_micros); |
2272 | | |
2273 | 2.77k | status = FinalizeProcessKeyValueStatus(cfd, input_iter, c_iter.get(), status); |
2274 | | |
2275 | 2.77k | FinalizeSubcompaction(sub_compact, status, open_file_func, close_file_func, |
2276 | 2.77k | blob_file_builder.get(), c_iter.get(), input_iter, |
2277 | 2.77k | start_cpu_micros, prev_cpu_micros, io_stats); |
2278 | | |
2279 | 2.77k | NotifyOnSubcompactionCompleted(sub_compact); |
2280 | 2.77k | } |
2281 | | |
2282 | | void CompactionJob::FinalizeSubcompaction( |
2283 | | SubcompactionState* sub_compact, Status status, |
2284 | | const CompactionFileOpenFunc& open_file_func, |
2285 | | const CompactionFileCloseFunc& close_file_func, |
2286 | | BlobFileBuilder* blob_file_builder, CompactionIterator* c_iter, |
2287 | | [[maybe_unused]] InternalIterator* input_iter, uint64_t start_cpu_micros, |
2288 | 2.77k | uint64_t prev_cpu_micros, const CompactionIOStatsSnapshot& io_stats) { |
2289 | 2.77k | status = CleanupCompactionFiles(sub_compact, status, open_file_func, |
2290 | 2.77k | close_file_func); |
2291 | 2.77k | status = FinalizeBlobFiles(sub_compact, blob_file_builder, status); |
2292 | | |
2293 | 2.77k | FinalizeSubcompactionJobStats(sub_compact, c_iter, start_cpu_micros, |
2294 | 2.77k | prev_cpu_micros, io_stats); |
2295 | | |
2296 | | #ifdef ROCKSDB_ASSERT_STATUS_CHECKED |
2297 | | if (!status.ok()) { |
2298 | | if (c_iter) { |
2299 | | c_iter->status().PermitUncheckedError(); |
2300 | | } |
2301 | | if (input_iter) { |
2302 | | input_iter->status().PermitUncheckedError(); |
2303 | | } |
2304 | | } |
2305 | | #endif // ROCKSDB_ASSERT_STATUS_CHECKED |
2306 | | |
2307 | 2.77k | sub_compact->status = status; |
2308 | 2.77k | } |
2309 | | |
2310 | 0 | uint64_t CompactionJob::GetCompactionId(SubcompactionState* sub_compact) const { |
2311 | 0 | return (uint64_t)job_id_ << 32 | sub_compact->sub_job_id; |
2312 | 0 | } |
2313 | | |
2314 | | void CompactionJob::RecordDroppedKeys( |
2315 | | const CompactionIterationStats& c_iter_stats, |
2316 | 3.98k | CompactionJobStats* compaction_job_stats) { |
2317 | 3.98k | if (c_iter_stats.num_record_drop_user > 0) { |
2318 | 0 | RecordTick(stats_, COMPACTION_KEY_DROP_USER, |
2319 | 0 | c_iter_stats.num_record_drop_user); |
2320 | 0 | } |
2321 | 3.98k | if (c_iter_stats.num_record_drop_hidden > 0) { |
2322 | 1.59k | RecordTick(stats_, COMPACTION_KEY_DROP_NEWER_ENTRY, |
2323 | 1.59k | c_iter_stats.num_record_drop_hidden); |
2324 | 1.59k | if (compaction_job_stats) { |
2325 | 1.59k | compaction_job_stats->num_records_replaced += |
2326 | 1.59k | c_iter_stats.num_record_drop_hidden; |
2327 | 1.59k | } |
2328 | 1.59k | } |
2329 | 3.98k | if (c_iter_stats.num_record_drop_obsolete > 0) { |
2330 | 1.29k | RecordTick(stats_, COMPACTION_KEY_DROP_OBSOLETE, |
2331 | 1.29k | c_iter_stats.num_record_drop_obsolete); |
2332 | 1.29k | if (compaction_job_stats) { |
2333 | 1.29k | compaction_job_stats->num_expired_deletion_records += |
2334 | 1.29k | c_iter_stats.num_record_drop_obsolete; |
2335 | 1.29k | } |
2336 | 1.29k | } |
2337 | 3.98k | if (c_iter_stats.num_record_drop_range_del > 0) { |
2338 | 0 | RecordTick(stats_, COMPACTION_KEY_DROP_RANGE_DEL, |
2339 | 0 | c_iter_stats.num_record_drop_range_del); |
2340 | 0 | } |
2341 | 3.98k | if (c_iter_stats.num_range_del_drop_obsolete > 0) { |
2342 | 0 | RecordTick(stats_, COMPACTION_RANGE_DEL_DROP_OBSOLETE, |
2343 | 0 | c_iter_stats.num_range_del_drop_obsolete); |
2344 | 0 | } |
2345 | 3.98k | if (c_iter_stats.num_optimized_del_drop_obsolete > 0) { |
2346 | 0 | RecordTick(stats_, COMPACTION_OPTIMIZED_DEL_DROP_OBSOLETE, |
2347 | 0 | c_iter_stats.num_optimized_del_drop_obsolete); |
2348 | 0 | } |
2349 | 3.98k | } |
2350 | | |
2351 | | Status CompactionJob::FinishCompactionOutputFile( |
2352 | | const Status& input_status, |
2353 | | const ParsedInternalKey& prev_iter_output_internal_key, |
2354 | | const Slice& next_table_min_key, const Slice* comp_start_user_key, |
2355 | | const Slice* comp_end_user_key, const CompactionIterator* c_iter, |
2356 | | bool post_prepare_retry, SubcompactionState* sub_compact, |
2357 | | BlobFileBuilder* blob_file_builder, |
2358 | 1.69k | const BlobCountingIterator* blob_counter, CompactionOutputs& outputs) { |
2359 | 1.69k | AutoThreadOperationStageUpdater stage_updater( |
2360 | 1.69k | ThreadStatus::STAGE_COMPACTION_SYNC_FILE); |
2361 | 1.69k | assert(sub_compact != nullptr); |
2362 | 1.69k | assert(outputs.HasBuilder()); |
2363 | | |
2364 | 1.69k | FileMetaData* meta = outputs.GetMetaData(); |
2365 | 1.69k | uint64_t output_number = meta->fd.GetNumber(); |
2366 | 1.69k | assert(output_number != 0); |
2367 | | |
2368 | 1.69k | ColumnFamilyData* cfd = sub_compact->compaction->column_family_data(); |
2369 | 1.69k | std::string file_checksum = kUnknownFileChecksum; |
2370 | 1.69k | std::string file_checksum_func_name = kUnknownFileChecksumFuncName; |
2371 | | |
2372 | | // Check for iterator errors |
2373 | 1.69k | Status s = input_status; |
2374 | | |
2375 | | // Add range tombstones |
2376 | 1.69k | if (s.ok()) { |
2377 | | // Inclusive lower bound, exclusive upper bound |
2378 | 1.21k | std::pair<SequenceNumber, SequenceNumber> keep_seqno_range{ |
2379 | 1.21k | 0, kMaxSequenceNumber}; |
2380 | 1.21k | if (sub_compact->compaction->SupportsPerKeyPlacement()) { |
2381 | | // Point entries are routed to proximal output only when their seqno is |
2382 | | // strictly greater than `proximal_after_seqno_`. Range tombstones use a |
2383 | | // [lower, upper) filter, so split them at the next seqno to preserve the |
2384 | | // same boundary. |
2385 | 0 | SequenceNumber range_del_split_seqno = proximal_after_seqno_; |
2386 | 0 | if (range_del_split_seqno < kMaxSequenceNumber) { |
2387 | 0 | range_del_split_seqno++; |
2388 | 0 | } |
2389 | 0 | if (outputs.IsProximalLevel()) { |
2390 | 0 | keep_seqno_range.first = range_del_split_seqno; |
2391 | 0 | } else { |
2392 | 0 | keep_seqno_range.second = range_del_split_seqno; |
2393 | 0 | } |
2394 | 0 | } |
2395 | 1.21k | CompactionIterationStats range_del_out_stats; |
2396 | | // NOTE1: Use `bottommost_level_ = true` for both bottommost and |
2397 | | // output_to_proximal_level compaction here, as it's only used to decide |
2398 | | // if range dels could be dropped. (Logically, we are taking a single sorted |
2399 | | // run returned from CompactionIterator and physically splitting it between |
2400 | | // two output levels.) |
2401 | | // NOTE2: with per-key placement, range tombstones will be filtered on |
2402 | | // each output level based on sequence number (traversed twice). This is |
2403 | | // CPU-inefficient for a large number of range tombstones, but that would |
2404 | | // be an unusual work load. |
2405 | 1.21k | if (sub_compact->HasRangeDel()) { |
2406 | 0 | s = outputs.AddRangeDels(*sub_compact->RangeDelAgg(), comp_start_user_key, |
2407 | 0 | comp_end_user_key, range_del_out_stats, |
2408 | 0 | bottommost_level_, cfd->internal_comparator(), |
2409 | 0 | earliest_snapshot_, keep_seqno_range, |
2410 | 0 | next_table_min_key, full_history_ts_low_); |
2411 | 0 | } |
2412 | 1.21k | RecordDroppedKeys(range_del_out_stats, &sub_compact->compaction_job_stats); |
2413 | 1.21k | TEST_SYNC_POINT("CompactionJob::FinishCompactionOutputFile1"); |
2414 | 1.21k | } |
2415 | | |
2416 | 1.69k | const uint64_t current_entries = outputs.NumEntries(); |
2417 | | |
2418 | 1.69k | s = outputs.Finish(s, seqno_to_time_mapping_); |
2419 | 1.69k | TEST_SYNC_POINT_CALLBACK( |
2420 | 1.69k | "CompactionJob::FinishCompactionOutputFile()::AfterFinish", &s); |
2421 | | |
2422 | 1.69k | if (s.ok()) { |
2423 | | // With accurate smallest and largest key, we can get a slightly more |
2424 | | // accurate oldest ancestor time. |
2425 | | // This makes oldest ancestor time in manifest more accurate than in |
2426 | | // table properties. Not sure how to resolve it. |
2427 | 1.21k | if (meta->smallest.size() > 0 && meta->largest.size() > 0) { |
2428 | 1.21k | uint64_t refined_oldest_ancestor_time; |
2429 | 1.21k | Slice new_smallest = meta->smallest.user_key(); |
2430 | 1.21k | Slice new_largest = meta->largest.user_key(); |
2431 | 1.21k | if (!new_largest.empty() && !new_smallest.empty()) { |
2432 | 351 | refined_oldest_ancestor_time = |
2433 | 351 | sub_compact->compaction->MinInputFileOldestAncestorTime( |
2434 | 351 | &(meta->smallest), &(meta->largest)); |
2435 | 351 | if (refined_oldest_ancestor_time != |
2436 | 351 | std::numeric_limits<uint64_t>::max()) { |
2437 | 101 | meta->oldest_ancestor_time = refined_oldest_ancestor_time; |
2438 | 101 | } |
2439 | 351 | } |
2440 | 1.21k | } |
2441 | 1.21k | } |
2442 | | |
2443 | | // Finish and check for file errors |
2444 | 1.69k | IOStatus io_s = outputs.WriterSyncClose(s, db_options_.clock, stats_, |
2445 | 1.69k | db_options_.use_fsync); |
2446 | | |
2447 | 1.69k | if (s.ok() && io_s.ok()) { |
2448 | 1.21k | file_checksum = meta->file_checksum; |
2449 | 1.21k | file_checksum_func_name = meta->file_checksum_func_name; |
2450 | 1.21k | } |
2451 | | |
2452 | 1.69k | if (s.ok()) { |
2453 | 1.21k | s = io_s; |
2454 | 1.21k | } |
2455 | 1.69k | if (sub_compact->io_status.ok()) { |
2456 | 1.69k | sub_compact->io_status = io_s; |
2457 | | // Since this error is really a copy of the |
2458 | | // "normal" status, it does not also need to be checked |
2459 | 1.69k | sub_compact->io_status.PermitUncheckedError(); |
2460 | 1.69k | } |
2461 | | |
2462 | 1.69k | TableProperties tp; |
2463 | 1.69k | if (s.ok()) { |
2464 | 1.21k | tp = outputs.GetTableProperties(); |
2465 | 1.21k | } |
2466 | 1.69k | if (s.ok() && current_entries == 0 && tp.num_range_deletions == 0) { |
2467 | | // If there is nothing to output, no necessary to generate a sst file. |
2468 | | // This happens when the output level is bottom level, at the same time |
2469 | | // the sub_compact output nothing. |
2470 | 0 | std::string fname = GetTableFileName(meta->fd.GetNumber()); |
2471 | | |
2472 | | // TODO(AR) it is not clear if there are any larger implications if |
2473 | | // DeleteFile fails here |
2474 | 0 | Status ds = env_->DeleteFile(fname); |
2475 | 0 | if (!ds.ok()) { |
2476 | 0 | ROCKS_LOG_WARN( |
2477 | 0 | db_options_.info_log, |
2478 | 0 | "[%s] [JOB %d] Unable to remove SST file for table #%" PRIu64 |
2479 | 0 | " at bottom level%s", |
2480 | 0 | cfd->GetName().c_str(), job_id_, output_number, |
2481 | 0 | meta->marked_for_compaction ? " (need compaction)" : ""); |
2482 | 0 | } |
2483 | | |
2484 | | // Also need to remove the file from outputs, or it will be added to the |
2485 | | // VersionEdit. |
2486 | 0 | outputs.RemoveLastOutput(); |
2487 | 0 | meta = nullptr; |
2488 | 0 | } |
2489 | | |
2490 | 1.69k | if (s.ok() && (current_entries > 0 || tp.num_range_deletions > 0)) { |
2491 | | // Output to event logger and fire events. |
2492 | 1.21k | outputs.UpdateTableProperties(); |
2493 | 1.21k | ROCKS_LOG_INFO(db_options_.info_log, |
2494 | 1.21k | "[%s] [JOB %d] Generated table #%" PRIu64 ": %" PRIu64 |
2495 | 1.21k | " keys, %" PRIu64 " bytes%s, temperature: %s", |
2496 | 1.21k | cfd->GetName().c_str(), job_id_, output_number, |
2497 | 1.21k | current_entries, meta->fd.file_size, |
2498 | 1.21k | meta->marked_for_compaction ? " (need compaction)" : "", |
2499 | 1.21k | temperature_to_string[meta->temperature].c_str()); |
2500 | 1.21k | } |
2501 | 1.69k | std::string fname; |
2502 | 1.69k | FileDescriptor output_fd; |
2503 | 1.69k | uint64_t oldest_blob_file_number = kInvalidBlobFileNumber; |
2504 | 1.69k | Status status_for_listener = s; |
2505 | 1.69k | if (meta != nullptr) { |
2506 | 1.69k | fname = GetTableFileName(meta->fd.GetNumber()); |
2507 | 1.69k | output_fd = meta->fd; |
2508 | 1.69k | oldest_blob_file_number = meta->oldest_blob_file_number; |
2509 | 1.69k | } else { |
2510 | 0 | fname = "(nil)"; |
2511 | 0 | if (s.ok()) { |
2512 | 0 | status_for_listener = Status::Aborted("Empty SST file not kept"); |
2513 | 0 | } |
2514 | 0 | } |
2515 | 1.69k | EventHelpers::LogAndNotifyTableFileCreationFinished( |
2516 | 1.69k | event_logger_, cfd->ioptions().listeners, dbname_, cfd->GetName(), fname, |
2517 | 1.69k | job_id_, output_fd, oldest_blob_file_number, tp, |
2518 | 1.69k | TableFileCreationReason::kCompaction, status_for_listener, file_checksum, |
2519 | 1.69k | file_checksum_func_name); |
2520 | | |
2521 | | // Report new file to SstFileManagerImpl |
2522 | 1.69k | auto sfm = |
2523 | 1.69k | static_cast<SstFileManagerImpl*>(db_options_.sst_file_manager.get()); |
2524 | 1.69k | if (sfm && meta != nullptr && meta->fd.GetPathId() == 0) { |
2525 | 1.69k | Status add_s = sfm->OnAddFile(fname); |
2526 | 1.69k | if (!add_s.ok() && s.ok()) { |
2527 | 0 | s = add_s; |
2528 | 0 | } |
2529 | 1.69k | if (sfm->IsMaxAllowedSpaceReached()) { |
2530 | | // TODO(ajkr): should we return OK() if max space was reached by the final |
2531 | | // compaction output file (similarly to how flush works when full)? |
2532 | 0 | s = Status::SpaceLimit("Max allowed space was reached"); |
2533 | 0 | TEST_SYNC_POINT( |
2534 | 0 | "CompactionJob::FinishCompactionOutputFile:MaxAllowedSpaceReached"); |
2535 | 0 | InstrumentedMutexLock l(db_mutex_); |
2536 | 0 | db_error_handler_->SetBGError(s, BackgroundErrorReason::kCompaction); |
2537 | 0 | } |
2538 | 1.69k | } |
2539 | | |
2540 | 1.69k | bool should_update_progress = |
2541 | 1.69k | s.ok() && ShouldUpdateSubcompactionProgress( |
2542 | 1.21k | sub_compact, c_iter, prev_iter_output_internal_key, |
2543 | 1.21k | next_table_min_key, meta, blob_file_builder, blob_counter, |
2544 | 1.21k | post_prepare_retry); |
2545 | 1.69k | if (post_prepare_retry) { |
2546 | 0 | TEST_SYNC_POINT_CALLBACK( |
2547 | 0 | "CompactionJob::FinishCompactionOutputFile:" |
2548 | 0 | "PostPrepareRetryShouldUpdateProgress", |
2549 | 0 | &should_update_progress); |
2550 | 0 | } |
2551 | 1.69k | if (should_update_progress) { |
2552 | 0 | s = sub_compact->UpdateBlobGarbageCheckpoint( |
2553 | 0 | blob_counter == nullptr || !blob_counter->TracksCurrentKeyInflow() |
2554 | 0 | ? nullptr |
2555 | 0 | : &blob_counter->GetCurrentKeyInflow()); |
2556 | 0 | } |
2557 | 1.69k | if (s.ok() && should_update_progress && blob_file_builder != nullptr) { |
2558 | | // An SST checkpoint must never reference a blob file that is still open or |
2559 | | // absent from the persisted BlobFileAddition state. Closing here also |
2560 | | // makes a crash before the progress record harmless: workspace cleanup |
2561 | | // removes the uncheckpointed blob file and SST together. |
2562 | 0 | s = blob_file_builder->Finish(); |
2563 | 0 | } |
2564 | 1.69k | if (s.ok() && should_update_progress) { |
2565 | 0 | UpdateSubcompactionProgress(c_iter, next_table_min_key, sub_compact); |
2566 | 0 | s = PersistSubcompactionProgress(sub_compact); |
2567 | 0 | } |
2568 | 1.69k | outputs.ResetBuilder(); |
2569 | 1.69k | return s; |
2570 | 1.69k | } |
2571 | | |
2572 | | bool CompactionJob::ShouldUpdateSubcompactionProgress( |
2573 | | const SubcompactionState* sub_compact, const CompactionIterator* c_iter, |
2574 | | const ParsedInternalKey& prev_iter_output_internal_key, |
2575 | | const Slice& next_table_min_internal_key, const FileMetaData* meta, |
2576 | | const BlobFileBuilder* blob_file_builder, |
2577 | 1.21k | const BlobCountingIterator* blob_counter, bool post_prepare_retry) const { |
2578 | 1.21k | if (post_prepare_retry) { |
2579 | 0 | return false; |
2580 | 0 | } |
2581 | 1.21k | const auto* cfd = sub_compact->compaction->column_family_data(); |
2582 | | // No need to update when the progress will not get persisted |
2583 | 1.21k | if (compaction_progress_writer_ == nullptr) { |
2584 | 1.21k | return false; |
2585 | 1.21k | } |
2586 | | |
2587 | 0 | bool supports_per_key_placement = |
2588 | 0 | sub_compact->compaction->SupportsPerKeyPlacement(); |
2589 | 0 | TEST_SYNC_POINT_CALLBACK( |
2590 | 0 | "CompactionJob::ShouldUpdateSubcompactionProgress:" |
2591 | 0 | "SupportsPerKeyPlacement", |
2592 | 0 | &supports_per_key_placement); |
2593 | 0 | if (supports_per_key_placement && |
2594 | 0 | (blob_file_builder != nullptr || blob_counter != nullptr)) { |
2595 | | // Per-key placement prepares blob output before reaching this boundary. |
2596 | | // Checkpointing would retain an unreferenced blob record when the current |
2597 | | // key is processed again after resumption. |
2598 | 0 | return false; |
2599 | 0 | } |
2600 | | |
2601 | | // LIMITATION: With per-key placement, the file that just closed can belong |
2602 | | // to one output level while the other level still has an open file with |
2603 | | // keys before this boundary. A resume can restore only finished files, so it |
2604 | | // would lose those keys. |
2605 | 0 | if (sub_compact->HasUnfinishedOutputFile()) { |
2606 | 0 | return false; |
2607 | 0 | } |
2608 | | |
2609 | | // No need to update for a new empty output |
2610 | 0 | if (meta == nullptr) { |
2611 | 0 | return false; |
2612 | 0 | } |
2613 | | |
2614 | | // TODO(hx235): save progress even on the last output file |
2615 | 0 | if (next_table_min_internal_key.empty()) { |
2616 | 0 | return false; |
2617 | 0 | } |
2618 | 0 | assert(c_iter != nullptr); |
2619 | | |
2620 | | // LIMITATION: Persisting compaction progress with timestamp |
2621 | | // is not supported since the feature of persisting timestamp of the key in |
2622 | | // SST files itself is still experimental |
2623 | 0 | size_t ts_sz = cfd->user_comparator()->timestamp_size(); |
2624 | 0 | if (ts_sz > 0) { |
2625 | 0 | return false; |
2626 | 0 | } |
2627 | | |
2628 | | // LIMITATION: Compaction progress persistence disabled for file boundaries |
2629 | | // containing range deletions. Range deletions can span file boundaries, |
2630 | | // making it difficult to ensure adjacent output tables have different user |
2631 | | // keys. See the last check for why different users keys of adjacent output |
2632 | | // tables are needed |
2633 | 0 | const ValueType next_table_min_internal_key_type = |
2634 | 0 | ExtractValueType(next_table_min_internal_key); |
2635 | 0 | const ValueType prev_iter_output_internal_key_type = |
2636 | 0 | prev_iter_output_internal_key.user_key.empty() |
2637 | 0 | ? ValueType::kTypeValue |
2638 | 0 | : prev_iter_output_internal_key.type; |
2639 | | |
2640 | | // Range deletes truncated to align with file boundaries may be output by the |
2641 | | // compaction iterator with `ValueType::kTypeMaxValid` instead of the original |
2642 | | // type. |
2643 | 0 | if ((next_table_min_internal_key_type == ValueType::kTypeRangeDeletion || |
2644 | 0 | next_table_min_internal_key_type == ValueType::kTypeMaxValid) || |
2645 | 0 | (prev_iter_output_internal_key_type == ValueType::kTypeRangeDeletion || |
2646 | 0 | prev_iter_output_internal_key_type == ValueType::kTypeMaxValid)) { |
2647 | 0 | return false; |
2648 | 0 | } |
2649 | | |
2650 | | // LIMITATION: Compaction progress persistence disabled when adjacent output |
2651 | | // tables share the same user key at boundaries. This ensures a simple Seek() |
2652 | | // of the next key when resuming can process all versions of a user key |
2653 | 0 | const Slice next_table_min_user_key = |
2654 | 0 | ExtractUserKey(next_table_min_internal_key); |
2655 | 0 | const Slice prev_table_last_user_key = |
2656 | 0 | prev_iter_output_internal_key.user_key.empty() |
2657 | 0 | ? Slice() |
2658 | 0 | : prev_iter_output_internal_key.user_key; |
2659 | |
|
2660 | 0 | if (cfd->user_comparator()->EqualWithoutTimestamp(next_table_min_user_key, |
2661 | 0 | prev_table_last_user_key)) { |
2662 | 0 | return false; |
2663 | 0 | } |
2664 | | |
2665 | | // LIMITATION: Don't save progress when the current output required looking |
2666 | | // ahead or consuming an earlier input entry of the same user key. Resuming |
2667 | | // through Seek() would process those entries again and double-count them. |
2668 | | // A future implementation could persist enough iterator state to allow |
2669 | | // checkpoints at these boundaries. |
2670 | 0 | if (c_iter->IsCurrentKeyAlreadyScanned() || |
2671 | 0 | c_iter->HasConsumedEarlierEntriesOfCurrentKey()) { |
2672 | 0 | if (!logged_unsafe_resume_boundary_.exchange(true, |
2673 | 0 | std::memory_order_relaxed)) { |
2674 | 0 | ROCKS_LOG_INFO(db_options_.info_log, |
2675 | 0 | "[%s] [JOB %d] Skipping resumable compaction checkpoint " |
2676 | 0 | "at an input-consumed user-key boundary", |
2677 | 0 | cfd->GetName().c_str(), job_id_); |
2678 | 0 | } |
2679 | 0 | return false; |
2680 | 0 | } |
2681 | | |
2682 | 0 | return true; |
2683 | 0 | } |
2684 | | |
2685 | 2.07k | Status CompactionJob::InstallCompactionResults(bool* compaction_released) { |
2686 | 2.07k | assert(compact_); |
2687 | | |
2688 | 2.07k | db_mutex_->AssertHeld(); |
2689 | | |
2690 | 2.07k | const ReadOptions read_options(Env::IOActivity::kCompaction); |
2691 | 2.07k | const WriteOptions write_options(Env::IOActivity::kCompaction); |
2692 | | |
2693 | 2.07k | auto* compaction = compact_->compaction; |
2694 | 2.07k | assert(compaction); |
2695 | | |
2696 | 2.07k | { |
2697 | 2.07k | Compaction::InputLevelSummaryBuffer inputs_summary; |
2698 | 2.07k | if (internal_stats_.has_proximal_level_output) { |
2699 | 0 | ROCKS_LOG_BUFFER( |
2700 | 0 | log_buffer_, |
2701 | 0 | "[%s] [JOB %d] Compacted %s => output_to_proximal_level: %" PRIu64 |
2702 | 0 | " bytes + last: %" PRIu64 " bytes. Total: %" PRIu64 " bytes", |
2703 | 0 | compaction->column_family_data()->GetName().c_str(), job_id_, |
2704 | 0 | compaction->InputLevelSummary(&inputs_summary), |
2705 | 0 | internal_stats_.proximal_level_stats.bytes_written, |
2706 | 0 | internal_stats_.output_level_stats.bytes_written, |
2707 | 0 | internal_stats_.TotalBytesWritten()); |
2708 | 2.07k | } else { |
2709 | 2.07k | ROCKS_LOG_BUFFER(log_buffer_, |
2710 | 2.07k | "[%s] [JOB %d] Compacted %s => %" PRIu64 " bytes", |
2711 | 2.07k | compaction->column_family_data()->GetName().c_str(), |
2712 | 2.07k | job_id_, compaction->InputLevelSummary(&inputs_summary), |
2713 | 2.07k | internal_stats_.TotalBytesWritten()); |
2714 | 2.07k | } |
2715 | 2.07k | } |
2716 | | |
2717 | 2.07k | VersionEdit* const edit = compaction->edit(); |
2718 | 2.07k | assert(edit); |
2719 | | |
2720 | 2.07k | struct BlobFileState { |
2721 | 2.07k | uint64_t total_blob_count; |
2722 | 2.07k | uint64_t total_blob_bytes; |
2723 | 2.07k | uint64_t garbage_blob_count; |
2724 | 2.07k | uint64_t garbage_blob_bytes; |
2725 | 2.07k | }; |
2726 | 2.07k | std::unordered_map<uint64_t, BlobFileState> blob_files; |
2727 | 2.07k | const VersionStorageInfo* const storage_info = |
2728 | 2.07k | compaction->input_version()->storage_info(); |
2729 | 2.07k | for (const auto& blob_file : storage_info->GetBlobFiles()) { |
2730 | 0 | assert(blob_file); |
2731 | 0 | blob_files.emplace(blob_file->GetBlobFileNumber(), |
2732 | 0 | BlobFileState{blob_file->GetTotalBlobCount(), |
2733 | 0 | blob_file->GetTotalBlobBytes(), |
2734 | 0 | blob_file->GetGarbageBlobCount(), |
2735 | 0 | blob_file->GetGarbageBlobBytes()}); |
2736 | 0 | } |
2737 | 2.07k | for (const auto& sub_compact : compact_->sub_compact_states) { |
2738 | 2.07k | for (const auto& blob : sub_compact.Current().GetBlobFileAdditions()) { |
2739 | 0 | if (!blob_files |
2740 | 0 | .emplace(blob.GetBlobFileNumber(), |
2741 | 0 | BlobFileState{blob.GetTotalBlobCount(), |
2742 | 0 | blob.GetTotalBlobBytes(), 0, 0}) |
2743 | 0 | .second) { |
2744 | 0 | return Status::Corruption( |
2745 | 0 | "Compaction produced duplicate blob file additions"); |
2746 | 0 | } |
2747 | 0 | } |
2748 | 2.07k | } |
2749 | | |
2750 | 2.07k | std::unordered_map<uint64_t, BlobGarbageMeter::BlobStats> blob_total_garbage; |
2751 | 2.07k | uint64_t skipped_missing_blob_flows = 0; |
2752 | 2.07k | for (const auto& sub_compact : compact_->sub_compact_states) { |
2753 | 2.07k | if (sub_compact.Current().GetBlobGarbageMeter()) { |
2754 | 0 | const auto& flows = sub_compact.Current().GetBlobGarbageMeter()->flows(); |
2755 | |
|
2756 | 0 | for (const auto& pair : flows) { |
2757 | 0 | const uint64_t blob_file_number = pair.first; |
2758 | 0 | const BlobGarbageMeter::BlobInOutFlow& flow = pair.second; |
2759 | |
|
2760 | 0 | assert(flow.IsValid()); |
2761 | 0 | if (flow.HasGarbage()) { |
2762 | 0 | const std::unordered_map<uint64_t, BlobFileState>::const_iterator |
2763 | 0 | blob_file = blob_files.find(blob_file_number); |
2764 | 0 | if (blob_file == blob_files.end()) { |
2765 | 0 | if (blob_file_number != kInvalidBlobFileNumber && |
2766 | 0 | (compaction->skip_missing_blob_files() || |
2767 | 0 | storage_info->IsDroppedBlobFile(blob_file_number))) { |
2768 | 0 | ++skipped_missing_blob_flows; |
2769 | 0 | continue; |
2770 | 0 | } |
2771 | 0 | return Status::Corruption( |
2772 | 0 | "Compaction produced garbage for an unknown blob file"); |
2773 | 0 | } |
2774 | 0 | const BlobFileState& state = blob_file->second; |
2775 | 0 | if (state.garbage_blob_count > state.total_blob_count || |
2776 | 0 | state.garbage_blob_bytes > state.total_blob_bytes) { |
2777 | 0 | return Status::Corruption( |
2778 | 0 | "Compaction encountered invalid existing blob garbage"); |
2779 | 0 | } |
2780 | 0 | BlobGarbageMeter::BlobStats& aggregate = |
2781 | 0 | blob_total_garbage[blob_file_number]; |
2782 | 0 | const uint64_t available_blob_count = |
2783 | 0 | state.total_blob_count - state.garbage_blob_count; |
2784 | 0 | const uint64_t available_blob_bytes = |
2785 | 0 | state.total_blob_bytes - state.garbage_blob_bytes; |
2786 | 0 | if (aggregate.GetCount() > available_blob_count || |
2787 | 0 | aggregate.GetBytes() > available_blob_bytes || |
2788 | 0 | flow.GetGarbageCount() > |
2789 | 0 | available_blob_count - aggregate.GetCount() || |
2790 | 0 | flow.GetGarbageBytes() > |
2791 | 0 | available_blob_bytes - aggregate.GetBytes()) { |
2792 | 0 | return Status::Corruption( |
2793 | 0 | "Compaction blob garbage exceeds file totals"); |
2794 | 0 | } |
2795 | 0 | aggregate.Add(flow.GetGarbageCount(), flow.GetGarbageBytes()); |
2796 | 0 | } |
2797 | 0 | } |
2798 | 0 | } |
2799 | 2.07k | } |
2800 | | |
2801 | 2.07k | if (skipped_missing_blob_flows != 0) { |
2802 | 0 | ROCKS_LOG_BUFFER( |
2803 | 0 | log_buffer_, |
2804 | 0 | "[%s] [JOB %d] Repair omitted %" PRIu64 |
2805 | 0 | " garbage flows for blob files absent from the input Version", |
2806 | 0 | compaction->column_family_data()->GetName().c_str(), job_id_, |
2807 | 0 | skipped_missing_blob_flows); |
2808 | 0 | } |
2809 | | |
2810 | | // Add compaction inputs and outputs only after all blob metadata has been |
2811 | | // validated across subcompactions. |
2812 | 2.07k | compaction->AddInputDeletions(edit); |
2813 | 2.07k | for (const auto& sub_compact : compact_->sub_compact_states) { |
2814 | 2.07k | sub_compact.AddOutputsEdit(edit); |
2815 | | |
2816 | 2.07k | for (const auto& blob : sub_compact.Current().GetBlobFileAdditions()) { |
2817 | 0 | edit->AddBlobFile(blob); |
2818 | 0 | } |
2819 | 2.07k | } |
2820 | | |
2821 | 2.07k | for (const auto& pair : blob_total_garbage) { |
2822 | 0 | const uint64_t blob_file_number = pair.first; |
2823 | 0 | const BlobGarbageMeter::BlobStats& stats = pair.second; |
2824 | |
|
2825 | 0 | edit->AddBlobFileGarbage(blob_file_number, stats.GetCount(), |
2826 | 0 | stats.GetBytes()); |
2827 | 0 | } |
2828 | | |
2829 | 2.07k | if ((compaction->compaction_reason() == |
2830 | 2.07k | CompactionReason::kLevelMaxLevelSize || |
2831 | 2.07k | compaction->compaction_reason() == CompactionReason::kRoundRobinTtl) && |
2832 | 0 | compaction->immutable_options().compaction_pri == kRoundRobin) { |
2833 | 0 | int start_level = compaction->start_level(); |
2834 | 0 | if (start_level > 0) { |
2835 | 0 | auto vstorage = compaction->input_version()->storage_info(); |
2836 | 0 | edit->AddCompactCursor(start_level, |
2837 | 0 | vstorage->GetNextCompactCursor( |
2838 | 0 | start_level, compaction->num_input_files(0))); |
2839 | 0 | } |
2840 | 0 | } |
2841 | | |
2842 | 2.07k | auto manifest_wcb = [&compaction, &compaction_released](const Status& s) { |
2843 | 2.07k | compaction->ReleaseCompactionFiles(s); |
2844 | 2.07k | *compaction_released = true; |
2845 | 2.07k | }; |
2846 | | |
2847 | 2.07k | Status s; |
2848 | 2.07k | TEST_SYNC_POINT_CALLBACK( |
2849 | 2.07k | "CompactionJob::InstallCompactionResults:BeforeLogAndApply", &s); |
2850 | 2.07k | if (s.ok()) { |
2851 | 2.07k | s = versions_->LogAndApply(compaction->column_family_data(), read_options, |
2852 | 2.07k | write_options, edit, db_mutex_, db_directory_, |
2853 | 2.07k | /*new_descriptor_log=*/false, |
2854 | 2.07k | /*column_family_options=*/nullptr, manifest_wcb); |
2855 | 2.07k | } |
2856 | 2.07k | return s; |
2857 | 2.07k | } |
2858 | | |
2859 | 5.54k | void CompactionJob::RecordCompactionIOStats() { |
2860 | 5.54k | RecordTick(stats_, COMPACT_READ_BYTES, IOSTATS(bytes_read)); |
2861 | 5.54k | RecordTick(stats_, COMPACT_WRITE_BYTES, IOSTATS(bytes_written)); |
2862 | 5.54k | CompactionReason compaction_reason = |
2863 | 5.54k | compact_->compaction->compaction_reason(); |
2864 | 5.54k | if (compaction_reason == CompactionReason::kFilesMarkedForCompaction) { |
2865 | 0 | RecordTick(stats_, COMPACT_READ_BYTES_MARKED, IOSTATS(bytes_read)); |
2866 | 0 | RecordTick(stats_, COMPACT_WRITE_BYTES_MARKED, IOSTATS(bytes_written)); |
2867 | 5.54k | } else if (compaction_reason == CompactionReason::kPeriodicCompaction) { |
2868 | 0 | RecordTick(stats_, COMPACT_READ_BYTES_PERIODIC, IOSTATS(bytes_read)); |
2869 | 0 | RecordTick(stats_, COMPACT_WRITE_BYTES_PERIODIC, IOSTATS(bytes_written)); |
2870 | 5.54k | } else if (compaction_reason == CompactionReason::kTtl) { |
2871 | 0 | RecordTick(stats_, COMPACT_READ_BYTES_TTL, IOSTATS(bytes_read)); |
2872 | 0 | RecordTick(stats_, COMPACT_WRITE_BYTES_TTL, IOSTATS(bytes_written)); |
2873 | 0 | } |
2874 | 5.54k | ThreadStatusUtil::IncreaseThreadOperationProperty( |
2875 | 5.54k | ThreadStatus::COMPACTION_BYTES_READ, IOSTATS(bytes_read)); |
2876 | 5.54k | IOSTATS_RESET(bytes_read); |
2877 | 5.54k | ThreadStatusUtil::IncreaseThreadOperationProperty( |
2878 | 5.54k | ThreadStatus::COMPACTION_BYTES_WRITTEN, IOSTATS(bytes_written)); |
2879 | 5.54k | IOSTATS_RESET(bytes_written); |
2880 | 5.54k | } |
2881 | | |
2882 | | Status CompactionJob::OpenCompactionOutputFile(SubcompactionState* sub_compact, |
2883 | 1.69k | CompactionOutputs& outputs) { |
2884 | 1.69k | assert(sub_compact != nullptr); |
2885 | | |
2886 | | // no need to lock because VersionSet::next_file_number_ is atomic |
2887 | 1.69k | uint64_t file_number = versions_->NewFileNumber(); |
2888 | | #ifndef NDEBUG |
2889 | | TEST_SYNC_POINT_CALLBACK( |
2890 | | "CompactionJob::OpenCompactionOutputFile::NewFileNumber", &file_number); |
2891 | | #endif |
2892 | 1.69k | std::string fname = GetTableFileName(file_number); |
2893 | | // Fire events. |
2894 | 1.69k | ColumnFamilyData* cfd = sub_compact->compaction->column_family_data(); |
2895 | 1.69k | EventHelpers::NotifyTableFileCreationStarted( |
2896 | 1.69k | cfd->ioptions().listeners, dbname_, cfd->GetName(), fname, job_id_, |
2897 | 1.69k | TableFileCreationReason::kCompaction); |
2898 | | // Make the output file |
2899 | 1.69k | std::unique_ptr<FSWritableFile> writable_file; |
2900 | | #ifndef NDEBUG |
2901 | | bool syncpoint_arg = file_options_.use_direct_writes; |
2902 | | TEST_SYNC_POINT_CALLBACK("CompactionJob::OpenCompactionOutputFile", |
2903 | | &syncpoint_arg); |
2904 | | #endif |
2905 | | |
2906 | | // Pass temperature of the last level files to FileSystem. |
2907 | 1.69k | FileOptions fo_copy = file_options_; |
2908 | 1.69k | auto temperature = |
2909 | 1.69k | sub_compact->compaction->GetOutputTemperature(outputs.IsProximalLevel()); |
2910 | 1.69k | fo_copy.temperature = temperature; |
2911 | 1.69k | fo_copy.open_contract = FileOpenContract::kNoReopenForWrite | |
2912 | 1.69k | FileOpenContract::kNoReadersWhileOpenForWrite; |
2913 | 1.69k | fo_copy.write_hint = write_hint_; |
2914 | | |
2915 | 1.69k | Status s; |
2916 | 1.69k | IOStatus io_s = NewWritableFile(fs_.get(), fname, &writable_file, fo_copy); |
2917 | 1.69k | s = io_s; |
2918 | 1.69k | if (io_s.ok()) { |
2919 | | // Track the SST file path for cleanup on abort. |
2920 | 1.69k | outputs.AddOutputFilePath(fname); |
2921 | 1.69k | } |
2922 | 1.69k | if (sub_compact->io_status.ok()) { |
2923 | 1.69k | sub_compact->io_status = io_s; |
2924 | | // Since this error is really a copy of the io_s that is checked below as s, |
2925 | | // it does not also need to be checked. |
2926 | 1.69k | sub_compact->io_status.PermitUncheckedError(); |
2927 | 1.69k | } |
2928 | 1.69k | if (!s.ok()) { |
2929 | 0 | ROCKS_LOG_ERROR( |
2930 | 0 | db_options_.info_log, |
2931 | 0 | "[%s] [JOB %d] OpenCompactionOutputFiles for table #%" PRIu64 |
2932 | 0 | " fails at NewWritableFile with status %s", |
2933 | 0 | sub_compact->compaction->column_family_data()->GetName().c_str(), |
2934 | 0 | job_id_, file_number, s.ToString().c_str()); |
2935 | 0 | LogFlush(db_options_.info_log); |
2936 | 0 | EventHelpers::LogAndNotifyTableFileCreationFinished( |
2937 | 0 | event_logger_, cfd->ioptions().listeners, dbname_, cfd->GetName(), |
2938 | 0 | fname, job_id_, FileDescriptor(), kInvalidBlobFileNumber, |
2939 | 0 | TableProperties(), TableFileCreationReason::kCompaction, s, |
2940 | 0 | kUnknownFileChecksum, kUnknownFileChecksumFuncName); |
2941 | 0 | return s; |
2942 | 0 | } |
2943 | | |
2944 | | // Try to figure out the output file's oldest ancestor time. |
2945 | 1.69k | int64_t temp_current_time = 0; |
2946 | 1.69k | auto get_time_status = db_options_.clock->GetCurrentTime(&temp_current_time); |
2947 | | // Safe to proceed even if GetCurrentTime fails. So, log and proceed. |
2948 | 1.69k | if (!get_time_status.ok()) { |
2949 | 0 | ROCKS_LOG_WARN(db_options_.info_log, |
2950 | 0 | "Failed to get current time. Status: %s", |
2951 | 0 | get_time_status.ToString().c_str()); |
2952 | 0 | } |
2953 | 1.69k | uint64_t current_time = static_cast<uint64_t>(temp_current_time); |
2954 | 1.69k | InternalKey tmp_start; |
2955 | 1.69k | InternalKey tmp_end; |
2956 | 1.69k | if (sub_compact->start.has_value()) { |
2957 | 0 | tmp_start.SetMinPossibleForUserKey(*(sub_compact->start)); |
2958 | 0 | } |
2959 | 1.69k | if (sub_compact->end.has_value()) { |
2960 | 0 | tmp_end.SetMinPossibleForUserKey(*(sub_compact->end)); |
2961 | 0 | } |
2962 | 1.69k | uint64_t oldest_ancestor_time = |
2963 | 1.69k | sub_compact->compaction->MinInputFileOldestAncestorTime( |
2964 | 1.69k | sub_compact->start.has_value() ? &tmp_start : nullptr, |
2965 | 1.69k | sub_compact->end.has_value() ? &tmp_end : nullptr); |
2966 | 1.69k | if (oldest_ancestor_time == std::numeric_limits<uint64_t>::max()) { |
2967 | | // TODO: fix DBSSTTest.GetTotalSstFilesSize and use |
2968 | | // kUnknownOldestAncestorTime |
2969 | 0 | oldest_ancestor_time = current_time; |
2970 | 0 | } |
2971 | | |
2972 | 1.69k | uint64_t newest_key_time = sub_compact->compaction->MaxInputFileNewestKeyTime( |
2973 | 1.69k | sub_compact->start.has_value() ? &tmp_start : nullptr, |
2974 | 1.69k | sub_compact->end.has_value() ? &tmp_end : nullptr); |
2975 | | |
2976 | | // Initialize a SubcompactionState::Output and add it to sub_compact->outputs |
2977 | 1.69k | uint64_t epoch_number = sub_compact->compaction->MinInputFileEpochNumber(); |
2978 | 1.69k | { |
2979 | 1.69k | FileMetaData meta; |
2980 | 1.69k | meta.fd = FileDescriptor(file_number, |
2981 | 1.69k | sub_compact->compaction->output_path_id(), 0); |
2982 | 1.69k | meta.oldest_ancestor_time = oldest_ancestor_time; |
2983 | 1.69k | meta.file_creation_time = current_time; |
2984 | 1.69k | meta.db_ownership_start_time = current_time; |
2985 | 1.69k | meta.data_file_provenance = |
2986 | 1.69k | DataFileProvenanceForOrigin(DataFileProvenance::Origin::kCompaction); |
2987 | 1.69k | meta.epoch_number = epoch_number; |
2988 | 1.69k | meta.temperature = temperature; |
2989 | 1.69k | assert(!db_id_.empty()); |
2990 | 1.69k | assert(!db_session_id_.empty()); |
2991 | 1.69k | s = GetSstInternalUniqueId(db_id_, db_session_id_, meta.fd.GetNumber(), |
2992 | 1.69k | &meta.unique_id); |
2993 | 1.69k | if (!s.ok()) { |
2994 | 0 | ROCKS_LOG_ERROR(db_options_.info_log, |
2995 | 0 | "[%s] [JOB %d] file #%" PRIu64 |
2996 | 0 | " failed to generate unique id: %s.", |
2997 | 0 | cfd->GetName().c_str(), job_id_, meta.fd.GetNumber(), |
2998 | 0 | s.ToString().c_str()); |
2999 | 0 | return s; |
3000 | 0 | } |
3001 | | |
3002 | | // Enable hash computation if paranoid_file_checks is on or if |
3003 | | // verify_output_flags includes kVerifyIteration, so that |
3004 | | // VerifyOutputFiles() can compare the hash of the written data |
3005 | | // against a re-read of the output file. |
3006 | 1.69k | bool enable_output_hash = |
3007 | 1.69k | paranoid_file_checks_ || |
3008 | 1.69k | !!(sub_compact->compaction->mutable_cf_options().verify_output_flags & |
3009 | 1.69k | VerifyOutputFlags::kVerifyIteration); |
3010 | 1.69k | outputs.AddOutput(std::move(meta), cfd->internal_comparator(), |
3011 | 1.69k | enable_output_hash); |
3012 | 1.69k | } |
3013 | | |
3014 | 0 | writable_file->SetIOPriority(GetRateLimiterPriority()); |
3015 | | // Subsequent attempts to override the hint via SetWriteLifeTimeHint |
3016 | | // with the very same value will be ignored by the fs. |
3017 | 1.69k | writable_file->SetWriteLifeTimeHint(fo_copy.write_hint); |
3018 | 1.69k | FileTypeSet tmp_set = db_options_.checksum_handoff_file_types; |
3019 | 1.69k | writable_file->SetPreallocationBlockSize(static_cast<size_t>( |
3020 | 1.69k | sub_compact->compaction->OutputFilePreallocationSize())); |
3021 | 1.69k | const auto& listeners = |
3022 | 1.69k | sub_compact->compaction->immutable_options().listeners; |
3023 | 1.69k | outputs.AssignFileWriter(new WritableFileWriter( |
3024 | 1.69k | std::move(writable_file), fname, fo_copy, db_options_.clock, io_tracer_, |
3025 | 1.69k | db_options_.stats, Histograms::SST_WRITE_MICROS, listeners, |
3026 | 1.69k | db_options_.file_checksum_gen_factory.get(), |
3027 | 1.69k | tmp_set.Contains(FileType::kTableFile), |
3028 | 1.69k | tmp_set.Contains(FileType::kTableFile))); |
3029 | | |
3030 | | // TODO(hx235): pass in the correct `oldest_key_time` instead of `0` |
3031 | 1.69k | const ReadOptions read_options(Env::IOActivity::kCompaction); |
3032 | 1.69k | const WriteOptions write_options(Env::IOActivity::kCompaction); |
3033 | 1.69k | TableBuilderOptions tboptions( |
3034 | 1.69k | cfd->ioptions(), sub_compact->compaction->mutable_cf_options(), |
3035 | 1.69k | read_options, write_options, cfd->internal_comparator(), |
3036 | 1.69k | cfd->internal_tbl_prop_coll_factories(), |
3037 | 1.69k | sub_compact->compaction->output_compression(), |
3038 | 1.69k | sub_compact->compaction->output_compression_opts(), cfd->GetID(), |
3039 | 1.69k | cfd->GetName(), sub_compact->compaction->output_level(), newest_key_time, |
3040 | 1.69k | bottommost_level_, TableFileCreationReason::kCompaction, |
3041 | 1.69k | 0 /* oldest_key_time */, db_id_, db_session_id_, |
3042 | 1.69k | sub_compact->compaction->max_output_file_size(), file_number, |
3043 | 1.69k | proximal_after_seqno_ /*last_level_inclusive_max_seqno_threshold*/, |
3044 | 1.69k | dbname_ /*db_name*/, IsRemoteCompaction() /*is_remote_compaction*/); |
3045 | | |
3046 | 1.69k | outputs.NewBuilder(tboptions); |
3047 | | |
3048 | 1.69k | LogFlush(db_options_.info_log); |
3049 | 1.69k | return s; |
3050 | 1.69k | } |
3051 | | |
3052 | 2.77k | void CompactionJob::CleanupCompaction() { |
3053 | 2.77k | for (SubcompactionState& sub_compact : compact_->sub_compact_states) { |
3054 | 2.77k | sub_compact.Cleanup(table_cache_.get(), compact_->status); |
3055 | 2.77k | } |
3056 | 2.77k | delete compact_; |
3057 | 2.77k | compact_ = nullptr; |
3058 | 2.77k | } |
3059 | | |
3060 | | namespace { |
3061 | 2.42k | void CopyPrefix(const Slice& src, size_t prefix_length, std::string* dst) { |
3062 | 2.42k | assert(prefix_length > 0); |
3063 | 2.42k | size_t length = src.size() > prefix_length ? prefix_length : src.size(); |
3064 | 2.42k | dst->assign(src.data(), length); |
3065 | 2.42k | } |
3066 | | } // namespace |
3067 | | |
3068 | | bool CompactionJob::UpdateInternalStatsFromInputFiles( |
3069 | 2.77k | uint64_t* num_input_range_del) { |
3070 | 2.77k | assert(compact_); |
3071 | | |
3072 | 2.77k | Compaction* compaction = compact_->compaction; |
3073 | 2.77k | internal_stats_.output_level_stats.num_input_files_in_non_output_levels = 0; |
3074 | 2.77k | internal_stats_.output_level_stats.num_input_files_in_output_level = 0; |
3075 | | |
3076 | 2.77k | bool has_error = false; |
3077 | 2.77k | const ReadOptions read_options(Env::IOActivity::kCompaction); |
3078 | 2.77k | const auto& input_table_properties = compaction->GetInputTableProperties(); |
3079 | | |
3080 | | // Check all input files for old block-based SST format_version. Why? Old |
3081 | | // block-based SST files from roughly version 5.0 to 5.18 could produce |
3082 | | // inaccurate num_entries counts due to the evolution of its handling along |
3083 | | // with num_range_deletions. We have to disable some paranoid checks when |
3084 | | // compacting files from such an old release. However, we don't have great |
3085 | | // information to identify those files, so we heuristically over-approximate |
3086 | | // that set of files using |
3087 | | // (a) format_version < 5, which will be true for any files from RocksDB < |
3088 | | // 6.6.0 and should not be true for any recent production files |
3089 | | // (b) to avoid including non-block-based SST files (which still use older |
3090 | | // format_version markers, and do not support DeleteRange), we also require |
3091 | | // the presence of the user property "rocksdb.block.based.table.index.type", |
3092 | | // which was added in RocksDB 2.8 and is always present in block-based tables. |
3093 | 10.8k | for (const auto& tp_pair : input_table_properties) { |
3094 | 10.8k | if (tp_pair.second && tp_pair.second->format_version < 5) { |
3095 | | // Check for block-based table by looking for its index type property |
3096 | 0 | const auto& user_props = tp_pair.second->user_collected_properties; |
3097 | 0 | if (user_props.find(BlockBasedTablePropertyNames::kIndexType) != |
3098 | 0 | user_props.end()) { |
3099 | 0 | job_stats_->has_accurate_num_input_records = false; |
3100 | 0 | break; |
3101 | 0 | } |
3102 | 0 | } |
3103 | 10.8k | } |
3104 | | |
3105 | 2.77k | for (int input_level = 0; |
3106 | 7.19k | input_level < static_cast<int>(compaction->num_input_levels()); |
3107 | 4.41k | ++input_level) { |
3108 | 4.41k | const LevelFilesBrief* flevel = compaction->input_levels(input_level); |
3109 | 4.41k | size_t num_input_files = flevel->num_files; |
3110 | 4.41k | uint64_t* bytes_read; |
3111 | 4.41k | if (compaction->level(input_level) != compaction->output_level()) { |
3112 | 1.99k | internal_stats_.output_level_stats.num_input_files_in_non_output_levels += |
3113 | 1.99k | static_cast<int>(num_input_files); |
3114 | 1.99k | bytes_read = |
3115 | 1.99k | &internal_stats_.output_level_stats.bytes_read_non_output_levels; |
3116 | 2.41k | } else { |
3117 | 2.41k | internal_stats_.output_level_stats.num_input_files_in_output_level += |
3118 | 2.41k | static_cast<int>(num_input_files); |
3119 | 2.41k | bytes_read = &internal_stats_.output_level_stats.bytes_read_output_level; |
3120 | 2.41k | } |
3121 | 15.2k | for (size_t i = 0; i < num_input_files; ++i) { |
3122 | 10.8k | const FileMetaData* file_meta = flevel->files[i].file_metadata; |
3123 | 10.8k | *bytes_read += file_meta->fd.GetFileSize(); |
3124 | 10.8k | uint64_t file_input_entries = file_meta->num_entries; |
3125 | 10.8k | uint64_t file_num_range_del = file_meta->num_range_deletions; |
3126 | 10.8k | if (file_input_entries == 0) { |
3127 | 0 | uint64_t file_number = file_meta->fd.GetNumber(); |
3128 | | // Try getting info from table property |
3129 | 0 | std::string fn = TableFileName(compaction->immutable_options().cf_paths, |
3130 | 0 | file_number, file_meta->fd.GetPathId()); |
3131 | 0 | const auto& tp = input_table_properties.find(fn); |
3132 | 0 | if (tp != input_table_properties.end()) { |
3133 | 0 | file_input_entries = tp->second->num_entries; |
3134 | 0 | file_num_range_del = tp->second->num_range_deletions; |
3135 | 0 | } else { |
3136 | 0 | has_error = true; |
3137 | 0 | } |
3138 | 0 | } |
3139 | 10.8k | internal_stats_.output_level_stats.num_input_records += |
3140 | 10.8k | file_input_entries; |
3141 | 10.8k | if (num_input_range_del) { |
3142 | 10.8k | *num_input_range_del += file_num_range_del; |
3143 | 10.8k | } |
3144 | 10.8k | } |
3145 | | |
3146 | 4.41k | const std::vector<FileMetaData*>& filtered_flevel = |
3147 | 4.41k | compaction->filtered_input_levels(input_level); |
3148 | 4.41k | size_t num_filtered_input_files = filtered_flevel.size(); |
3149 | 4.41k | uint64_t* bytes_skipped; |
3150 | 4.41k | if (compaction->level(input_level) != compaction->output_level()) { |
3151 | 1.99k | internal_stats_.output_level_stats |
3152 | 1.99k | .num_filtered_input_files_in_non_output_levels += |
3153 | 1.99k | static_cast<int>(num_filtered_input_files); |
3154 | 1.99k | bytes_skipped = |
3155 | 1.99k | &internal_stats_.output_level_stats.bytes_skipped_non_output_levels; |
3156 | 2.41k | } else { |
3157 | 2.41k | internal_stats_.output_level_stats |
3158 | 2.41k | .num_filtered_input_files_in_output_level += |
3159 | 2.41k | static_cast<int>(num_filtered_input_files); |
3160 | 2.41k | bytes_skipped = |
3161 | 2.41k | &internal_stats_.output_level_stats.bytes_skipped_output_level; |
3162 | 2.41k | } |
3163 | 4.41k | for (const FileMetaData* filtered_file_meta : filtered_flevel) { |
3164 | 0 | *bytes_skipped += filtered_file_meta->fd.GetFileSize(); |
3165 | 0 | } |
3166 | 4.41k | } |
3167 | | |
3168 | | // TODO - find a better place to set these two |
3169 | 2.77k | assert(job_stats_); |
3170 | 2.77k | internal_stats_.output_level_stats.bytes_read_blob = |
3171 | 2.77k | job_stats_->total_blob_bytes_read; |
3172 | 2.77k | internal_stats_.output_level_stats.num_dropped_records = |
3173 | 2.77k | internal_stats_.DroppedRecords(); |
3174 | 2.77k | return !has_error; |
3175 | 2.77k | } |
3176 | | |
3177 | | void CompactionJob::UpdateCompactionJobInputStatsFromInternalStats( |
3178 | | const InternalStats::CompactionStatsFull& internal_stats, |
3179 | 2.77k | uint64_t num_input_range_del) const { |
3180 | 2.77k | assert(job_stats_); |
3181 | | // input information |
3182 | 2.77k | job_stats_->total_input_bytes = |
3183 | 2.77k | internal_stats.output_level_stats.bytes_read_non_output_levels + |
3184 | 2.77k | internal_stats.output_level_stats.bytes_read_output_level; |
3185 | 2.77k | job_stats_->num_input_records = |
3186 | 2.77k | internal_stats.output_level_stats.num_input_records - num_input_range_del; |
3187 | 2.77k | job_stats_->num_input_files = |
3188 | 2.77k | internal_stats.output_level_stats.num_input_files_in_non_output_levels + |
3189 | 2.77k | internal_stats.output_level_stats.num_input_files_in_output_level; |
3190 | 2.77k | job_stats_->num_input_files_at_output_level = |
3191 | 2.77k | internal_stats.output_level_stats.num_input_files_in_output_level; |
3192 | 2.77k | job_stats_->num_filtered_input_files = |
3193 | 2.77k | internal_stats.output_level_stats |
3194 | 2.77k | .num_filtered_input_files_in_non_output_levels + |
3195 | 2.77k | internal_stats.output_level_stats |
3196 | 2.77k | .num_filtered_input_files_in_output_level; |
3197 | 2.77k | job_stats_->num_filtered_input_files_at_output_level = |
3198 | 2.77k | internal_stats.output_level_stats |
3199 | 2.77k | .num_filtered_input_files_in_output_level; |
3200 | 2.77k | job_stats_->total_skipped_input_bytes = |
3201 | 2.77k | internal_stats.output_level_stats.bytes_skipped_non_output_levels + |
3202 | 2.77k | internal_stats.output_level_stats.bytes_skipped_output_level; |
3203 | | |
3204 | 2.77k | if (internal_stats.has_proximal_level_output) { |
3205 | 0 | job_stats_->total_input_bytes += |
3206 | 0 | internal_stats.proximal_level_stats.bytes_read_non_output_levels + |
3207 | 0 | internal_stats.proximal_level_stats.bytes_read_output_level; |
3208 | 0 | job_stats_->num_input_records += |
3209 | 0 | internal_stats.proximal_level_stats.num_input_records; |
3210 | 0 | job_stats_->num_input_files += |
3211 | 0 | internal_stats.proximal_level_stats |
3212 | 0 | .num_input_files_in_non_output_levels + |
3213 | 0 | internal_stats.proximal_level_stats.num_input_files_in_output_level; |
3214 | 0 | job_stats_->num_input_files_at_output_level += |
3215 | 0 | internal_stats.proximal_level_stats.num_input_files_in_output_level; |
3216 | 0 | job_stats_->num_filtered_input_files += |
3217 | 0 | internal_stats.proximal_level_stats |
3218 | 0 | .num_filtered_input_files_in_non_output_levels + |
3219 | 0 | internal_stats.proximal_level_stats |
3220 | 0 | .num_filtered_input_files_in_output_level; |
3221 | 0 | job_stats_->num_filtered_input_files_at_output_level += |
3222 | 0 | internal_stats.proximal_level_stats |
3223 | 0 | .num_filtered_input_files_in_output_level; |
3224 | 0 | job_stats_->total_skipped_input_bytes += |
3225 | 0 | internal_stats.proximal_level_stats.bytes_skipped_non_output_levels + |
3226 | 0 | internal_stats.proximal_level_stats.bytes_skipped_output_level; |
3227 | 0 | } |
3228 | 2.77k | } |
3229 | | |
3230 | | void CompactionJob::UpdateCompactionJobOutputStatsFromInternalStats( |
3231 | | const Status& status, |
3232 | 2.77k | const InternalStats::CompactionStatsFull& internal_stats) const { |
3233 | 2.77k | assert(job_stats_); |
3234 | 2.77k | job_stats_->elapsed_micros = internal_stats.output_level_stats.micros; |
3235 | 2.77k | job_stats_->cpu_micros = internal_stats.output_level_stats.cpu_micros; |
3236 | | |
3237 | | // output information |
3238 | 2.77k | job_stats_->total_output_bytes = |
3239 | 2.77k | internal_stats.output_level_stats.bytes_written; |
3240 | 2.77k | job_stats_->total_output_bytes_blob = |
3241 | 2.77k | internal_stats.output_level_stats.bytes_written_blob; |
3242 | 2.77k | job_stats_->num_output_records = |
3243 | 2.77k | internal_stats.output_level_stats.num_output_records; |
3244 | 2.77k | job_stats_->num_output_files = |
3245 | 2.77k | internal_stats.output_level_stats.num_output_files; |
3246 | 2.77k | job_stats_->num_output_files_blob = |
3247 | 2.77k | internal_stats.output_level_stats.num_output_files_blob; |
3248 | | |
3249 | 2.77k | if (internal_stats.has_proximal_level_output) { |
3250 | 0 | job_stats_->total_output_bytes += |
3251 | 0 | internal_stats.proximal_level_stats.bytes_written; |
3252 | 0 | job_stats_->total_output_bytes_blob += |
3253 | 0 | internal_stats.proximal_level_stats.bytes_written_blob; |
3254 | 0 | job_stats_->num_output_records += |
3255 | 0 | internal_stats.proximal_level_stats.num_output_records; |
3256 | 0 | job_stats_->num_output_files += |
3257 | 0 | internal_stats.proximal_level_stats.num_output_files; |
3258 | 0 | job_stats_->num_output_files_blob += |
3259 | 0 | internal_stats.proximal_level_stats.num_output_files_blob; |
3260 | 0 | } |
3261 | | |
3262 | 2.77k | if (status.ok() && job_stats_->num_output_files > 0) { |
3263 | 1.21k | CopyPrefix(compact_->SmallestUserKey(), |
3264 | 1.21k | CompactionJobStats::kMaxPrefixLength, |
3265 | 1.21k | &job_stats_->smallest_output_key_prefix); |
3266 | 1.21k | CopyPrefix(compact_->LargestUserKey(), CompactionJobStats::kMaxPrefixLength, |
3267 | 1.21k | &job_stats_->largest_output_key_prefix); |
3268 | 1.21k | } |
3269 | 2.77k | } |
3270 | | |
3271 | 2.77k | void CompactionJob::LogCompaction() { |
3272 | 2.77k | Compaction* compaction = compact_->compaction; |
3273 | 2.77k | ColumnFamilyData* cfd = compaction->column_family_data(); |
3274 | | // Let's check if anything will get logged. Don't prepare all the info if |
3275 | | // we're not logging |
3276 | 2.77k | if (db_options_.info_log_level <= InfoLogLevel::INFO_LEVEL) { |
3277 | 2.77k | Compaction::InputLevelSummaryBuffer inputs_summary; |
3278 | 2.77k | ROCKS_LOG_INFO( |
3279 | 2.77k | db_options_.info_log, "[%s] [JOB %d] Compacting %s, score %.2f", |
3280 | 2.77k | cfd->GetName().c_str(), job_id_, |
3281 | 2.77k | compaction->InputLevelSummary(&inputs_summary), compaction->score()); |
3282 | 2.77k | char scratch[2345]; |
3283 | 2.77k | compaction->Summary(scratch, sizeof(scratch)); |
3284 | 2.77k | ROCKS_LOG_INFO(db_options_.info_log, "[%s]: Compaction start summary: %s\n", |
3285 | 2.77k | cfd->GetName().c_str(), scratch); |
3286 | | // build event logger report |
3287 | 2.77k | auto stream = event_logger_->Log(); |
3288 | 2.77k | stream << "job" << job_id_ << "event" << "compaction_started" << "cf_name" |
3289 | 2.77k | << cfd->GetName() << "compaction_reason" |
3290 | 2.77k | << GetCompactionReasonString(compaction->compaction_reason()); |
3291 | 7.19k | for (size_t i = 0; i < compaction->num_input_levels(); ++i) { |
3292 | 4.41k | stream << ("files_L" + std::to_string(compaction->level(i))); |
3293 | 4.41k | stream.StartArray(); |
3294 | 10.8k | for (auto f : *compaction->inputs(i)) { |
3295 | 10.8k | stream << f->fd.GetNumber(); |
3296 | 10.8k | } |
3297 | 4.41k | stream.EndArray(); |
3298 | 4.41k | } |
3299 | 2.77k | stream << "score" << compaction->score() << "input_data_size" |
3300 | 2.77k | << compaction->CalculateTotalInputSize() << "oldest_snapshot_seqno" |
3301 | 2.77k | << (job_context_->snapshot_seqs.empty() |
3302 | 2.77k | ? int64_t{-1} // Use -1 for "none" |
3303 | 2.77k | : static_cast<int64_t>( |
3304 | 0 | job_context_->GetEarliestSnapshotSequence())); |
3305 | 2.77k | if (compaction->SupportsPerKeyPlacement()) { |
3306 | 0 | stream << "proximal_after_seqno" << proximal_after_seqno_; |
3307 | 0 | stream << "preserve_seqno_after" << preserve_seqno_after_; |
3308 | 0 | stream << "proximal_output_level" << compaction->GetProximalLevel(); |
3309 | 0 | stream << "proximal_output_range" |
3310 | 0 | << GetCompactionProximalOutputRangeTypeString( |
3311 | 0 | compaction->GetProximalOutputRangeType()); |
3312 | |
|
3313 | 0 | if (compaction->GetProximalOutputRangeType() == |
3314 | 0 | Compaction::ProximalOutputRangeType::kDisabled) { |
3315 | 0 | ROCKS_LOG_WARN( |
3316 | 0 | db_options_.info_log, |
3317 | 0 | "[%s] [JOB %d] Proximal level output is disabled, likely " |
3318 | 0 | "because of the range conflict in the proximal level", |
3319 | 0 | cfd->GetName().c_str(), job_id_); |
3320 | 0 | } |
3321 | 0 | } |
3322 | 2.77k | } |
3323 | 2.77k | } |
3324 | | |
3325 | 3.38k | std::string CompactionJob::GetTableFileName(uint64_t file_number) { |
3326 | 3.38k | return TableFileName(compact_->compaction->immutable_options().cf_paths, |
3327 | 3.38k | file_number, compact_->compaction->output_path_id()); |
3328 | 3.38k | } |
3329 | | |
3330 | 0 | const std::string& CompactionJob::GetBlobOutputPath() const { |
3331 | 0 | return compact_->compaction->immutable_options().cf_paths.front().path; |
3332 | 0 | } |
3333 | | |
3334 | 0 | uint64_t CompactionJob::NewBlobFileNumber() { |
3335 | 0 | return versions_->NewFileNumber(); |
3336 | 0 | } |
3337 | | |
3338 | 0 | uint64_t CompactionJob::GetNextBlobFileNumber() const { |
3339 | 0 | return versions_->current_next_file_number(); |
3340 | 0 | } |
3341 | | |
3342 | | Status CompactionJob::RestoreNextBlobFileNumber( |
3343 | 0 | const SubcompactionProgress& subcompaction_progress) { |
3344 | 0 | if (!subcompaction_progress.has_blob_compaction_progress) { |
3345 | 0 | return Status::OK(); |
3346 | 0 | } |
3347 | | |
3348 | 0 | const uint64_t next_blob_file_number = |
3349 | 0 | subcompaction_progress.next_blob_file_number; |
3350 | 0 | if (next_blob_file_number == kInvalidBlobFileNumber) { |
3351 | 0 | return Status::Corruption("Invalid next blob file number in progress"); |
3352 | 0 | } |
3353 | 0 | const uint64_t current_next_file_number = |
3354 | 0 | versions_->current_next_file_number(); |
3355 | 0 | if (current_next_file_number < next_blob_file_number) { |
3356 | 0 | versions_->FetchAddFileNumber(next_blob_file_number - |
3357 | 0 | current_next_file_number); |
3358 | 0 | } |
3359 | 0 | return Status::OK(); |
3360 | 0 | } |
3361 | | |
3362 | 5.68k | Env::IOPriority CompactionJob::GetRateLimiterPriority() { |
3363 | 5.68k | if (versions_ && versions_->GetColumnFamilySet() && |
3364 | 5.68k | versions_->GetColumnFamilySet()->write_controller()) { |
3365 | 5.68k | WriteController* write_controller = |
3366 | 5.68k | versions_->GetColumnFamilySet()->write_controller(); |
3367 | 5.68k | if (write_controller->NeedsDelay() || write_controller->IsStopped()) { |
3368 | 8 | return Env::IO_USER; |
3369 | 8 | } |
3370 | 5.68k | } |
3371 | | |
3372 | 5.67k | return Env::IO_LOW; |
3373 | 5.68k | } |
3374 | | |
3375 | | Status CompactionJob::ReadTablePropertiesDirectly( |
3376 | | const ImmutableOptions& ioptions, const MutableCFOptions& moptions, |
3377 | | const FileMetaData* file_meta, const ReadOptions& read_options, |
3378 | 0 | std::shared_ptr<const TableProperties>* tp) { |
3379 | 0 | std::unique_ptr<FSRandomAccessFile> file; |
3380 | 0 | std::string file_name = GetTableFileName(file_meta->fd.GetNumber()); |
3381 | 0 | FileOptions fopts = file_options_; |
3382 | 0 | fopts.file_checksum = file_meta->file_checksum; |
3383 | 0 | fopts.file_checksum_func_name = file_meta->file_checksum_func_name; |
3384 | 0 | Status s = ioptions.fs->NewRandomAccessFile(file_name, fopts, &file, |
3385 | 0 | nullptr /* dbg */); |
3386 | 0 | if (!s.ok()) { |
3387 | 0 | return s; |
3388 | 0 | } |
3389 | | |
3390 | 0 | std::unique_ptr<RandomAccessFileReader> file_reader( |
3391 | 0 | new RandomAccessFileReader( |
3392 | 0 | std::move(file), file_name, ioptions.clock, io_tracer_, |
3393 | 0 | ioptions.stats, Histograms::SST_READ_MICROS /* hist_type */, |
3394 | 0 | nullptr /* file_read_hist */, ioptions.rate_limiter.get(), |
3395 | 0 | ioptions.listeners)); |
3396 | |
|
3397 | 0 | std::unique_ptr<TableProperties> props; |
3398 | |
|
3399 | 0 | uint64_t magic_number = kBlockBasedTableMagicNumber; |
3400 | |
|
3401 | 0 | const auto* table_factory = moptions.table_factory.get(); |
3402 | 0 | if (table_factory == nullptr) { |
3403 | 0 | return Status::Incomplete("Table factory is not set"); |
3404 | 0 | } else { |
3405 | 0 | const auto& table_factory_name = table_factory->Name(); |
3406 | 0 | if (table_factory_name == TableFactory::kPlainTableName()) { |
3407 | 0 | magic_number = kPlainTableMagicNumber; |
3408 | 0 | } else if (table_factory_name == TableFactory::kCuckooTableName()) { |
3409 | 0 | magic_number = kCuckooTableMagicNumber; |
3410 | 0 | } |
3411 | 0 | } |
3412 | | |
3413 | 0 | s = ReadTableProperties(file_reader.get(), file_meta->fd.GetFileSize(), |
3414 | 0 | magic_number, ioptions, read_options, &props); |
3415 | 0 | if (!s.ok()) { |
3416 | 0 | return s; |
3417 | 0 | } |
3418 | | |
3419 | 0 | *tp = std::move(props); |
3420 | 0 | return s; |
3421 | 0 | } |
3422 | | |
3423 | | Status CompactionJob::ReadOutputFilesTableProperties( |
3424 | | const autovector<FileMetaData>& output_files, |
3425 | | const ReadOptions& read_options, |
3426 | | std::vector<std::shared_ptr<const TableProperties>>& |
3427 | | output_files_table_properties, |
3428 | 0 | bool is_proximal_level) { |
3429 | | // `output_files` can be empty: with per-key placement, one of the two output |
3430 | | // levels may have no output file yet when the progress was saved. |
3431 | 0 | const char* level_type = is_proximal_level ? "proximal output" : "output"; |
3432 | |
|
3433 | 0 | output_files_table_properties.reserve(output_files.size()); |
3434 | |
|
3435 | 0 | Status s; |
3436 | |
|
3437 | 0 | for (const FileMetaData& metadata : output_files) { |
3438 | 0 | std::shared_ptr<const TableProperties> tp; |
3439 | 0 | s = ReadTablePropertiesDirectly(compact_->compaction->immutable_options(), |
3440 | 0 | compact_->compaction->mutable_cf_options(), |
3441 | 0 | &metadata, read_options, &tp); |
3442 | 0 | if (!s.ok()) { |
3443 | 0 | ROCKS_LOG_ERROR( |
3444 | 0 | db_options_.info_log, |
3445 | 0 | "Failed to read table properties for %s level output file #%" PRIu64 |
3446 | 0 | ": %s", |
3447 | 0 | level_type, metadata.fd.GetNumber(), s.ToString().c_str()); |
3448 | 0 | return s; |
3449 | 0 | } |
3450 | | |
3451 | 0 | if (tp == nullptr) { |
3452 | 0 | ROCKS_LOG_ERROR(db_options_.info_log, |
3453 | 0 | "Empty table property for %s level output file #%" PRIu64 |
3454 | 0 | "", |
3455 | 0 | level_type, metadata.fd.GetNumber()); |
3456 | |
|
3457 | 0 | s = Status::Corruption("Empty table property for " + |
3458 | 0 | std::string(level_type) + |
3459 | 0 | " level output files during resuming"); |
3460 | 0 | return s; |
3461 | 0 | } |
3462 | 0 | output_files_table_properties.push_back(tp); |
3463 | 0 | } |
3464 | 0 | return s; |
3465 | 0 | } |
3466 | | |
3467 | | void CompactionJob::RestoreCompactionOutputs( |
3468 | | const ColumnFamilyData* cfd, |
3469 | | const std::vector<std::shared_ptr<const TableProperties>>& |
3470 | | output_files_table_properties, |
3471 | | SubcompactionProgressPerLevel& subcompaction_progress_per_level, |
3472 | 0 | CompactionOutputs* outputs_to_restore) { |
3473 | 0 | assert(outputs_to_restore->GetOutputs().size() == 0); |
3474 | |
|
3475 | 0 | const auto& output_files = subcompaction_progress_per_level.GetOutputFiles(); |
3476 | |
|
3477 | 0 | const bool enable_output_hash = |
3478 | 0 | paranoid_file_checks_ || |
3479 | 0 | !!(compact_->compaction->mutable_cf_options().verify_output_flags & |
3480 | 0 | VerifyOutputFlags::kVerifyIteration); |
3481 | |
|
3482 | 0 | uint64_t restored_bytes = 0; |
3483 | 0 | for (size_t i = 0; i < output_files.size(); i++) { |
3484 | 0 | FileMetaData file_copy = output_files[i]; |
3485 | 0 | restored_bytes += file_copy.fd.GetFileSize(); |
3486 | |
|
3487 | 0 | outputs_to_restore->AddOutput(std::move(file_copy), |
3488 | 0 | cfd->internal_comparator(), |
3489 | 0 | enable_output_hash, true /* finished */); |
3490 | |
|
3491 | 0 | outputs_to_restore->UpdateTableProperties( |
3492 | 0 | *output_files_table_properties[i]); |
3493 | 0 | } |
3494 | |
|
3495 | 0 | outputs_to_restore->SetNumOutputRecords( |
3496 | 0 | subcompaction_progress_per_level.GetNumProcessedOutputRecords()); |
3497 | 0 | outputs_to_restore->AddRestoredBytesWritten(restored_bytes); |
3498 | 0 | } |
3499 | | |
3500 | | Status CompactionJob::RestoreBlobCompactionProgress( |
3501 | | SubcompactionState* sub_compact, |
3502 | 0 | const SubcompactionProgress& subcompaction_progress) { |
3503 | 0 | if (!subcompaction_progress.has_blob_compaction_progress) { |
3504 | 0 | return RestoreNextBlobFileNumber(subcompaction_progress); |
3505 | 0 | } |
3506 | | |
3507 | 0 | CompactionOutputs* const outputs = sub_compact->Outputs(false); |
3508 | 0 | assert(outputs); |
3509 | 0 | assert(outputs->GetBlobFileAdditions().empty()); |
3510 | 0 | *outputs->GetBlobFileAdditionsPtr() = |
3511 | 0 | subcompaction_progress.blob_file_additions; |
3512 | |
|
3513 | 0 | if (!subcompaction_progress.blob_file_garbages.empty()) { |
3514 | 0 | BlobGarbageMeter* meter = outputs->GetBlobGarbageMeter(); |
3515 | 0 | if (meter == nullptr) { |
3516 | 0 | meter = outputs->CreateBlobGarbageMeter(); |
3517 | 0 | } |
3518 | 0 | for (const auto& garbage : subcompaction_progress.blob_file_garbages) { |
3519 | 0 | meter->AddGarbage(garbage.GetBlobFileNumber(), |
3520 | 0 | garbage.GetGarbageBlobCount(), |
3521 | 0 | garbage.GetGarbageBlobBytes()); |
3522 | 0 | } |
3523 | 0 | } |
3524 | |
|
3525 | 0 | return RestoreNextBlobFileNumber(subcompaction_progress); |
3526 | 0 | } |
3527 | | |
3528 | | // Attempt to resume compaction from a previously persisted compaction progress. |
3529 | | // |
3530 | | // RETURNS: |
3531 | | // - Status::OK(): |
3532 | | // * Input iterator positioned at next unprocessed key |
3533 | | // * CompactionOutputs objects fully restored for both output and proximal |
3534 | | // output levels in SubcompactionState, including BlobDB additions and garbage |
3535 | | // * Compaction job statistics accurately reflect input and output records |
3536 | | // processed for record count verification |
3537 | | // * SST and blob file-number generation advanced to prevent conflicts with |
3538 | | // existing outputs |
3539 | | // - Status::NotFound(): No valid progress to resume from |
3540 | | // - Status::Incomplete(): Blob state cannot be resumed safely |
3541 | | // - Status::Corruption(): Resume key is invalid, beyond input range, or output |
3542 | | // restoration failed |
3543 | | Status CompactionJob::MaybeResumeSubcompactionProgressOnInputIterator( |
3544 | 2.77k | SubcompactionState* sub_compact, InternalIterator* input_iter) { |
3545 | 2.77k | const ReadOptions read_options(Env::IOActivity::kCompaction); |
3546 | 2.77k | ColumnFamilyData* cfd = sub_compact->compaction->column_family_data(); |
3547 | 2.77k | SubcompactionProgress& subcompaction_progress = |
3548 | 2.77k | sub_compact->GetSubcompactionProgressRef(); |
3549 | | |
3550 | 2.77k | if (subcompaction_progress.output_level_progress |
3551 | 2.77k | .GetNumProcessedOutputRecords() == 0 && |
3552 | 2.77k | subcompaction_progress.proximal_output_level_progress |
3553 | 2.77k | .GetNumProcessedOutputRecords() == 0) { |
3554 | 2.77k | return Status::NotFound("No subcompaction progress to resume"); |
3555 | 2.77k | } |
3556 | | |
3557 | 0 | ROCKS_LOG_INFO(db_options_.info_log, "[%s] [JOB %d] Resuming compaction : %s", |
3558 | 0 | cfd->GetName().c_str(), job_id_, |
3559 | 0 | subcompaction_progress.ToString().c_str()); |
3560 | |
|
3561 | 0 | input_iter->Seek(subcompaction_progress.next_internal_key_to_compact); |
3562 | |
|
3563 | 0 | if (!input_iter->Valid()) { |
3564 | 0 | ROCKS_LOG_ERROR(db_options_.info_log, |
3565 | 0 | "[%s] [JOB %d] Iterator is invalid after " |
3566 | 0 | "seeking to the key to resume. This indicates the key is " |
3567 | 0 | "incorrectly beyond the input data range.", |
3568 | 0 | cfd->GetName().c_str(), job_id_); |
3569 | 0 | return Status::Corruption( |
3570 | 0 | "The key to resume is beyond the input data range"); |
3571 | 0 | } else if (!input_iter->status().ok()) { |
3572 | 0 | ROCKS_LOG_ERROR(db_options_.info_log, |
3573 | 0 | "[%s] [JOB %d] Iterator has error after seeking to " |
3574 | 0 | "the key to resume: %s", |
3575 | 0 | cfd->GetName().c_str(), job_id_, |
3576 | 0 | input_iter->status().ToString().c_str()); |
3577 | 0 | return Status::Corruption( |
3578 | 0 | "Iterator has error status after seeking to the key: " + |
3579 | 0 | input_iter->status().ToString()); |
3580 | 0 | } |
3581 | | |
3582 | 0 | sub_compact->compaction_job_stats.has_accurate_num_input_records = |
3583 | 0 | subcompaction_progress.num_processed_input_records != 0; |
3584 | |
|
3585 | 0 | sub_compact->compaction_job_stats.num_input_records = |
3586 | 0 | subcompaction_progress.num_processed_input_records; |
3587 | |
|
3588 | 0 | for (const bool& is_proximal_level : {false, true}) { |
3589 | 0 | if (is_proximal_level && |
3590 | 0 | !sub_compact->compaction->SupportsPerKeyPlacement()) { |
3591 | 0 | continue; |
3592 | 0 | } |
3593 | | |
3594 | 0 | Status s; |
3595 | 0 | SubcompactionProgressPerLevel& subcompaction_progress_per_level = |
3596 | 0 | is_proximal_level |
3597 | 0 | ? subcompaction_progress.proximal_output_level_progress |
3598 | 0 | : subcompaction_progress.output_level_progress; |
3599 | |
|
3600 | 0 | const auto& output_files = |
3601 | 0 | subcompaction_progress_per_level.GetOutputFiles(); |
3602 | |
|
3603 | 0 | std::vector<std::shared_ptr<const TableProperties>> |
3604 | 0 | output_files_table_properties; |
3605 | | |
3606 | | // TODO(hx235): investigate if we can skip reading properties to save read |
3607 | | // IO |
3608 | 0 | s = ReadOutputFilesTableProperties(output_files, read_options, |
3609 | 0 | output_files_table_properties, |
3610 | 0 | is_proximal_level); |
3611 | 0 | if (!s.ok()) { |
3612 | 0 | ROCKS_LOG_ERROR( |
3613 | 0 | db_options_.info_log, |
3614 | 0 | "[%s] [JOB %d] Failed to read table properties for %s level output " |
3615 | 0 | "files during resume: %s.", |
3616 | 0 | cfd->GetName().c_str(), job_id_, |
3617 | 0 | is_proximal_level ? "proximal output" : "output", |
3618 | 0 | s.ToString().c_str()); |
3619 | 0 | return Status::Corruption( |
3620 | 0 | "Not able to resume due to table property reading error " + |
3621 | 0 | s.ToString()); |
3622 | 0 | } |
3623 | | |
3624 | 0 | RestoreCompactionOutputs(cfd, output_files_table_properties, |
3625 | 0 | subcompaction_progress_per_level, |
3626 | 0 | sub_compact->Outputs(is_proximal_level)); |
3627 | | |
3628 | | // Skip past all the used file numbers to avoid creating new output files |
3629 | | // after resumption that conflict with the existing output files |
3630 | 0 | for (const auto& file_meta : output_files) { |
3631 | 0 | uint64_t file_number = file_meta.fd.GetNumber(); |
3632 | 0 | while (versions_->NewFileNumber() <= file_number) { |
3633 | 0 | versions_->FetchAddFileNumber(1); |
3634 | 0 | } |
3635 | 0 | } |
3636 | 0 | } |
3637 | | |
3638 | 0 | Status blob_status = |
3639 | 0 | RestoreBlobCompactionProgress(sub_compact, subcompaction_progress); |
3640 | 0 | if (!blob_status.ok()) { |
3641 | 0 | return blob_status; |
3642 | 0 | } |
3643 | | |
3644 | 0 | return Status::OK(); |
3645 | 0 | } |
3646 | | |
3647 | | void CompactionJob::UpdateSubcompactionProgress( |
3648 | | const CompactionIterator* c_iter, const Slice next_table_min_key, |
3649 | 0 | SubcompactionState* sub_compact) { |
3650 | 0 | assert(c_iter); |
3651 | 0 | SubcompactionProgress& subcompaction_progress = |
3652 | 0 | sub_compact->GetSubcompactionProgressRef(); |
3653 | |
|
3654 | 0 | IterKey next_ikey_to_compact; |
3655 | 0 | next_ikey_to_compact.SetInternalKey(ExtractUserKey(next_table_min_key), |
3656 | 0 | kMaxSequenceNumber, kValueTypeForSeek); |
3657 | 0 | subcompaction_progress.next_internal_key_to_compact = |
3658 | 0 | next_ikey_to_compact.GetInternalKey().ToString(); |
3659 | | |
3660 | | // Track total processed input records for progress reporting by combining: |
3661 | | // - Resumed count: records already processed before compaction was |
3662 | | // interrupted |
3663 | | // - Current count: records scanned in the current compaction session |
3664 | | // Only update when both tracking mechanisms provide accurate counts to ensure |
3665 | | // reliability. |
3666 | 0 | subcompaction_progress.num_processed_input_records = |
3667 | 0 | c_iter->HasNumInputEntryScanned() && |
3668 | 0 | sub_compact->compaction_job_stats.has_accurate_num_input_records |
3669 | 0 | ? c_iter->NumInputEntryScanned() + |
3670 | 0 | sub_compact->compaction_job_stats.num_input_records |
3671 | 0 | : 0; |
3672 | |
|
3673 | 0 | UpdateSubcompactionProgressPerLevel( |
3674 | 0 | sub_compact, false /* is_proximal_level */, subcompaction_progress); |
3675 | |
|
3676 | 0 | if (sub_compact->compaction->SupportsPerKeyPlacement()) { |
3677 | 0 | UpdateSubcompactionProgressPerLevel( |
3678 | 0 | sub_compact, true /* is_proximal_level */, subcompaction_progress); |
3679 | 0 | } |
3680 | |
|
3681 | 0 | UpdateBlobCompactionProgress(sub_compact, subcompaction_progress); |
3682 | 0 | } |
3683 | | |
3684 | | void CompactionJob::UpdateBlobCompactionProgress( |
3685 | | SubcompactionState* sub_compact, |
3686 | 0 | SubcompactionProgress& subcompaction_progress) { |
3687 | 0 | CompactionOutputs* const outputs = sub_compact->Outputs(false); |
3688 | 0 | assert(outputs); |
3689 | |
|
3690 | 0 | const MutableCFOptions& mutable_cf_options = |
3691 | 0 | sub_compact->compaction->mutable_cf_options(); |
3692 | 0 | const bool can_create_blob_files = |
3693 | 0 | mutable_cf_options.enable_blob_files && |
3694 | 0 | sub_compact->compaction->output_level() >= |
3695 | 0 | mutable_cf_options.blob_file_starting_level; |
3696 | 0 | const BlobGarbageMeter* garbage_meter = |
3697 | 0 | sub_compact->GetBlobGarbageCheckpoint(); |
3698 | 0 | if (garbage_meter == nullptr) { |
3699 | 0 | garbage_meter = outputs->GetBlobGarbageMeter(); |
3700 | 0 | } |
3701 | 0 | if (!can_create_blob_files && garbage_meter == nullptr) { |
3702 | 0 | return; |
3703 | 0 | } |
3704 | | |
3705 | 0 | subcompaction_progress.has_blob_compaction_progress = true; |
3706 | 0 | subcompaction_progress.blob_file_additions = outputs->GetBlobFileAdditions(); |
3707 | 0 | subcompaction_progress.blob_file_garbages.clear(); |
3708 | 0 | if (garbage_meter != nullptr) { |
3709 | 0 | for (const auto& [file_number, flow] : garbage_meter->flows()) { |
3710 | 0 | assert(flow.IsValid()); |
3711 | 0 | if (flow.HasGarbage()) { |
3712 | 0 | subcompaction_progress.blob_file_garbages.emplace_back( |
3713 | 0 | file_number, flow.GetGarbageCount(), flow.GetGarbageBytes()); |
3714 | 0 | } |
3715 | 0 | } |
3716 | 0 | std::sort(subcompaction_progress.blob_file_garbages.begin(), |
3717 | 0 | subcompaction_progress.blob_file_garbages.end(), |
3718 | 0 | [](const BlobFileGarbage& lhs, const BlobFileGarbage& rhs) { |
3719 | 0 | return lhs.GetBlobFileNumber() < rhs.GetBlobFileNumber(); |
3720 | 0 | }); |
3721 | 0 | } |
3722 | 0 | subcompaction_progress.next_blob_file_number = GetNextBlobFileNumber(); |
3723 | 0 | } |
3724 | | |
3725 | | void CompactionJob::UpdateSubcompactionProgressPerLevel( |
3726 | | SubcompactionState* sub_compact, bool is_proximal_level, |
3727 | 0 | SubcompactionProgress& subcompaction_progress) { |
3728 | 0 | SubcompactionProgressPerLevel& subcompaction_progress_per_level = |
3729 | 0 | is_proximal_level ? subcompaction_progress.proximal_output_level_progress |
3730 | 0 | : subcompaction_progress.output_level_progress; |
3731 | |
|
3732 | 0 | subcompaction_progress_per_level.SetNumProcessedOutputRecords( |
3733 | 0 | sub_compact->OutputStats(is_proximal_level)->num_output_records); |
3734 | |
|
3735 | 0 | const auto& prev_output_files = |
3736 | 0 | subcompaction_progress_per_level.GetOutputFiles(); |
3737 | |
|
3738 | 0 | const auto& current_output_files = |
3739 | 0 | sub_compact->Outputs(is_proximal_level)->GetOutputs(); |
3740 | |
|
3741 | 0 | for (size_t i = prev_output_files.size(); i < current_output_files.size(); |
3742 | 0 | i++) { |
3743 | 0 | subcompaction_progress_per_level.AddToOutputFiles( |
3744 | 0 | current_output_files[i].meta); |
3745 | 0 | } |
3746 | 0 | } |
3747 | | |
3748 | | Status CompactionJob::PersistSubcompactionProgress( |
3749 | 0 | SubcompactionState* sub_compact) { |
3750 | 0 | SubcompactionProgress& subcompaction_progress = |
3751 | 0 | sub_compact->GetSubcompactionProgressRef(); |
3752 | |
|
3753 | 0 | assert(compaction_progress_writer_); |
3754 | |
|
3755 | 0 | VersionEdit edit; |
3756 | 0 | edit.SetSubcompactionProgress(subcompaction_progress); |
3757 | |
|
3758 | 0 | std::string record; |
3759 | 0 | if (!edit.EncodeTo(&record)) { |
3760 | 0 | ROCKS_LOG_ERROR( |
3761 | 0 | db_options_.info_log, |
3762 | 0 | "[%s] [JOB %d] Failed to encode subcompaction " |
3763 | 0 | "progress", |
3764 | 0 | compact_->compaction->column_family_data()->GetName().c_str(), job_id_); |
3765 | 0 | return Status::Corruption("Failed to encode subcompaction progress"); |
3766 | 0 | } |
3767 | | |
3768 | 0 | WriteOptions write_options(Env::IOActivity::kCompaction); |
3769 | 0 | Status s = compaction_progress_writer_->AddRecord(write_options, record); |
3770 | 0 | IOOptions opts; |
3771 | 0 | if (s.ok()) { |
3772 | 0 | s = WritableFileWriter::PrepareIOOptions(write_options, opts); |
3773 | 0 | } |
3774 | 0 | if (s.ok()) { |
3775 | 0 | s = db_options_.use_fsync ? compaction_progress_writer_->file()->Fsync(opts) |
3776 | 0 | : compaction_progress_writer_->file()->Sync(opts); |
3777 | 0 | } |
3778 | |
|
3779 | 0 | if (!s.ok()) { |
3780 | 0 | ROCKS_LOG_ERROR( |
3781 | 0 | db_options_.info_log, |
3782 | 0 | "[%s] [JOB %d] Failed to persist subcompaction " |
3783 | 0 | "progress: %s", |
3784 | 0 | compact_->compaction->column_family_data()->GetName().c_str(), job_id_, |
3785 | 0 | s.ToString().c_str()); |
3786 | 0 | return s; |
3787 | 0 | } |
3788 | | |
3789 | 0 | TEST_SYNC_POINT("CompactionJob::PersistSubcompactionProgress:Complete"); |
3790 | |
|
3791 | 0 | subcompaction_progress.output_level_progress |
3792 | 0 | .UpdateLastPersistedOutputFilesCount(); |
3793 | |
|
3794 | 0 | subcompaction_progress.proximal_output_level_progress |
3795 | 0 | .UpdateLastPersistedOutputFilesCount(); |
3796 | |
|
3797 | 0 | subcompaction_progress.UpdateLastPersistedBlobFileAdditionsCount(); |
3798 | |
|
3799 | 0 | return Status::OK(); |
3800 | 0 | } |
3801 | | |
3802 | | Status CompactionJob::VerifyInputRecordCount( |
3803 | 2.07k | uint64_t num_input_range_del) const { |
3804 | 2.07k | size_t ts_sz = compact_->compaction->column_family_data() |
3805 | 2.07k | ->user_comparator() |
3806 | 2.07k | ->timestamp_size(); |
3807 | | // When trim_ts_ is non-empty, CompactionIterator takes |
3808 | | // HistoryTrimmingIterator as input iterator and sees a trimmed view of |
3809 | | // input keys. So the number of keys it processed is not suitable for |
3810 | | // verification here. |
3811 | | // TODO: support verification when trim_ts_ is non-empty. |
3812 | 2.07k | if (!(ts_sz > 0 && !trim_ts_.empty())) { |
3813 | 2.07k | assert(internal_stats_.output_level_stats.num_input_records > 0); |
3814 | | // TODO: verify the number of range deletion entries. |
3815 | 2.07k | uint64_t expected = internal_stats_.output_level_stats.num_input_records - |
3816 | 2.07k | num_input_range_del; |
3817 | 2.07k | uint64_t actual = job_stats_->num_input_records; |
3818 | 2.07k | TEST_SYNC_POINT_CALLBACK("CompactionJob::VerifyInputRecordCount:Actual", |
3819 | 2.07k | &actual); |
3820 | 2.07k | if (expected != actual) { |
3821 | 0 | char scratch[2345]; |
3822 | 0 | compact_->compaction->Summary(scratch, sizeof(scratch)); |
3823 | 0 | std::string msg = |
3824 | 0 | "Compaction number of input keys does not match " |
3825 | 0 | "number of keys processed. Expected " + |
3826 | 0 | std::to_string(expected) + " but processed " + |
3827 | 0 | std::to_string(actual) + ". Compaction summary: " + scratch; |
3828 | 0 | ROCKS_LOG_WARN( |
3829 | 0 | db_options_.info_log, |
3830 | 0 | "[%s] [JOB %d] VerifyInputRecordCount() Status: %s", |
3831 | 0 | compact_->compaction->column_family_data()->GetName().c_str(), |
3832 | 0 | job_context_->job_id, msg.c_str()); |
3833 | 0 | if (db_options_.compaction_verify_record_count) { |
3834 | 0 | return Status::Corruption(msg); |
3835 | 0 | } |
3836 | 0 | } |
3837 | 2.07k | } |
3838 | 2.07k | return Status::OK(); |
3839 | 2.07k | } |
3840 | | |
3841 | 2.07k | Status CompactionJob::VerifyOutputRecordCount() const { |
3842 | 2.07k | uint64_t total_output_num = 0; |
3843 | 2.07k | for (const auto& state : compact_->sub_compact_states) { |
3844 | 2.07k | for (const auto& output : state.GetOutputs()) { |
3845 | 1.21k | total_output_num += output.table_properties->num_entries - |
3846 | 1.21k | output.table_properties->num_range_deletions; |
3847 | 1.21k | } |
3848 | 2.07k | } |
3849 | | |
3850 | 2.07k | uint64_t expected = internal_stats_.output_level_stats.num_output_records; |
3851 | 2.07k | if (internal_stats_.has_proximal_level_output) { |
3852 | 0 | expected += internal_stats_.proximal_level_stats.num_output_records; |
3853 | 0 | } |
3854 | 2.07k | if (expected != total_output_num) { |
3855 | 0 | char scratch[2345]; |
3856 | 0 | compact_->compaction->Summary(scratch, sizeof(scratch)); |
3857 | 0 | std::string msg = |
3858 | 0 | "Number of keys in compaction output SST files does not match " |
3859 | 0 | "number of keys added. Expected " + |
3860 | 0 | std::to_string(expected) + " but there are " + |
3861 | 0 | std::to_string(total_output_num) + |
3862 | 0 | " in output SST files. Compaction summary: " + scratch; |
3863 | 0 | ROCKS_LOG_WARN( |
3864 | 0 | db_options_.info_log, |
3865 | 0 | "[%s] [JOB %d] VerifyOutputRecordCount() status: %s", |
3866 | 0 | compact_->compaction->column_family_data()->GetName().c_str(), |
3867 | 0 | job_context_->job_id, msg.c_str()); |
3868 | 0 | if (db_options_.compaction_verify_record_count) { |
3869 | 0 | return Status::Corruption(msg); |
3870 | 0 | } |
3871 | 0 | } |
3872 | 2.07k | return Status::OK(); |
3873 | 2.07k | } |
3874 | | } // namespace ROCKSDB_NAMESPACE |