/src/rocksdb/db/blob/blob_file_builder.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 | | #include "db/blob/blob_file_builder.h" |
7 | | |
8 | | #include <cassert> |
9 | | #include <utility> |
10 | | |
11 | | #include "db/blob/blob_contents.h" |
12 | | #include "db/blob/blob_file_addition.h" |
13 | | #include "db/blob/blob_file_completion_callback.h" |
14 | | #include "db/blob/blob_index.h" |
15 | | #include "db/blob/blob_log_format.h" |
16 | | #include "db/blob/blob_log_writer.h" |
17 | | #include "db/blob/blob_source.h" |
18 | | #include "db/event_helpers.h" |
19 | | #include "db/version_set.h" |
20 | | #include "file/filename.h" |
21 | | #include "file/read_write_util.h" |
22 | | #include "file/writable_file_writer.h" |
23 | | #include "logging/logging.h" |
24 | | #include "options/cf_options.h" |
25 | | #include "options/options_helper.h" |
26 | | #include "rocksdb/file_system.h" |
27 | | #include "rocksdb/slice.h" |
28 | | #include "rocksdb/status.h" |
29 | | #include "test_util/sync_point.h" |
30 | | #include "trace_replay/io_tracer.h" |
31 | | #include "util/compression.h" |
32 | | |
33 | | namespace ROCKSDB_NAMESPACE { |
34 | | |
35 | | BlobFileBuilder::BlobFileBuilder( |
36 | | VersionSet* versions, FileSystem* fs, |
37 | | const ImmutableOptions* immutable_options, |
38 | | const MutableCFOptions* mutable_cf_options, const FileOptions* file_options, |
39 | | const WriteOptions* write_options, std::string db_id, |
40 | | std::string db_session_id, int job_id, uint32_t column_family_id, |
41 | | const std::string& column_family_name, Env::WriteLifeTimeHint write_hint, |
42 | | const std::shared_ptr<IOTracer>& io_tracer, |
43 | | BlobFileCompletionCallback* blob_callback, |
44 | | BlobFileCreationReason creation_reason, std::string blob_output_path, |
45 | | std::vector<std::string>* output_file_paths, |
46 | | std::vector<BlobFileAddition>* blob_file_additions) |
47 | 0 | : BlobFileBuilder([versions]() { return versions->NewFileNumber(); }, fs, |
48 | 0 | immutable_options, mutable_cf_options, file_options, |
49 | 0 | write_options, std::move(db_id), std::move(db_session_id), |
50 | 0 | job_id, column_family_id, column_family_name, write_hint, |
51 | 0 | io_tracer, blob_callback, creation_reason, |
52 | 0 | std::move(blob_output_path), output_file_paths, |
53 | 0 | blob_file_additions) {} |
54 | | |
55 | | BlobFileBuilder::BlobFileBuilder( |
56 | | std::function<uint64_t()> file_number_generator, FileSystem* fs, |
57 | | const ImmutableOptions* immutable_options, |
58 | | const MutableCFOptions* mutable_cf_options, const FileOptions* file_options, |
59 | | const WriteOptions* write_options, std::string db_id, |
60 | | std::string db_session_id, int job_id, uint32_t column_family_id, |
61 | | const std::string& column_family_name, Env::WriteLifeTimeHint write_hint, |
62 | | const std::shared_ptr<IOTracer>& io_tracer, |
63 | | BlobFileCompletionCallback* blob_callback, |
64 | | BlobFileCreationReason creation_reason, std::string blob_output_path, |
65 | | std::vector<std::string>* output_file_paths, |
66 | | std::vector<BlobFileAddition>* blob_file_additions) |
67 | 0 | : file_number_generator_(std::move(file_number_generator)), |
68 | 0 | fs_(fs), |
69 | 0 | immutable_options_(immutable_options), |
70 | 0 | min_blob_size_(mutable_cf_options->min_blob_size), |
71 | 0 | blob_file_size_(mutable_cf_options->blob_file_size), |
72 | | blob_file_writable_file_max_buffer_size_( |
73 | 0 | mutable_cf_options->blob_file_writable_file_max_buffer_size), |
74 | 0 | blob_compression_type_(mutable_cf_options->blob_compression_type), |
75 | | // TODO with schema change: support custom compression manager and options |
76 | | // such as max_compressed_bytes_per_kb |
77 | | // NOTE: returns nullptr for kNoCompression |
78 | 0 | blob_compressor_(GetBuiltinV2CompressionManager()->GetCompressor( |
79 | 0 | mutable_cf_options->blob_compression_opts, blob_compression_type_)), |
80 | 0 | blob_compressor_wa_(blob_compressor_ |
81 | 0 | ? blob_compressor_->ObtainWorkingArea() |
82 | 0 | : Compressor::ManagedWorkingArea{}), |
83 | 0 | prepopulate_blob_cache_(mutable_cf_options->prepopulate_blob_cache), |
84 | 0 | file_options_(file_options), |
85 | 0 | write_options_(write_options), |
86 | 0 | db_id_(std::move(db_id)), |
87 | 0 | db_session_id_(std::move(db_session_id)), |
88 | 0 | job_id_(job_id), |
89 | 0 | column_family_id_(column_family_id), |
90 | 0 | column_family_name_(column_family_name), |
91 | 0 | write_hint_(write_hint), |
92 | 0 | io_tracer_(io_tracer), |
93 | 0 | blob_callback_(blob_callback), |
94 | 0 | creation_reason_(creation_reason), |
95 | 0 | blob_output_path_(std::move(blob_output_path)), |
96 | 0 | output_file_paths_(output_file_paths), |
97 | 0 | blob_file_additions_(blob_file_additions), |
98 | 0 | blob_count_(0), |
99 | 0 | blob_bytes_(0) { |
100 | 0 | assert(file_number_generator_); |
101 | 0 | assert(fs_); |
102 | 0 | assert(immutable_options_); |
103 | 0 | assert(file_options_); |
104 | 0 | assert(write_options_); |
105 | 0 | assert(output_file_paths_); |
106 | 0 | assert(output_file_paths_->empty()); |
107 | 0 | assert(blob_file_additions_); |
108 | | // Resumable compactions restore finalized additions before creating a new |
109 | | // builder. Any newly generated blob files are appended to that state. |
110 | 0 | } |
111 | | |
112 | 0 | BlobFileBuilder::~BlobFileBuilder() = default; |
113 | | |
114 | 0 | std::string BlobFileBuilder::GetBlobFilePath(uint64_t blob_file_number) const { |
115 | 0 | return BlobFileName(blob_output_path_, blob_file_number); |
116 | 0 | } |
117 | | |
118 | | Status BlobFileBuilder::Add(const Slice& key, const Slice& value, |
119 | 0 | std::string* blob_index) { |
120 | 0 | assert(blob_index); |
121 | 0 | assert(blob_index->empty()); |
122 | |
|
123 | 0 | if (value.empty() || value.size() < min_blob_size_) { |
124 | 0 | return Status::OK(); |
125 | 0 | } |
126 | | |
127 | 0 | { |
128 | 0 | const Status s = OpenBlobFileIfNeeded(); |
129 | 0 | if (!s.ok()) { |
130 | 0 | return s; |
131 | 0 | } |
132 | 0 | } |
133 | | |
134 | 0 | Slice blob = value; |
135 | 0 | GrowableBuffer compressed_blob; |
136 | |
|
137 | 0 | { |
138 | 0 | const Status s = CompressBlobIfNeeded(&blob, &compressed_blob); |
139 | 0 | if (!s.ok()) { |
140 | 0 | return s; |
141 | 0 | } |
142 | 0 | } |
143 | | |
144 | 0 | uint64_t blob_file_number = 0; |
145 | 0 | uint64_t blob_offset = 0; |
146 | |
|
147 | 0 | { |
148 | 0 | const Status s = |
149 | 0 | WriteBlobToFile(key, blob, &blob_file_number, &blob_offset); |
150 | 0 | if (!s.ok()) { |
151 | 0 | return s; |
152 | 0 | } |
153 | 0 | } |
154 | | |
155 | 0 | { |
156 | 0 | const Status s = CloseBlobFileIfNeeded(); |
157 | 0 | if (!s.ok()) { |
158 | 0 | return s; |
159 | 0 | } |
160 | 0 | } |
161 | | |
162 | 0 | { |
163 | 0 | const Status s = |
164 | 0 | PutBlobIntoCacheIfNeeded(value, blob_file_number, blob_offset); |
165 | 0 | if (!s.ok()) { |
166 | 0 | ROCKS_LOG_WARN(immutable_options_->info_log, |
167 | 0 | "Failed to pre-populate the blob into blob cache: %s", |
168 | 0 | s.ToString().c_str()); |
169 | 0 | } |
170 | 0 | } |
171 | |
|
172 | 0 | BlobIndex::EncodeBlob(blob_index, blob_file_number, blob_offset, blob.size(), |
173 | 0 | blob_compression_type_); |
174 | |
|
175 | 0 | return Status::OK(); |
176 | 0 | } |
177 | | |
178 | 0 | Status BlobFileBuilder::Finish() { |
179 | 0 | if (!IsBlobFileOpen()) { |
180 | 0 | return Status::OK(); |
181 | 0 | } |
182 | | |
183 | 0 | return CloseBlobFile(); |
184 | 0 | } |
185 | | |
186 | 0 | bool BlobFileBuilder::IsBlobFileOpen() const { return !!writer_; } |
187 | | |
188 | 0 | Status BlobFileBuilder::OpenBlobFileIfNeeded() { |
189 | 0 | if (IsBlobFileOpen()) { |
190 | 0 | return Status::OK(); |
191 | 0 | } |
192 | | |
193 | 0 | assert(!blob_count_); |
194 | 0 | assert(!blob_bytes_); |
195 | |
|
196 | 0 | assert(file_number_generator_); |
197 | 0 | const uint64_t blob_file_number = file_number_generator_(); |
198 | 0 | if (blob_file_number == kInvalidBlobFileNumber) { |
199 | 0 | return Status::Incomplete("Blob file number range exhausted"); |
200 | 0 | } |
201 | | |
202 | 0 | std::string blob_file_path = GetBlobFilePath(blob_file_number); |
203 | |
|
204 | 0 | if (blob_callback_) { |
205 | 0 | blob_callback_->OnBlobFileCreationStarted( |
206 | 0 | blob_file_path, column_family_name_, job_id_, creation_reason_); |
207 | 0 | } |
208 | |
|
209 | 0 | std::unique_ptr<FSWritableFile> file; |
210 | 0 | FileOptions fo_copy; |
211 | 0 | { |
212 | 0 | assert(file_options_); |
213 | 0 | fo_copy = *file_options_; |
214 | 0 | fo_copy.open_contract = FileOpenContract::kNoReopenForWrite | |
215 | 0 | FileOpenContract::kNoReadersWhileOpenForWrite; |
216 | 0 | fo_copy.write_hint = write_hint_; |
217 | 0 | if (blob_file_writable_file_max_buffer_size_ > 0) { |
218 | 0 | fo_copy.writable_file_max_buffer_size = |
219 | 0 | blob_file_writable_file_max_buffer_size_; |
220 | 0 | } |
221 | 0 | Status s = NewWritableFile(fs_, blob_file_path, &file, fo_copy); |
222 | |
|
223 | 0 | TEST_SYNC_POINT_CALLBACK( |
224 | 0 | "BlobFileBuilder::OpenBlobFileIfNeeded:NewWritableFile", &s); |
225 | |
|
226 | 0 | if (!s.ok()) { |
227 | 0 | return s; |
228 | 0 | } |
229 | 0 | } |
230 | | |
231 | | // Note: blob files get added to output_file_paths_ right after the open, so |
232 | | // they can be cleaned up upon failure. Contrast this with |
233 | | // blob_file_additions_, which only contains successfully written files. |
234 | 0 | assert(output_file_paths_); |
235 | 0 | output_file_paths_->emplace_back(std::move(blob_file_path)); |
236 | |
|
237 | 0 | assert(file); |
238 | 0 | file->SetIOPriority(write_options_->rate_limiter_priority); |
239 | | // Subsequent attempts to override the hint via SetWriteLifeTimeHint |
240 | | // with the very same value will be ignored by the fs. |
241 | 0 | file->SetWriteLifeTimeHint(fo_copy.write_hint); |
242 | 0 | FileTypeSet tmp_set = immutable_options_->checksum_handoff_file_types; |
243 | 0 | Statistics* const statistics = immutable_options_->stats; |
244 | 0 | std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter( |
245 | 0 | std::move(file), output_file_paths_->back(), fo_copy, |
246 | 0 | immutable_options_->clock, io_tracer_, statistics, |
247 | 0 | Histograms::BLOB_DB_BLOB_FILE_WRITE_MICROS, immutable_options_->listeners, |
248 | 0 | immutable_options_->file_checksum_gen_factory.get(), |
249 | 0 | tmp_set.Contains(FileType::kBlobFile), false)); |
250 | |
|
251 | 0 | constexpr bool do_flush = false; |
252 | |
|
253 | 0 | std::unique_ptr<BlobLogWriter> blob_log_writer(new BlobLogWriter( |
254 | 0 | std::move(file_writer), immutable_options_->clock, statistics, |
255 | 0 | blob_file_number, immutable_options_->use_fsync, do_flush)); |
256 | |
|
257 | 0 | constexpr bool has_ttl = false; |
258 | 0 | constexpr ExpirationRange expiration_range; |
259 | |
|
260 | 0 | BlobLogHeader header(column_family_id_, blob_compression_type_, has_ttl, |
261 | 0 | expiration_range); |
262 | |
|
263 | 0 | { |
264 | 0 | Status s = blob_log_writer->WriteHeader(*write_options_, header); |
265 | |
|
266 | 0 | TEST_SYNC_POINT_CALLBACK( |
267 | 0 | "BlobFileBuilder::OpenBlobFileIfNeeded:WriteHeader", &s); |
268 | |
|
269 | 0 | if (!s.ok()) { |
270 | 0 | return s; |
271 | 0 | } |
272 | 0 | } |
273 | | |
274 | 0 | writer_ = std::move(blob_log_writer); |
275 | |
|
276 | 0 | assert(IsBlobFileOpen()); |
277 | |
|
278 | 0 | return Status::OK(); |
279 | 0 | } |
280 | | |
281 | | Status BlobFileBuilder::CompressBlobIfNeeded( |
282 | 0 | Slice* blob, GrowableBuffer* compressed_blob) const { |
283 | 0 | assert(blob); |
284 | 0 | assert(compressed_blob); |
285 | 0 | assert(compressed_blob->empty()); |
286 | 0 | assert(immutable_options_); |
287 | |
|
288 | 0 | if (!blob_compressor_) { |
289 | 0 | assert(blob_compression_type_ == kNoCompression); |
290 | 0 | return Status::OK(); |
291 | 0 | } |
292 | 0 | assert(blob_compression_type_ != kNoCompression); |
293 | | |
294 | | // WART: always stored as compressed even when that increases the size. |
295 | |
|
296 | 0 | Status s; |
297 | 0 | StopWatch stop_watch(immutable_options_->clock, immutable_options_->stats, |
298 | 0 | BLOB_DB_COMPRESSION_MICROS); |
299 | 0 | s = LegacyForceBuiltinCompression(*blob_compressor_, &blob_compressor_wa_, |
300 | 0 | *blob, compressed_blob); |
301 | 0 | if (!s.ok()) { |
302 | 0 | return s; |
303 | 0 | } |
304 | | |
305 | 0 | *blob = Slice(*compressed_blob); |
306 | |
|
307 | 0 | return Status::OK(); |
308 | 0 | } |
309 | | |
310 | | Status BlobFileBuilder::WriteBlobToFile(const Slice& key, const Slice& blob, |
311 | | uint64_t* blob_file_number, |
312 | 0 | uint64_t* blob_offset) { |
313 | 0 | assert(IsBlobFileOpen()); |
314 | 0 | assert(blob_file_number); |
315 | 0 | assert(blob_offset); |
316 | |
|
317 | 0 | uint64_t key_offset = 0; |
318 | |
|
319 | 0 | Status s = |
320 | 0 | writer_->AddRecord(*write_options_, key, blob, &key_offset, blob_offset); |
321 | |
|
322 | 0 | TEST_SYNC_POINT_CALLBACK("BlobFileBuilder::WriteBlobToFile:AddRecord", &s); |
323 | |
|
324 | 0 | if (!s.ok()) { |
325 | 0 | return s; |
326 | 0 | } |
327 | | |
328 | 0 | *blob_file_number = writer_->get_log_number(); |
329 | |
|
330 | 0 | ++blob_count_; |
331 | 0 | blob_bytes_ += BlobLogRecord::kHeaderSize + key.size() + blob.size(); |
332 | |
|
333 | 0 | return Status::OK(); |
334 | 0 | } |
335 | | |
336 | 0 | Status BlobFileBuilder::CloseBlobFile() { |
337 | 0 | assert(IsBlobFileOpen()); |
338 | |
|
339 | 0 | BlobLogFooter footer; |
340 | 0 | footer.blob_count = blob_count_; |
341 | |
|
342 | 0 | std::string checksum_method; |
343 | 0 | std::string checksum_value; |
344 | |
|
345 | 0 | Status s = writer_->AppendFooter(*write_options_, footer, &checksum_method, |
346 | 0 | &checksum_value); |
347 | |
|
348 | 0 | TEST_SYNC_POINT_CALLBACK("BlobFileBuilder::WriteBlobToFile:AppendFooter", &s); |
349 | |
|
350 | 0 | if (!s.ok()) { |
351 | 0 | return s; |
352 | 0 | } |
353 | | |
354 | 0 | const uint64_t blob_file_number = writer_->get_log_number(); |
355 | |
|
356 | 0 | if (blob_callback_) { |
357 | 0 | s = blob_callback_->OnBlobFileCompleted( |
358 | 0 | GetBlobFilePath(blob_file_number), column_family_name_, job_id_, |
359 | 0 | blob_file_number, creation_reason_, s, checksum_value, checksum_method, |
360 | 0 | blob_count_, blob_bytes_, /*set_bg_error_on_space_limit=*/true); |
361 | 0 | } |
362 | |
|
363 | 0 | assert(blob_file_additions_); |
364 | 0 | blob_file_additions_->emplace_back(blob_file_number, blob_count_, blob_bytes_, |
365 | 0 | std::move(checksum_method), |
366 | 0 | std::move(checksum_value)); |
367 | |
|
368 | 0 | assert(immutable_options_); |
369 | 0 | ROCKS_LOG_INFO(immutable_options_->logger, |
370 | 0 | "[%s] [JOB %d] Generated blob file #%" PRIu64 ": %" PRIu64 |
371 | 0 | " total blobs, %" PRIu64 " total bytes", |
372 | 0 | column_family_name_.c_str(), job_id_, blob_file_number, |
373 | 0 | blob_count_, blob_bytes_); |
374 | |
|
375 | 0 | writer_.reset(); |
376 | 0 | blob_count_ = 0; |
377 | 0 | blob_bytes_ = 0; |
378 | |
|
379 | 0 | return s; |
380 | 0 | } |
381 | | |
382 | 0 | Status BlobFileBuilder::CloseBlobFileIfNeeded() { |
383 | 0 | assert(IsBlobFileOpen()); |
384 | |
|
385 | 0 | const WritableFileWriter* const file_writer = writer_->file(); |
386 | 0 | assert(file_writer); |
387 | |
|
388 | 0 | if (file_writer->GetFileSize() < blob_file_size_) { |
389 | 0 | return Status::OK(); |
390 | 0 | } |
391 | | |
392 | 0 | return CloseBlobFile(); |
393 | 0 | } |
394 | | |
395 | 0 | void BlobFileBuilder::Abandon(const Status& s) { |
396 | 0 | if (!IsBlobFileOpen()) { |
397 | 0 | return; |
398 | 0 | } |
399 | 0 | if (blob_callback_) { |
400 | | // BlobFileBuilder::Abandon() is called because of error while writing to |
401 | | // Blob files. So we can ignore the below error. |
402 | 0 | const uint64_t blob_file_number = writer_->get_log_number(); |
403 | 0 | blob_callback_ |
404 | 0 | ->OnBlobFileCompleted(GetBlobFilePath(blob_file_number), |
405 | 0 | column_family_name_, job_id_, blob_file_number, |
406 | 0 | creation_reason_, s, "", "", blob_count_, |
407 | 0 | blob_bytes_, /*set_bg_error_on_space_limit=*/true) |
408 | 0 | .PermitUncheckedError(); |
409 | 0 | } |
410 | |
|
411 | 0 | writer_.reset(); |
412 | 0 | blob_count_ = 0; |
413 | 0 | blob_bytes_ = 0; |
414 | 0 | } |
415 | | |
416 | | Status BlobFileBuilder::PutBlobIntoCacheIfNeeded(const Slice& blob, |
417 | | uint64_t blob_file_number, |
418 | 0 | uint64_t blob_offset) const { |
419 | 0 | Status s = Status::OK(); |
420 | |
|
421 | 0 | BlobSource::SharedCacheInterface blob_cache{immutable_options_->blob_cache}; |
422 | 0 | auto statistics = immutable_options_->statistics.get(); |
423 | 0 | bool warm_cache = |
424 | 0 | prepopulate_blob_cache_ == PrepopulateBlobCache::kFlushOnly && |
425 | 0 | creation_reason_ == BlobFileCreationReason::kFlush; |
426 | |
|
427 | 0 | if (blob_cache && warm_cache) { |
428 | 0 | const OffsetableCacheKey base_cache_key(db_id_, db_session_id_, |
429 | 0 | blob_file_number); |
430 | 0 | const CacheKey cache_key = base_cache_key.WithOffset(blob_offset); |
431 | 0 | const Slice key = cache_key.AsSlice(); |
432 | |
|
433 | 0 | const Cache::Priority priority = Cache::Priority::BOTTOM; |
434 | |
|
435 | 0 | s = blob_cache.InsertSaved(key, blob, nullptr /*context*/, priority, |
436 | 0 | immutable_options_->lowest_used_cache_tier); |
437 | |
|
438 | 0 | if (s.ok()) { |
439 | 0 | RecordTick(statistics, BLOB_DB_CACHE_ADD); |
440 | 0 | RecordTick(statistics, BLOB_DB_CACHE_BYTES_WRITE, blob.size()); |
441 | 0 | } else { |
442 | 0 | RecordTick(statistics, BLOB_DB_CACHE_ADD_FAILURES); |
443 | 0 | } |
444 | 0 | } |
445 | |
|
446 | 0 | return s; |
447 | 0 | } |
448 | | |
449 | | } // namespace ROCKSDB_NAMESPACE |