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