Coverage Report

Created: 2026-11-12 08:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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