/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 |