Coverage Report

Created: 2026-09-28 07:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/rocksdb/db/blob/blob_fetcher.h
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
#pragma once
7
8
#include <utility>
9
10
#include "db/blob/blob_constants.h"
11
#include "rocksdb/options.h"
12
#include "rocksdb/status.h"
13
14
namespace ROCKSDB_NAMESPACE {
15
16
class BlobFileCache;
17
class Version;
18
class Slice;
19
class FilePrefetchBuffer;
20
class PinnableSlice;
21
class BlobIndex;
22
class SameFileBlobReader;
23
24
// Abstract interface for blob retrieval on the read path. An implementation
25
// resolves a BlobIndex to its value, pinning it into a PinnableSlice. Callers
26
// hold a plain const BlobFetcher* so resolution can be intercepted by a
27
// decorator (e.g. EmbeddedAwareBlobFetcher) without their knowledge.
28
class BlobFetcher {
29
 public:
30
57.0k
  virtual ~BlobFetcher() = default;
31
32
  // Convenience overload: decodes `blob_index_slice` into a BlobIndex and
33
  // dispatches to the virtual overload below. Non-virtual; shared by all
34
  // implementations.
35
  Status FetchBlob(const Slice& user_key, const Slice& blob_index_slice,
36
                   FilePrefetchBuffer* prefetch_buffer,
37
                   PinnableSlice* blob_value, uint64_t* bytes_read) const;
38
39
  virtual Status FetchBlob(const Slice& user_key, const BlobIndex& blob_index,
40
                           FilePrefetchBuffer* prefetch_buffer,
41
                           PinnableSlice* blob_value,
42
                           uint64_t* bytes_read) const = 0;
43
44
  // The read options this fetcher operates under. Used (via an
45
  // EmbeddedAwareBlobFetcher decorator) to resolve same-file blob references,
46
  // which need the current read_tier.
47
  virtual const ReadOptions& read_options() const = 0;
48
};
49
50
// Shared implementation of the two Version-backed BlobFetchers below. Reads
51
// through a Version, optionally falling back to direct-write blob files that
52
// are not yet manifest-visible. Everything except the ReadOptions is stored
53
// here; the concrete subclass decides whether it references a caller-owned
54
// ReadOptions (VersionBlobFetcher) or owns its own copy
55
// (OwningVersionBlobFetcher). FetchBlob reads the options via the virtual
56
// read_options() so both storage strategies share one implementation.
57
class VersionBlobFetcherBase : public BlobFetcher {
58
 public:
59
  // Un-hide the base's Slice-based convenience overload.
60
  using BlobFetcher::FetchBlob;
61
62
  Status FetchBlob(const Slice& user_key, const BlobIndex& blob_index,
63
                   FilePrefetchBuffer* prefetch_buffer,
64
                   PinnableSlice* blob_value,
65
                   uint64_t* bytes_read) const override;
66
67
  // Fetches the value of a separate-file blob reference into *blob_value,
68
  // applying `verify_policy` (see BlobVerifyPolicy). range_length ==
69
  // kWholeBlobLength reads the whole value (delegating to FetchBlob, which also
70
  // handles the direct-write fallback, and forcing whole-record checksum
71
  // verification for kVerifyIfPresent even when ReadOptions::verify_checksums
72
  // is off). Any other length reads only the sub-range [range_offset,
73
  // range_offset + range_length) directly from the blob file, reading just
74
  // those bytes on a cache miss -- no record-header/key read, no whole-record
75
  // checksum, no cache fill (see Version::GetBlobRange /
76
  // BlobSource::GetBlobRange). A strict sub-range is only valid when
77
  // SupportsRangeRead() is true and verify_policy != kVerifyIfPresent
78
  // (verifying a strict sub-range would have to amplify the read to the whole
79
  // record); callers check that first and take the whole-value path otherwise.
80
  // Not routed through the same-file (embedded) decorator -- same-file
81
  // references are resolved by the caller through a SameFileBlobReader.
82
  Status FetchBlobRange(const Slice& user_key, const BlobIndex& blob_index,
83
                        uint64_t range_offset, size_t range_length,
84
                        BlobVerifyPolicy verify_policy,
85
                        PinnableSlice* blob_value, uint64_t* bytes_read) const;
86
87
  // True if this fetcher has enough context to resolve a (non-inlined,
88
  // non-same-file) blob reference: either a Version to read through, or the
89
  // direct-write fallback (a blob file cache) enabled. Callers that may hold a
90
  // null Version (e.g. DBIter) can check this to surface a clean Corruption on
91
  // an unexpected blob index instead of dereferencing null in FetchBlob.
92
0
  bool CanResolve() const {
93
0
    return version_ != nullptr ||
94
0
           (allow_write_path_fallback_ && blob_file_cache_ != nullptr);
95
0
  }
96
97
  // True if this fetcher can serve an I/O-saving byte-range read: it reads
98
  // through a Version (FetchBlobRange goes to Version::GetBlobRange) and is not
99
  // in direct-write fallback mode (whose reads resolve whole records instead).
100
0
  bool SupportsRangeRead() const {
101
0
    return version_ != nullptr && !allow_write_path_fallback_;
102
0
  }
103
104
  // The Version this fetcher reads separate-file blob references through (may
105
  // be null for a direct-write-fallback-only fetcher). Used by the cross-key
106
  // lazy coalescing path to group and dispatch reads via
107
  // Version::MultiGetBlobLazy.
108
0
  const Version* version() const { return version_; }
109
110
 protected:
111
  VersionBlobFetcherBase(const Version* version, BlobFileCache* blob_file_cache,
112
                         bool allow_write_path_fallback)
113
59.8k
      : version_(version),
114
59.8k
        blob_file_cache_(blob_file_cache),
115
59.8k
        allow_write_path_fallback_(allow_write_path_fallback) {}
116
117
  const Version* version_;
118
  BlobFileCache* blob_file_cache_;
119
  bool allow_write_path_fallback_;
120
};
121
122
// The default BlobFetcher for transient/stack use: holds a reference to a
123
// caller-owned ReadOptions rather than a copy, avoiding a per-fetcher copy of
124
// the (growing) ReadOptions on the hot path. The referenced ReadOptions must
125
// outlive the fetcher, so this is only appropriate for fetchers whose lifetime
126
// is bounded by the caller's (e.g. a stack local per Get / per resolution).
127
// Use OwningVersionBlobFetcher for lifetime-independent fetchers.
128
class VersionBlobFetcher : public VersionBlobFetcherBase {
129
 public:
130
  VersionBlobFetcher(const Version* version, const ReadOptions& read_options,
131
                     BlobFileCache* blob_file_cache = nullptr,
132
                     bool allow_write_path_fallback = false)
133
2.80k
      : VersionBlobFetcherBase(version, blob_file_cache,
134
2.80k
                               allow_write_path_fallback),
135
2.80k
        read_options_(read_options) {}
136
137
  // Reject binding to a temporary ReadOptions, which would leave a dangling
138
  // reference. Callers that cannot guarantee the ReadOptions outlives the
139
  // fetcher must use OwningVersionBlobFetcher instead.
140
  VersionBlobFetcher(const Version* version, ReadOptions&& read_options,
141
                     BlobFileCache* blob_file_cache = nullptr,
142
                     bool allow_write_path_fallback = false) = delete;
143
144
0
  const ReadOptions& read_options() const override { return read_options_; }
145
146
 private:
147
  const ReadOptions& read_options_;
148
};
149
150
// A BlobFetcher that owns its ReadOptions, for fetchers whose lifetime is
151
// independent of the caller that created them (the lazy read-path resolver and
152
// compaction). Takes ownership of the ReadOptions by rvalue so ownership
153
// transfer is explicit and no hidden copy is made on the way in; later reads
154
// never touch caller state. Callers holding a borrowed ReadOptions they cannot
155
// move must make the copy explicit (e.g. ReadOptions(read_options)).
156
class OwningVersionBlobFetcher : public VersionBlobFetcherBase {
157
 public:
158
  OwningVersionBlobFetcher(const Version* version, ReadOptions&& read_options,
159
                           BlobFileCache* blob_file_cache = nullptr,
160
                           bool allow_write_path_fallback = false)
161
57.0k
      : VersionBlobFetcherBase(version, blob_file_cache,
162
57.0k
                               allow_write_path_fallback),
163
57.0k
        read_options_(std::move(read_options)) {}
164
165
0
  const ReadOptions& read_options() const override { return read_options_; }
166
167
 private:
168
  ReadOptions read_options_;
169
};
170
171
// A BlobFetcher decorator that resolves same-file ("embedded") blob references
172
// against a SameFileBlobReader (the current SST) and delegates all other
173
// references to a base BlobFetcher. It is cheap to construct as a stack local
174
// per resolution: the same-file target is per-SST while the base fetcher is one
175
// per-Get object shared across SSTs, so an immutable per-SST decorator avoids
176
// shared-mutable-state hazards under async MultiGet.
177
//
178
// A decorator is "enabled" when it has a same-file reader and "disabled"
179
// (unusable) when constructed with a null `same_file_reader` (e.g. via the
180
// default constructor). A disabled decorator must not be routed through
181
// directly; callers construct one unconditionally and use EffectiveFetcher(),
182
// which returns this decorator when enabled or the undecorated base fetcher
183
// otherwise. This avoids std::optional / branching at call sites while adding
184
// no indirection on the common (non-embedded) path.
185
//
186
// The base fetcher may be null for an entity whose blob columns are all
187
// same-file (no separate-file blob support wired, e.g. SstFileReader); a
188
// non-same-file reference then surfaces as a Corruption rather than silently.
189
class EmbeddedAwareBlobFetcher : public BlobFetcher {
190
 public:
191
  explicit EmbeddedAwareBlobFetcher(
192
      const BlobFetcher* base = nullptr,
193
      const SameFileBlobReader* same_file_reader = nullptr)
194
0
      : base_(base), same_file_reader_(same_file_reader) {}
195
196
  // The fetcher to route resolution through: this decorator when it has a
197
  // same-file reader, otherwise the (undecorated) base fetcher. Only an enabled
198
  // decorator's FetchBlob is ever invoked.
199
0
  const BlobFetcher* EffectiveFetcher() const {
200
0
    return same_file_reader_ != nullptr ? this : base_;
201
0
  }
202
203
  // Un-hide the base's Slice-based convenience overload.
204
  using BlobFetcher::FetchBlob;
205
206
  Status FetchBlob(const Slice& user_key, const BlobIndex& blob_index,
207
                   FilePrefetchBuffer* prefetch_buffer,
208
                   PinnableSlice* blob_value,
209
                   uint64_t* bytes_read) const override;
210
211
0
  const ReadOptions& read_options() const override {
212
    // Same-file resolution uses the base fetcher's read options; when there is
213
    // no base (e.g. SstFileReader) a default (kReadAllTier) suffices.
214
0
    static const ReadOptions kDefaultReadOptions;
215
0
    return base_ != nullptr ? base_->read_options() : kDefaultReadOptions;
216
0
  }
217
218
 private:
219
  const BlobFetcher* base_;
220
  // Null iff this decorator is disabled (see class comment); otherwise the
221
  // same-file blob reader for the current SST.
222
  const SameFileBlobReader* same_file_reader_;
223
};
224
225
}  // namespace ROCKSDB_NAMESPACE