be/src/io/cache/cached_remote_file_reader.h
Line | Count | Source |
1 | | // Licensed to the Apache Software Foundation (ASF) under one |
2 | | // or more contributor license agreements. See the NOTICE file |
3 | | // distributed with this work for additional information |
4 | | // regarding copyright ownership. The ASF licenses this file |
5 | | // to you under the Apache License, Version 2.0 (the |
6 | | // "License"); you may not use this file except in compliance |
7 | | // with the License. You may obtain a copy of the License at |
8 | | // |
9 | | // http://www.apache.org/licenses/LICENSE-2.0 |
10 | | // |
11 | | // Unless required by applicable law or agreed to in writing, |
12 | | // software distributed under the License is distributed on an |
13 | | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
14 | | // KIND, either express or implied. See the License for the |
15 | | // specific language governing permissions and limitations |
16 | | // under the License. |
17 | | |
18 | | #pragma once |
19 | | |
20 | | #include <cstddef> |
21 | | #include <cstdint> |
22 | | #include <map> |
23 | | #include <shared_mutex> |
24 | | #include <unordered_set> |
25 | | #include <utility> |
26 | | #include <vector> |
27 | | |
28 | | #include "common/status.h" |
29 | | #include "io/cache/block_file_cache.h" |
30 | | #include "io/cache/file_block.h" |
31 | | #include "io/cache/file_cache_common.h" |
32 | | #include "io/fs/file_reader.h" |
33 | | #include "io/fs/file_reader_writer_fwd.h" |
34 | | #include "io/fs/path.h" |
35 | | #include "util/slice.h" |
36 | | |
37 | | namespace doris::io { |
38 | | struct IOContext; |
39 | | struct FileCacheStatistics; |
40 | | struct PeerFetchResult; |
41 | | |
42 | | } // namespace doris::io |
43 | | |
44 | | namespace doris { |
45 | | struct PeerCandidate; |
46 | | } |
47 | | |
48 | | namespace doris::io { |
49 | | struct SourceReadBreakdown { |
50 | | int64_t local_bytes = 0; |
51 | | int64_t remote_bytes = 0; |
52 | | int64_t peer_bytes = 0; |
53 | | }; |
54 | | using PeerFetchedBlockSet = std::unordered_set<const FileBlock*>; |
55 | | |
56 | | class CachedRemoteFileReader final : public FileReader, |
57 | | public std::enable_shared_from_this<CachedRemoteFileReader> { |
58 | | public: |
59 | | /// Construct a cached reader on top of a remote reader. |
60 | | /// @param[in] remote_file_reader Underlying reader used for remote/peer fallback reads. |
61 | | /// @param[in] opts File reader options used to initialize cache identity and policy. |
62 | | /// @return None. |
63 | | CachedRemoteFileReader(FileReaderSPtr remote_file_reader, const FileReaderOptions& opts); |
64 | | |
65 | | /// Destroy the cached reader and release direct cache-file ownership tracked by this reader. |
66 | | /// @return None. |
67 | | ~CachedRemoteFileReader() override; |
68 | | |
69 | | /// Close the underlying remote reader. |
70 | | /// @return OK on success; otherwise the close error from the underlying reader. |
71 | | Status close() override; |
72 | | |
73 | | /// Get the path of the underlying file. |
74 | | /// @return Reference to the remote reader path. |
75 | 7.20k | const Path& path() const override { return _remote_file_reader->path(); } |
76 | | |
77 | | /// Get the logical size of the underlying file. |
78 | | /// @return File size in bytes. |
79 | 2.15M | size_t size() const override { return _remote_file_reader->size(); } |
80 | | |
81 | | /// Check whether the underlying reader has been closed. |
82 | | /// @return true if the underlying reader is closed; otherwise false. |
83 | 718k | bool closed() const override { return _remote_file_reader->closed(); } |
84 | | |
85 | | /// Expose the wrapped remote reader. |
86 | | /// @return Raw pointer to the underlying reader owned by this object. |
87 | 999 | FileReader* get_remote_reader() { return _remote_file_reader.get(); } |
88 | | |
89 | | /// Align a read range to file-cache block boundaries. |
90 | | /// @param[in] offset Requested read offset in bytes. |
91 | | /// @param[in] size Requested read size in bytes. |
92 | | /// @param[in] length Total file length in bytes. |
93 | | /// @return Pair of aligned start offset and aligned size. |
94 | | static std::pair<size_t, size_t> s_align_size(size_t offset, size_t size, size_t length); |
95 | | |
96 | 0 | int64_t mtime() const override { return _remote_file_reader->mtime(); } |
97 | | |
98 | | // Asynchronously prefetch a range of file cache blocks. |
99 | | // This method triggers read file cache in dryrun mode to warm up the cache |
100 | | // without actually reading the data into user buffers. |
101 | | // |
102 | | // Parameters: |
103 | | // offset: Starting offset in the file |
104 | | // size: Number of bytes to prefetch |
105 | | // io_ctx: IO context (can be nullptr, will create a dryrun context internally) |
106 | | // |
107 | | // Note: This is a best-effort operation. Errors are logged but not returned. |
108 | | void prefetch_range(size_t offset, size_t size, const IOContext* io_ctx = nullptr); |
109 | | |
110 | | protected: |
111 | | /// Read bytes from cache when possible and fall back to peer/S3 when needed. |
112 | | /// @param[in] offset Start offset in the file. |
113 | | /// @param[out] result Destination buffer for the requested bytes. |
114 | | /// @param[out] bytes_read Number of bytes copied into result. |
115 | | /// @param[in] io_ctx IO context carrying dry-run, warmup and statistics options. |
116 | | /// @return OK on success; otherwise an error from cache lookup or remote read. |
117 | | Status read_at_impl(size_t offset, Slice result, size_t* bytes_read, |
118 | | const IOContext* io_ctx) override; |
119 | | |
120 | | private: |
121 | | enum class FileCacheReadType { |
122 | | DATA, |
123 | | INVERTED_INDEX, |
124 | | SEGMENT_FOOTER_INDEX, |
125 | | }; |
126 | | |
127 | | /// Initialize cache metadata for Doris-table files. |
128 | | /// @return None. |
129 | | void _init_doris_table_cache(); |
130 | | |
131 | | /// Initialize cache metadata for external-table files. |
132 | | /// @param[in] opts Reader options used to choose cache key and cache base path. |
133 | | /// @return None. |
134 | | void _init_external_table_cache(const FileReaderOptions& opts); |
135 | | |
136 | | /// Check whether this reader can read cache files directly without get_or_set. |
137 | | /// @return true when direct cache-file reads are enabled for Doris-table files. |
138 | | bool _can_read_cache_file_directly() const; |
139 | | |
140 | | /// Decide whether remote cache miss reads should try peer cache first. |
141 | | /// @param[in] io_ctx IO context for warmup and request-mode checks. |
142 | | /// @return true if peer read is enabled for this request; otherwise false. |
143 | | bool _should_read_from_peer(const IOContext* io_ctx) const; |
144 | | |
145 | | /// Register a downloaded block in the direct-read map owned by this reader. |
146 | | /// @param[in] file_block Downloaded cache block to insert. |
147 | | /// @return None. |
148 | | void _insert_file_reader(FileBlockSPtr file_block); |
149 | | |
150 | | /// Try to satisfy the request by reading already downloaded cache files directly. |
151 | | /// @param[in] offset Requested file offset. |
152 | | /// @param[out] result Destination buffer for the request. |
153 | | /// @param[in] bytes_req Requested byte count. |
154 | | /// @param[in] is_dryrun True if local cache IO should be skipped. |
155 | | /// @param[in,out] stats Read statistics updated during the attempt. |
156 | | /// @param[in,out] already_read Bytes already filled into result by direct cache reads. |
157 | | /// @param[out] bytes_read Total bytes read when the whole request is satisfied directly. |
158 | | /// @return true if the whole request is completed by direct cache-file reads; otherwise false. |
159 | | bool _try_read_from_cached_files_directly(size_t offset, Slice result, size_t bytes_req, |
160 | | bool is_dryrun, ReadStatistics& stats, |
161 | | SourceReadBreakdown& source_read_breakdown, |
162 | | size_t& already_read, size_t* bytes_read); |
163 | | |
164 | | /// Collect blocks that still need remote data and update cache-hit statistics. |
165 | | /// @param[in] holder Cache blocks covering the aligned request range. |
166 | | /// @param[in,out] stats Read statistics updated according to block states. |
167 | | /// @return Blocks that should be fetched from peer/S3 by the current reader. |
168 | | std::vector<FileBlockSPtr> _collect_remote_read_blocks(const FileBlocksHolder& holder, |
169 | | ReadStatistics& stats); |
170 | | |
171 | | /// Fetch missing blocks from peer/S3, write them into cache, and copy the overlap to result. |
172 | | /// @param[in] empty_blocks Blocks selected for remote fetch. |
173 | | /// @param[in] offset Original request offset. |
174 | | /// @param[in] bytes_req Original request size. |
175 | | /// @param[in] already_read Bytes already produced before this step. |
176 | | /// @param[out] result Destination buffer for the original request. |
177 | | /// @param[in] is_dryrun True if cache-file writes and local buffer copies should be skipped. |
178 | | /// @param[in,out] stats Read statistics updated for remote and local cache work. |
179 | | /// @param[in] io_ctx IO context passed to peer/S3 reads. |
180 | | /// @param[in,out] indirect_read_bytes Bytes copied into result through the indirect path. |
181 | | /// @param[out] empty_start Left boundary of the fetched contiguous empty range. |
182 | | /// @param[out] empty_end Right boundary of the fetched contiguous empty range. |
183 | | /// @param[out] peer_fetched_blocks Exact blocks fetched by peer in sparse mode; empty for S3. |
184 | | /// @return OK on success; otherwise an error from peer/S3 read. |
185 | | Status _read_remote_blocks_into_cache(const std::vector<FileBlockSPtr>& empty_blocks, |
186 | | size_t offset, size_t bytes_req, size_t already_read, |
187 | | Slice result, bool is_dryrun, ReadStatistics& stats, |
188 | | SourceReadBreakdown& source_read_breakdown, |
189 | | const IOContext* io_ctx, size_t& indirect_read_bytes, |
190 | | size_t& empty_start, size_t& empty_end, |
191 | | PeerFetchedBlockSet& peer_fetched_blocks); |
192 | | |
193 | | /// Read cached blocks that were not covered by the remote-fetch range, with remote fallback. |
194 | | /// @param[in] holder Cache blocks covering the aligned request range. |
195 | | /// @param[in] offset Original request offset. |
196 | | /// @param[in] bytes_req Original request size. |
197 | | /// @param[out] result Destination buffer for the original request. |
198 | | /// @param[in] is_dryrun True if local cache IO should be skipped. |
199 | | /// @param[in] empty_start Left boundary of the range already handled by remote fetch. |
200 | | /// @param[in] empty_end Right boundary of the range already handled by remote fetch. |
201 | | /// @param[in] peer_fetched_blocks Exact blocks already filled by peer; empty for S3 path. |
202 | | /// @param[in,out] stats Read statistics updated for wait, cache, and remote fallback paths. |
203 | | /// @param[in,out] indirect_read_bytes Bytes copied into result through this indirect stage. |
204 | | /// @param[out] bytes_read Total bytes covered for the original request after this stage. |
205 | | /// @return OK on success; otherwise an error from cache read or remote fallback read. |
206 | | Status _read_remaining_blocks_from_cache(const FileBlocksHolder& holder, size_t offset, |
207 | | size_t bytes_req, Slice result, bool is_dryrun, |
208 | | size_t empty_start, size_t empty_end, |
209 | | const PeerFetchedBlockSet& peer_fetched_blocks, |
210 | | ReadStatistics& stats, |
211 | | SourceReadBreakdown& source_read_breakdown, |
212 | | size_t& indirect_read_bytes, size_t* bytes_read, |
213 | | const IOContext* io_ctx); |
214 | | |
215 | | /// Read through the block-cache metadata path when direct cache-file reads are insufficient. |
216 | | /// @param[in] offset Original request offset. |
217 | | /// @param[out] result Destination buffer for the original request. |
218 | | /// @param[in] bytes_req Original request size. |
219 | | /// @param[in] already_read Bytes already produced by direct cache-file reads. |
220 | | /// @param[in] is_dryrun True if local cache IO should be skipped. |
221 | | /// @param[out] bytes_read Total bytes read for the request. |
222 | | /// @param[in,out] stats Read statistics updated across the indirect path. |
223 | | /// @param[in] io_ctx IO context passed to cache lookup and remote read. |
224 | | /// @return OK on success; otherwise an error from cache lookup or remote read. |
225 | | Status _read_from_indirect_cache(size_t offset, Slice result, size_t bytes_req, |
226 | | size_t already_read, bool is_dryrun, size_t* bytes_read, |
227 | | ReadStatistics& stats, |
228 | | SourceReadBreakdown& source_read_breakdown, |
229 | | const IOContext* io_ctx); |
230 | | |
231 | | /// Fall back to S3: clear peer_result, allocate buffer, and read from remote storage. |
232 | | /// @param[in] empty_start Start offset of the contiguous remote-read range. |
233 | | /// @param[in] span_size Size of the remote-read range in bytes. |
234 | | /// @param[in,out] buffer Span buffer that receives S3 data. |
235 | | /// @param[in,out] peer_result Cleared when non-null. |
236 | | /// @param[in,out] stats Read statistics updated for S3 execution. |
237 | | /// @param[in] io_ctx IO context passed to the remote reader. |
238 | | /// @return OK on success; otherwise the S3 read error. |
239 | | Status _execute_s3_fallback(size_t empty_start, size_t span_size, |
240 | | std::unique_ptr<char[]>& buffer, PeerFetchResult* peer_result, |
241 | | ReadStatistics& stats, const IOContext* io_ctx); |
242 | | |
243 | | /// Sequential peer-then-S3 fallback: try the best peer candidate, update affinity on |
244 | | /// success/failure, and fall back to S3 if peer fails. |
245 | | /// @param[in] empty_blocks Blocks whose data is missing from local cache. |
246 | | /// @param[in] empty_start Start offset of the contiguous remote-read range. |
247 | | /// @param[in] span_size Size of the remote-read range in bytes. |
248 | | /// @param[in,out] buffer Span buffer that receives S3 data on S3 fallback. |
249 | | /// @param[in,out] peer_result Peer payloads populated on peer success. |
250 | | /// @param[in,out] stats Read statistics updated for peer/S3 execution. |
251 | | /// @param[in] io_ctx IO context passed to the remote reader. |
252 | | /// @param[in] candidates Peer candidates sorted by affinity. |
253 | | /// @param[in] tablet_id Tablet ID for affinity tracking. |
254 | | /// @return OK on success; otherwise the S3 read error. |
255 | | Status _execute_sequential_peer_read(const std::vector<FileBlockSPtr>& empty_blocks, |
256 | | size_t empty_start, size_t span_size, |
257 | | std::unique_ptr<char[]>& buffer, |
258 | | PeerFetchResult* peer_result, ReadStatistics& stats, |
259 | | const IOContext* io_ctx, |
260 | | const std::vector<doris::PeerCandidate>& candidates, |
261 | | int64_t tablet_id); |
262 | | |
263 | | /// Execute a remote fetch for the contiguous empty range, trying peer first when enabled. |
264 | | /// @param[in] empty_blocks Blocks whose data is missing from local cache. |
265 | | /// @param[in] empty_start Start offset of the contiguous remote-read range for S3 fallback. |
266 | | /// @param[in] span_size Size of the enclosing contiguous remote-read range for S3 fallback. |
267 | | /// @param[in,out] buffer Temporary span buffer receiving S3 data. |
268 | | /// @param[in,out] peer_result Segmented peer payloads when the peer path succeeds. |
269 | | /// @param[in,out] stats Read statistics updated for peer/S3 execution. |
270 | | /// @param[in] io_ctx IO context passed to the remote reader. |
271 | | /// @return OK on success; otherwise the peer/S3 read error. |
272 | | Status _execute_remote_read(const std::vector<FileBlockSPtr>& empty_blocks, size_t empty_start, |
273 | | size_t span_size, std::unique_ptr<char[]>& buffer, |
274 | | PeerFetchResult* peer_result, ReadStatistics& stats, |
275 | | const IOContext* io_ctx); |
276 | | |
277 | | /// Execute a winner race between peer read and S3 read for cross compute group scenarios. |
278 | | /// Launches both peer and S3 reads concurrently in bthreads and returns the first successful |
279 | | /// result. Uses bthread::Mutex and bthread::ConditionVariable for synchronization. |
280 | | /// @param[in] empty_blocks Blocks whose data is missing from local cache. |
281 | | /// @param[in] empty_start Start offset of the contiguous remote-read range. |
282 | | /// @param[in] span_size Size of the contiguous range for the S3 fallback read. |
283 | | /// @param[in,out] buffer Temporary span buffer that receives S3 data on S3 win. |
284 | | /// @param[out] peer_result Peer fetch payloads populated when peer wins. |
285 | | /// @param[in,out] stats Read statistics updated for the winning path. |
286 | | /// @param[in] io_ctx IO context passed to both peer and S3 reads. |
287 | | /// @param[in] candidates All peer candidates for the tablet. |
288 | | /// @return OK on success with buffer or peer_result populated; otherwise an error. |
289 | | Status _execute_winner_race(const std::vector<FileBlockSPtr>& empty_blocks, size_t empty_start, |
290 | | size_t span_size, std::unique_ptr<char[]>& buffer, |
291 | | PeerFetchResult* peer_result, ReadStatistics& stats, |
292 | | const IOContext* io_ctx, |
293 | | const std::vector<doris::PeerCandidate>& candidates, |
294 | | int64_t tablet_id); |
295 | | |
296 | | /// Merge per-read statistics into the external file-cache statistics accumulator. |
297 | | /// @param[in] stats Statistics produced by the current read. |
298 | | /// @param[in,out] state Destination statistics accumulator; ignored when null. |
299 | | /// @param[in] read_type Logical file-cache read type used for fine-grained profile counters. |
300 | | /// @return None. |
301 | | void _update_stats(const ReadStatistics& stats, |
302 | | const SourceReadBreakdown& source_read_breakdown, FileCacheStatistics* state, |
303 | | FileCacheReadType read_type) const; |
304 | | |
305 | | bool _is_doris_table = false; |
306 | | int64_t _tablet_id = -1; |
307 | | std::string _storage_resource_id; |
308 | | FileReaderSPtr _remote_file_reader; |
309 | | UInt128Wrapper _cache_hash; |
310 | | BlockFileCache* _cache = nullptr; |
311 | | std::shared_mutex _mtx; |
312 | | std::map<size_t, FileBlockSPtr> _cache_file_readers; |
313 | | }; |
314 | | |
315 | | } // namespace doris::io |