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_source.h
Line
Count
Source
1
//  Copyright (c) Meta Platforms, Inc. and affiliates.
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 <cinttypes>
9
#include <memory>
10
11
#include "cache/cache_key.h"
12
#include "cache/typed_cache.h"
13
#include "db/blob/blob_contents.h"
14
#include "db/blob/blob_file_cache.h"
15
#include "db/blob/blob_read_request.h"
16
#include "rocksdb/cache.h"
17
#include "rocksdb/rocksdb_namespace.h"
18
#include "table/block_based/cachable_entry.h"
19
#include "util/autovector.h"
20
21
namespace ROCKSDB_NAMESPACE {
22
23
struct ImmutableOptions;
24
struct MutableCFOptions;
25
class Status;
26
class FilePrefetchBuffer;
27
class Slice;
28
class RandomAccessFileReader;
29
enum ChecksumType : char;
30
31
// One whole-record request for BlobSource::MultiGetSimpleGen2Blob (the batched
32
// counterpart of GetSimpleGen2Blob). The per-file context (base cache key,
33
// file, checksum context) is passed once to the batch call; these are the
34
// per-record inputs and outputs.
35
struct SimpleGen2BlobReadRequest {
36
  uint64_t record_offset = 0;
37
  uint64_t payload_size = 0;
38
  CompressionType expected_compression = kNoCompression;
39
  PinnableSlice* result = nullptr;
40
  Status* status = nullptr;
41
};
42
43
// One sub-range request for BlobSource::MultiGetSimpleGen2BlobRange (the
44
// batched counterpart of GetSimpleGen2BlobRange).
45
struct SimpleGen2BlobRangeReadRequest {
46
  uint64_t record_offset = 0;
47
  uint64_t payload_size = 0;
48
  uint64_t range_offset = 0;
49
  size_t range_length = 0;
50
  CompressionType expected_compression = kNoCompression;
51
  PinnableSlice* result = nullptr;
52
  Status* status = nullptr;
53
};
54
55
// BlobSource is a class that provides universal access to blobs, regardless of
56
// whether they are in the blob cache, secondary cache, or (remote) storage.
57
// Depending on user settings, it always fetch blobs from multi-tier cache and
58
// storage with minimal cost.
59
class BlobSource {
60
 public:
61
  // NOTE: db_id, db_session_id, and blob_file_cache are saved by reference or
62
  // pointer.
63
  BlobSource(const ImmutableOptions& immutable_options,
64
             const MutableCFOptions& mutable_cf_options,
65
             const std::string& db_id, const std::string& db_session_id,
66
             BlobFileCache* blob_file_cache);
67
68
  BlobSource(const BlobSource&) = delete;
69
  BlobSource& operator=(const BlobSource&) = delete;
70
71
  ~BlobSource();
72
73
  // Read a blob from the underlying cache or one blob file.
74
  //
75
  // If successful, returns ok and sets "*value" to the newly retrieved
76
  // uncompressed blob. If there was an error while fetching the blob, sets
77
  // "*value" to empty and returns a non-ok status.
78
  //
79
  // Note: For consistency, whether the blob is found in the cache or on disk,
80
  // sets "*bytes_read" to the size of on-disk (possibly compressed) blob
81
  // record.
82
  Status GetBlob(const ReadOptions& read_options, const Slice& user_key,
83
                 uint64_t file_number, uint64_t offset, uint64_t file_size,
84
                 uint64_t value_size, CompressionType compression_type,
85
                 FilePrefetchBuffer* prefetch_buffer, PinnableSlice* value,
86
                 uint64_t* bytes_read);
87
88
  // Reads a byte sub-range [range_offset, range_offset + range_length) of an
89
  // *uncompressed* blob's value, reading only those bytes on a cache miss.
90
  //
91
  // First probes the blob cache for the whole value. On a hit, pins the cache
92
  // handle into *value and points it at the requested sub-range (zero-copy, no
93
  // disk I/O). On a miss, reads only the requested bytes from the blob file
94
  // (via BlobFileReader::GetBlobRange -- no record-header/key read and no
95
  // whole-record checksum verification) and pins the owned buffer into *value.
96
  //
97
  // Unlike GetBlob, a partial read never inserts into the blob cache: the cache
98
  // entry is keyed per (file, offset) and holds the *whole* BlobContents, so a
99
  // partial value would violate that invariant. `compression_type` must be
100
  // kNoCompression (a strict sub-range of a compressed blob cannot be
101
  // decompressed in isolation); callers that need a compressed column, a
102
  // whole-column read, or checksum verification use GetBlob and slice instead.
103
  //
104
  // The caller must ensure range_offset + range_length <= value_size. On a miss
105
  // *bytes_read (when non-null) is the number of bytes read from the file; on a
106
  // hit it is 0.
107
  Status GetBlobRange(const ReadOptions& read_options, const Slice& user_key,
108
                      uint64_t file_number, uint64_t offset, uint64_t file_size,
109
                      uint64_t value_size, CompressionType compression_type,
110
                      uint64_t range_offset, size_t range_length,
111
                      PinnableSlice* value, uint64_t* bytes_read);
112
113
  // Reads a SimpleGen2Blob payload (see db/blob/blob_gen2_format.h) through the
114
  // blob value cache and BLOB_DB_* statistics. This is the counterpart to
115
  // GetBlob() for the second-generation blob record format, which is read
116
  // directly from a RandomAccessFileReader rather than from a traditional blob
117
  // file.
118
  //
119
  // The cache key is derived from the SimpleGen2Blob format itself, not chosen
120
  // by the caller: it is GetSimpleGen2BlobCacheKey(base_cache_key,
121
  // record_offset), the same offset scheme block-based SST blocks use. The
122
  // caller supplies only its file's `base_cache_key` (db_id / db_session_id /
123
  // file_number). This keeps blob records collision-free with the file's data
124
  // blocks even when the blob cache and block cache are the same cache.
125
  //
126
  // `file`, `record_offset`, `payload_size`, `checksum_type`,
127
  // `base_context_checksum`, and `expected_compression` are the inputs to the
128
  // SimpleGen2Blob reader used on a cache miss (see
129
  // ReadAndVerifySimpleGen2BlobRecord). The on-disk record size (payload +
130
  // trailer) is reported via `*bytes_read` (when non-null) and the
131
  // BLOB_DB_BLOB_FILE_BYTES_READ / blob_read_byte counters, consistently on
132
  // both cache hits and misses.
133
  //
134
  // On a cache hit, pins the cached value into `*value` (no copy). On a miss,
135
  // reads + verifies the record into a cache-allocator buffer, records the
136
  // per-read stats, inserts it into the cache (when configured and fill_cache
137
  // is set), and pins it into `*value`. If blob_cache_ is not configured, the
138
  // record is still read and read stats recorded, just without a cache
139
  // lookup/insert.
140
  Status GetSimpleGen2Blob(const ReadOptions& read_options,
141
                           const OffsetableCacheKey& base_cache_key,
142
                           RandomAccessFileReader* file, uint64_t record_offset,
143
                           uint64_t payload_size, ChecksumType checksum_type,
144
                           uint32_t base_context_checksum,
145
                           CompressionType expected_compression,
146
                           PinnableSlice* value, uint64_t* bytes_read);
147
148
  // Reads a byte sub-range [range_offset, range_offset + range_length) of an
149
  // *uncompressed* SimpleGen2Blob payload, reading only those bytes on a cache
150
  // miss. This is the embedded (same-file) counterpart of GetBlobRange().
151
  //
152
  // First probes the blob cache for the whole payload (keyed exactly as
153
  // GetSimpleGen2Blob). On a hit, pins the cache handle into *value and points
154
  // it at the requested sub-range (zero-copy, no disk I/O). On a miss, reads
155
  // only the requested bytes from the file (via ReadSimpleGen2BlobRange -- no
156
  // trailer read and no checksum verification) and pins the owned buffer.
157
  //
158
  // Like GetBlobRange, a partial read never inserts into the blob cache (the
159
  // cache entry holds the whole payload). `expected_compression` must be
160
  // kNoCompression. The caller must ensure range_offset + range_length <=
161
  // payload_size. On a miss *bytes_read (when non-null) is the number of bytes
162
  // read from the file; on a hit it is 0.
163
  Status GetSimpleGen2BlobRange(const ReadOptions& read_options,
164
                                const OffsetableCacheKey& base_cache_key,
165
                                RandomAccessFileReader* file,
166
                                uint64_t record_offset, uint64_t payload_size,
167
                                ChecksumType checksum_type,
168
                                uint32_t base_context_checksum,
169
                                CompressionType expected_compression,
170
                                uint64_t range_offset, size_t range_length,
171
                                PinnableSlice* value, uint64_t* bytes_read);
172
173
  // Batched counterpart of GetSimpleGen2Blob: resolves `num_records` whole
174
  // embedded (SimpleGen2Blob) records from one file, coalescing all cache-miss
175
  // reads into a single MultiRead. The per-file context (base cache key, file,
176
  // checksum type, base context checksum) is shared by all records; per-record
177
  // inputs/outputs are in `reqs`. Each record independently probes the blob
178
  // cache and, on a miss, is read + verified from the file; whole records are
179
  // cache-filled (when configured and fill_cache) exactly as GetSimpleGen2Blob.
180
  // Per-record outcome is written to *reqs[i].status.
181
  void MultiGetSimpleGen2Blob(const ReadOptions& read_options,
182
                              const OffsetableCacheKey& base_cache_key,
183
                              RandomAccessFileReader* file,
184
                              ChecksumType checksum_type,
185
                              uint32_t base_context_checksum,
186
                              size_t num_records,
187
                              SimpleGen2BlobReadRequest* reqs);
188
189
  // Batched counterpart of GetSimpleGen2BlobRange: resolves `num_records`
190
  // uncompressed embedded sub-ranges from one file, coalescing cache-miss reads
191
  // into a single MultiRead. Like GetSimpleGen2BlobRange, each record probes
192
  // the whole-payload cache (slicing on a hit) and a partial read is never
193
  // inserted into the cache. Per-record outcome is in *reqs[i].status.
194
  void MultiGetSimpleGen2BlobRange(const ReadOptions& read_options,
195
                                   const OffsetableCacheKey& base_cache_key,
196
                                   RandomAccessFileReader* file,
197
                                   size_t num_records,
198
                                   SimpleGen2BlobRangeReadRequest* reqs);
199
200
  // Read multiple blobs from the underlying cache or blob file(s).
201
  //
202
  // If successful, returns ok and sets "result" in the elements of "blob_reqs"
203
  // to the newly retrieved uncompressed blobs. If there was an error while
204
  // fetching one of blobs, sets its "result" to empty and sets its
205
  // corresponding "status" to a non-ok status.
206
  //
207
  // Note:
208
  //  - The main difference between this function and MultiGetBlobFromOneFile is
209
  //    that this function can read multiple blobs from multiple blob files.
210
  //
211
  //  - For consistency, whether the blob is found in the cache or on disk, sets
212
  //  "*bytes_read" to the total size of on-disk (possibly compressed) blob
213
  //  records.
214
  void MultiGetBlob(const ReadOptions& read_options,
215
                    autovector<BlobFileReadRequests>& blob_reqs,
216
                    uint64_t* bytes_read);
217
218
  // Read multiple blobs from the underlying cache or one blob file.
219
  //
220
  // If successful, returns ok and sets "result" in the elements of "blob_reqs"
221
  // to the newly retrieved uncompressed blobs. If there was an error while
222
  // fetching one of blobs, sets its "result" to empty and sets its
223
  // corresponding "status" to a non-ok status.
224
  //
225
  // Note:
226
  //  - The main difference between this function and MultiGetBlob is that this
227
  //  function is only used for the case where the demanded blobs are stored in
228
  //  one blob file. MultiGetBlob will call this function multiple times if the
229
  //  demanded blobs are stored in multiple blob files.
230
  //
231
  //  - For consistency, whether the blob is found in the cache or on disk, sets
232
  //  "*bytes_read" to the total size of on-disk (possibly compressed) blob
233
  //  records.
234
  void MultiGetBlobFromOneFile(const ReadOptions& read_options,
235
                               uint64_t file_number, uint64_t file_size,
236
                               autovector<BlobReadRequest>& blob_reqs,
237
                               uint64_t* bytes_read);
238
239
  // Byte-range (partial) multi-read counterpart of MultiGetBlob: for each
240
  // request reads only its sub-range of an *uncompressed* blob value, from the
241
  // blob cache (whole-value hit, sliced) or coalesced disk reads (one MultiRead
242
  // per file). Like GetBlobRange, a partial read never fills the blob cache.
243
  // Groups requests by file and dispatches each file via
244
  // MultiGetBlobRangeFromOneFile. Per-request outcome is in
245
  // *blob_reqs[..].status.
246
  void MultiGetBlobRange(const ReadOptions& read_options,
247
                         autovector<BlobFileRangeReadRequests>& blob_reqs,
248
                         uint64_t* bytes_read);
249
250
  // Byte-range (partial) multi-read counterpart of MultiGetBlobFromOneFile for
251
  // a single blob file. See MultiGetBlobRange.
252
  void MultiGetBlobRangeFromOneFile(const ReadOptions& read_options,
253
                                    uint64_t file_number, uint64_t file_size,
254
                                    autovector<BlobRangeReadRequest>& blob_reqs,
255
                                    uint64_t* bytes_read);
256
257
  inline Status GetBlobFileReader(
258
      const ReadOptions& read_options, uint64_t blob_file_number,
259
0
      CacheHandleGuard<BlobFileReader>* blob_file_reader) {
260
0
    return blob_file_cache_->GetBlobFileReader(read_options, blob_file_number,
261
0
                                               blob_file_reader);
262
0
  }
263
264
0
  inline Cache* GetBlobCache() const { return blob_cache_.get(); }
265
266
  bool TEST_BlobInCache(uint64_t file_number, uint64_t file_size,
267
                        uint64_t offset, size_t* charge = nullptr) const;
268
269
  // For TypedSharedCacheInterface
270
  void Create(BlobContents** out, const char* buf, size_t size,
271
              MemoryAllocator* alloc);
272
273
  using SharedCacheInterface =
274
      FullTypedSharedCacheInterface<BlobContents, BlobContentsCreator>;
275
  using TypedHandle = SharedCacheInterface::TypedHandle;
276
277
 private:
278
  Status GetBlobFromCache(const Slice& cache_key,
279
                          CacheHandleGuard<BlobContents>* cached_blob) const;
280
281
  Status PutBlobIntoCache(const Slice& cache_key,
282
                          std::unique_ptr<BlobContents>* blob,
283
                          CacheHandleGuard<BlobContents>* cached_blob) const;
284
285
  static void PinCachedBlob(CacheHandleGuard<BlobContents>* cached_blob,
286
                            PinnableSlice* value);
287
288
  static void PinOwnedBlob(std::unique_ptr<BlobContents>* owned_blob,
289
                           PinnableSlice* value);
290
291
  TypedHandle* GetEntryFromCache(const Slice& key) const;
292
293
  Status InsertEntryIntoCache(const Slice& key, BlobContents* value,
294
                              TypedHandle** cache_handle,
295
                              Cache::Priority priority) const;
296
297
  inline CacheKey GetCacheKey(uint64_t file_number, uint64_t /*file_size*/,
298
0
                              uint64_t offset) const {
299
0
    OffsetableCacheKey base_cache_key(db_id_, db_session_id_, file_number);
300
0
    return base_cache_key.WithOffset(offset);
301
0
  }
302
303
  const std::string& db_id_;
304
  const std::string& db_session_id_;
305
306
  Statistics* statistics_;
307
308
  // A cache to store blob file reader.
309
  BlobFileCache* blob_file_cache_;
310
311
  // A cache to store uncompressed blobs.
312
  mutable SharedCacheInterface blob_cache_;
313
314
  // The control option of how the cache tiers will be used. Currently rocksdb
315
  // support block/blob cache (volatile tier) and secondary cache (this tier
316
  // isn't strictly speaking a non-volatile tier since the compressed cache in
317
  // this tier is in volatile memory).
318
  const CacheTier lowest_used_cache_tier_;
319
};
320
321
}  // namespace ROCKSDB_NAMESPACE