Coverage Report

Created: 2026-07-06 17:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
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