Coverage Report

Created: 2026-07-17 18:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/io/fs/buffered_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 <stddef.h>
21
#include <stdint.h>
22
23
#include <condition_variable>
24
#include <memory>
25
#include <mutex>
26
#include <string>
27
#include <utility>
28
#include <vector>
29
30
#include "common/status.h"
31
#include "core/custom_allocator.h"
32
#include "core/pod_array.h"
33
#include "core/typeid_cast.h"
34
#include "io/cache/cached_remote_file_reader.h"
35
#include "io/file_factory.h"
36
#include "io/fs/broker_file_reader.h"
37
#include "io/fs/file_reader.h"
38
#include "io/fs/path.h"
39
#include "io/fs/s3_file_reader.h"
40
#include "io/io_common.h"
41
#include "runtime/file_scan_profile.h"
42
#include "runtime/runtime_profile.h"
43
#include "storage/olap_define.h"
44
#include "util/slice.h"
45
namespace doris {
46
47
namespace io {
48
49
class FileSystem;
50
51
struct PrefetchRange {
52
    size_t start_offset;
53
    size_t end_offset;
54
55
    PrefetchRange(size_t start_offset, size_t end_offset)
56
1.04M
            : start_offset(start_offset), end_offset(end_offset) {}
57
58
6
    PrefetchRange() : start_offset(0), end_offset(0) {}
59
60
    bool operator==(const PrefetchRange& other) const {
61
        return (start_offset == other.start_offset) && (end_offset == other.end_offset);
62
    }
63
64
0
    bool operator!=(const PrefetchRange& other) const { return !(*this == other); }
65
66
0
    PrefetchRange span(const PrefetchRange& other) const {
67
0
        return {std::min(start_offset, other.end_offset), std::max(start_offset, other.end_offset)};
68
0
    }
69
363k
    PrefetchRange seq_span(const PrefetchRange& other) const {
70
363k
        return {start_offset, other.end_offset};
71
363k
    }
72
73
    //Ranges needs to be sorted.
74
    static std::vector<PrefetchRange> merge_adjacent_seq_ranges(
75
            const std::vector<PrefetchRange>& seq_ranges, int64_t max_merge_distance_bytes,
76
49.7k
            int64_t once_max_read_bytes) {
77
49.7k
        if (seq_ranges.empty()) {
78
2.82k
            return {};
79
2.82k
        }
80
        // Merge overlapping ranges
81
46.9k
        std::vector<PrefetchRange> result;
82
46.9k
        PrefetchRange last = seq_ranges.front();
83
410k
        for (size_t i = 1; i < seq_ranges.size(); ++i) {
84
363k
            PrefetchRange current = seq_ranges[i];
85
363k
            PrefetchRange merged = last.seq_span(current);
86
363k
            if (merged.end_offset <= once_max_read_bytes + merged.start_offset &&
87
363k
                last.end_offset + max_merge_distance_bytes >= current.start_offset) {
88
351k
                last = merged;
89
351k
            } else {
90
12.2k
                result.push_back(last);
91
12.2k
                last = current;
92
12.2k
            }
93
363k
        }
94
46.9k
        result.push_back(last);
95
46.9k
        return result;
96
49.7k
    }
97
};
98
99
class RangeFinder {
100
public:
101
9.63k
    virtual ~RangeFinder() = default;
102
    virtual Status get_range_for(int64_t desired_offset, io::PrefetchRange& result_range) = 0;
103
    virtual size_t get_max_range_size() const = 0;
104
};
105
106
class LinearProbeRangeFinder : public RangeFinder {
107
public:
108
9.60k
    LinearProbeRangeFinder(std::vector<io::PrefetchRange>&& ranges) : _ranges(std::move(ranges)) {}
109
110
    Status get_range_for(int64_t desired_offset, io::PrefetchRange& result_range) override;
111
112
9.60k
    size_t get_max_range_size() const override {
113
9.60k
        size_t max_range_size = 0;
114
9.60k
        for (const auto& range : _ranges) {
115
6.77k
            max_range_size = std::max(max_range_size, range.end_offset - range.start_offset);
116
6.77k
        }
117
9.60k
        return max_range_size;
118
9.60k
    }
119
120
9.63k
    ~LinearProbeRangeFinder() override = default;
121
122
private:
123
    std::vector<io::PrefetchRange> _ranges;
124
    size_t index {0};
125
};
126
127
/**
128
 * The reader provides a solution to read one range at a time. You can customize RangeFinder to meet your scenario.
129
 * For me, since there will be tiny stripes when reading orc files, in order to reduce the requests to hdfs,
130
 * I first merge the access to the orc files to be read (of course there is a problem of read amplification,
131
 * but in my scenario, compared with reading hdfs multiple times, it is faster to read more data on hdfs at one time),
132
 * and then because the actual reading of orc files is in order from front to back, I provide LinearProbeRangeFinder.
133
 */
134
class RangeCacheFileReader : public io::FileReader {
135
    struct RangeCacheReaderStatistics {
136
        int64_t request_io = 0;
137
        int64_t request_bytes = 0;
138
        int64_t request_time = 0;
139
        int64_t read_to_cache_time = 0;
140
        int64_t cache_refresh_count = 0;
141
        int64_t read_to_cache_bytes = 0;
142
    };
143
144
public:
145
    RangeCacheFileReader(RuntimeProfile* profile, io::FileReaderSPtr inner_reader,
146
                         std::shared_ptr<RangeFinder> range_finder);
147
148
9.61k
    ~RangeCacheFileReader() override = default;
149
150
3
    Status close() override {
151
3
        if (!_closed) {
152
3
            _closed = true;
153
3
        }
154
3
        return Status::OK();
155
3
    }
156
157
0
    const io::Path& path() const override { return _inner_reader->path(); }
158
159
0
    size_t size() const override { return _size; }
160
161
0
    bool closed() const override { return _closed; }
162
163
0
    int64_t mtime() const override { return _inner_reader->mtime(); }
164
165
protected:
166
    Status read_at_impl(size_t offset, Slice result, size_t* bytes_read,
167
                        const IOContext* io_ctx) override;
168
169
    void _collect_profile_before_close() override;
170
171
private:
172
    RuntimeProfile* _profile = nullptr;
173
    io::FileReaderSPtr _inner_reader;
174
    std::shared_ptr<RangeFinder> _range_finder;
175
176
    OwnedSlice _cache;
177
    int64_t _current_start_offset = -1;
178
179
    size_t _size;
180
    bool _closed = false;
181
182
    RuntimeProfile::Counter* _request_io = nullptr;
183
    RuntimeProfile::Counter* _total_time = nullptr;
184
    RuntimeProfile::Counter* _request_bytes = nullptr;
185
    RuntimeProfile::Counter* _request_time = nullptr;
186
    RuntimeProfile::Counter* _read_to_cache_time = nullptr;
187
    RuntimeProfile::Counter* _cache_refresh_count = nullptr;
188
    RuntimeProfile::Counter* _read_to_cache_bytes = nullptr;
189
    RangeCacheReaderStatistics _cache_statistics;
190
    /**
191
     * `RangeCacheFileReader`:
192
     *   1. `CacheRefreshCount`: how many IOs are merged
193
     *   2. `ReadToCacheBytes`: how much data is actually read after merging
194
     *   3. `ReadToCacheTime`: how long it takes to read data after merging
195
     *   4. `RequestBytes`: how many bytes does the apache-orc library actually need to read the orc file
196
     *   5. `RequestIO`: how many times the apache-orc library calls this read interface
197
     *   6. `RequestTime`: how long it takes the apache-orc library to call this read interface
198
     *
199
     *   It should be noted that `RangeCacheFileReader` is a wrapper of the reader that actually reads data,such as
200
     *   the hdfs reader, so strictly speaking, `CacheRefreshCount` is not equal to how many IOs are initiated to hdfs,
201
     *  because each time the hdfs reader is requested, the hdfs reader may not be able to read all the data at once.
202
     */
203
};
204
205
/**
206
 * A FileReader that efficiently supports random access format like parquet and orc.
207
 * In order to merge small IO in parquet and orc, the random access ranges should be generated
208
 * when creating the reader. The random access ranges is a list of ranges that order by offset.
209
 * The range in random access ranges should be reading sequentially, can be skipped, but can't be
210
 * read repeatedly. When calling read_at, if the start offset located in random access ranges, the
211
 * slice size should not span two ranges.
212
 *
213
 * For example, in parquet, the random access ranges is the column offsets in a row group.
214
 *
215
 * When reading at offset, if [offset, offset + 8MB) contains many random access ranges, the reader
216
 * will read data in [offset, offset + 8MB) as a whole, and copy the data in random access ranges
217
 * into small buffers(name as box, default 1MB, 128MB in total). A box can be occupied by many ranges,
218
 * and use a reference counter to record how many ranges are cached in the box. If reference counter
219
 * equals zero, the box can be release or reused by other ranges. When there is no empty box for a new
220
 * read operation, the read operation will do directly.
221
 */
222
class MergeRangeFileReader : public io::FileReader {
223
public:
224
    struct Statistics {
225
        int64_t copy_time = 0;
226
        int64_t read_time = 0;
227
        int64_t request_io = 0;
228
        int64_t merged_io = 0;
229
        int64_t request_bytes = 0;
230
        int64_t merged_bytes = 0;
231
    };
232
233
    struct RangeCachedData {
234
        size_t start_offset;
235
        size_t end_offset;
236
        std::vector<int16_t> ref_box;
237
        std::vector<uint32_t> box_start_offset;
238
        std::vector<uint32_t> box_end_offset;
239
        bool has_read = false;
240
241
        RangeCachedData(size_t start_offset, size_t end_offset)
242
0
                : start_offset(start_offset), end_offset(end_offset) {}
243
244
145k
        RangeCachedData() : start_offset(0), end_offset(0) {}
245
246
305k
        bool empty() const { return start_offset == end_offset; }
247
248
1.03M
        bool contains(size_t offset) const { return start_offset <= offset && offset < end_offset; }
249
250
268k
        void reset() {
251
268k
            start_offset = 0;
252
268k
            end_offset = 0;
253
268k
            ref_box.clear();
254
268k
            box_start_offset.clear();
255
268k
            box_end_offset.clear();
256
268k
        }
257
258
0
        int16_t release_last_box() {
259
0
            // we can only release the last referenced box to ensure sequential read in range
260
0
            if (!empty()) {
261
0
                int16_t last_box_ref = ref_box.back();
262
0
                uint32_t released_size = box_end_offset.back() - box_start_offset.back();
263
0
                ref_box.pop_back();
264
0
                box_start_offset.pop_back();
265
0
                box_end_offset.pop_back();
266
0
                end_offset -= released_size;
267
0
                if (empty()) {
268
0
                    reset();
269
0
                }
270
0
                return last_box_ref;
271
0
            }
272
0
            return -1;
273
0
        }
274
    };
275
276
    static constexpr size_t TOTAL_BUFFER_SIZE = 128 * 1024 * 1024;  // 128MB
277
    static constexpr size_t READ_SLICE_SIZE = 8 * 1024 * 1024;      // 8MB
278
    static constexpr size_t BOX_SIZE = 1 * 1024 * 1024;             // 1MB
279
    static constexpr size_t SMALL_IO = 2 * 1024 * 1024;             // 2MB
280
    static constexpr size_t NUM_BOX = TOTAL_BUFFER_SIZE / BOX_SIZE; // 128
281
282
    MergeRangeFileReader(RuntimeProfile* profile, io::FileReaderSPtr reader,
283
                         const std::vector<PrefetchRange>& random_access_ranges,
284
                         int64_t merge_read_slice_size = READ_SLICE_SIZE)
285
33.1k
            : _profile(profile),
286
33.1k
              _reader(std::move(reader)),
287
33.1k
              _random_access_ranges(random_access_ranges) {
288
33.1k
        _range_cached_data.resize(random_access_ranges.size());
289
33.1k
        _size = _reader->size();
290
33.1k
        _remaining = TOTAL_BUFFER_SIZE;
291
33.1k
        _is_oss = typeid_cast<io::S3FileReader*>(_reader.get()) != nullptr;
292
33.1k
        _max_amplified_ratio = config::max_amplified_read_ratio;
293
        // Equivalent min size of each IO that can reach the maximum storage speed limit:
294
        // 1MB for oss, 8KB for hdfs
295
33.1k
        _equivalent_io_size =
296
33.1k
                _is_oss ? config::merged_oss_min_io_size : config::merged_hdfs_min_io_size;
297
298
33.1k
        _merged_read_slice_size = merge_read_slice_size;
299
300
33.1k
        if (_merged_read_slice_size < 0) {
301
12
            _merged_read_slice_size = READ_SLICE_SIZE;
302
12
        }
303
304
33.1k
        if (_profile != nullptr) {
305
32.9k
            const char* random_profile = "MergedSmallIO";
306
32.9k
            _total_time = ADD_CHILD_TIMER_WITH_LEVEL(
307
32.9k
                    _profile, random_profile,
308
32.9k
                    file_scan_profile::parent_or_root(_profile, file_scan_profile::IO), 1);
309
32.9k
            _copy_time = ADD_CHILD_TIMER_WITH_LEVEL(_profile, "CopyTime", random_profile, 1);
310
32.9k
            _read_time = ADD_CHILD_TIMER_WITH_LEVEL(_profile, "ReadTime", random_profile, 1);
311
32.9k
            _request_io = ADD_CHILD_COUNTER_WITH_LEVEL(_profile, "RequestIO", TUnit::UNIT,
312
32.9k
                                                       random_profile, 1);
313
32.9k
            _merged_io = ADD_CHILD_COUNTER_WITH_LEVEL(_profile, "MergedIO", TUnit::UNIT,
314
32.9k
                                                      random_profile, 1);
315
32.9k
            _request_bytes = ADD_CHILD_COUNTER_WITH_LEVEL(_profile, "RequestBytes", TUnit::BYTES,
316
32.9k
                                                          random_profile, 1);
317
32.9k
            _merged_bytes = ADD_CHILD_COUNTER_WITH_LEVEL(_profile, "MergedBytes", TUnit::BYTES,
318
32.9k
                                                         random_profile, 1);
319
32.9k
        }
320
33.1k
    }
321
322
33.1k
    ~MergeRangeFileReader() override = default;
323
324
0
    Status close() override {
325
0
        if (!_closed) {
326
0
            _closed = true;
327
0
        }
328
0
        return Status::OK();
329
0
    }
330
331
14.2k
    const io::Path& path() const override { return _reader->path(); }
332
333
131k
    size_t size() const override { return _size; }
334
335
0
    bool closed() const override { return _closed; }
336
337
14.2k
    int64_t mtime() const override { return _reader->mtime(); }
338
339
    // for test only
340
    size_t buffer_remaining() const { return _remaining; }
341
342
    // for test only
343
    const std::vector<RangeCachedData>& range_cached_data() const { return _range_cached_data; }
344
345
    // for test only
346
    const std::vector<int16_t>& box_reference() const { return _box_ref; }
347
348
    // for test only
349
    const Statistics& statistics() const { return _statistics; }
350
351
protected:
352
    Status read_at_impl(size_t offset, Slice result, size_t* bytes_read,
353
                        const IOContext* io_ctx) override;
354
355
33.1k
    void _collect_profile_before_close() override {
356
33.1k
        if (_profile != nullptr) {
357
33.0k
            COUNTER_UPDATE(_total_time, _statistics.copy_time + _statistics.read_time);
358
33.0k
            COUNTER_UPDATE(_copy_time, _statistics.copy_time);
359
33.0k
            COUNTER_UPDATE(_read_time, _statistics.read_time);
360
33.0k
            COUNTER_UPDATE(_request_io, _statistics.request_io);
361
33.0k
            COUNTER_UPDATE(_merged_io, _statistics.merged_io);
362
33.0k
            COUNTER_UPDATE(_request_bytes, _statistics.request_bytes);
363
33.0k
            COUNTER_UPDATE(_merged_bytes, _statistics.merged_bytes);
364
33.1k
            if (_reader != nullptr) {
365
33.1k
                _reader->collect_profile_before_close();
366
33.1k
            }
367
33.0k
        }
368
33.1k
    }
369
370
private:
371
    RuntimeProfile::Counter* _total_time = nullptr;
372
    RuntimeProfile::Counter* _copy_time = nullptr;
373
    RuntimeProfile::Counter* _read_time = nullptr;
374
    RuntimeProfile::Counter* _request_io = nullptr;
375
    RuntimeProfile::Counter* _merged_io = nullptr;
376
    RuntimeProfile::Counter* _request_bytes = nullptr;
377
    RuntimeProfile::Counter* _merged_bytes = nullptr;
378
379
    int _search_read_range(size_t start_offset, size_t end_offset);
380
    void _clean_cached_data(RangeCachedData& cached_data);
381
    void _read_in_box(RangeCachedData& cached_data, size_t offset, Slice result,
382
                      size_t* bytes_read);
383
    Status _fill_box(int range_index, size_t start_offset, size_t to_read, size_t* bytes_read,
384
                     const IOContext* io_ctx);
385
    void _dec_box_ref(int16_t box_index);
386
387
    RuntimeProfile* _profile = nullptr;
388
    io::FileReaderSPtr _reader;
389
    const std::vector<PrefetchRange> _random_access_ranges;
390
    std::vector<RangeCachedData> _range_cached_data;
391
    size_t _size;
392
    bool _closed = false;
393
    size_t _remaining;
394
395
    std::unique_ptr<OwnedSlice> _read_slice;
396
    std::vector<OwnedSlice> _boxes;
397
    int16_t _last_box_ref = -1;
398
    uint32_t _last_box_usage = 0;
399
    std::vector<int16_t> _box_ref;
400
    bool _is_oss;
401
    double _max_amplified_ratio;
402
    size_t _equivalent_io_size;
403
    int64_t _merged_read_slice_size;
404
405
    Statistics _statistics;
406
};
407
408
/**
409
 * Create a file reader suitable for accessing scenarios:
410
 * 1. When file size < config::in_memory_file_size, create InMemoryFileReader file reader
411
 * 2. When reading sequential file(csv/json), create PrefetchBufferedReader
412
 * 3. When reading random access file(parquet/orc), create normal file reader
413
 */
414
class DelegateReader {
415
public:
416
    enum AccessMode { SEQUENTIAL, RANDOM };
417
418
    static Result<io::FileReaderSPtr> create_file_reader(
419
            RuntimeProfile* profile, const FileSystemProperties& system_properties,
420
            const FileDescription& file_description, const io::FileReaderOptions& reader_options,
421
            AccessMode access_mode = SEQUENTIAL, const IOContext* io_ctx = nullptr,
422
            const PrefetchRange file_range = PrefetchRange(0, 0));
423
424
    static Result<io::FileReaderSPtr> create_file_reader(
425
            RuntimeProfile* profile, const FileSystemProperties& system_properties,
426
            const FileDescription& file_description, const io::FileReaderOptions& reader_options,
427
            AccessMode access_mode, std::shared_ptr<const IOContext> io_ctx,
428
            const PrefetchRange file_range = PrefetchRange(0, 0));
429
};
430
431
class PrefetchBufferedReader;
432
struct PrefetchBuffer : std::enable_shared_from_this<PrefetchBuffer>, public ProfileCollector {
433
    enum class BufferStatus { RESET, PENDING, PREFETCHED, CLOSED };
434
435
    PrefetchBuffer(const PrefetchRange file_range, size_t buffer_size, size_t whole_buffer_size,
436
                   io::FileReaderSPtr reader, std::shared_ptr<const IOContext> io_ctx,
437
                   std::function<void(PrefetchBuffer&)> sync_profile)
438
212
            : _file_range(file_range),
439
212
              _size(buffer_size),
440
212
              _whole_buffer_size(whole_buffer_size),
441
212
              _reader(std::move(reader)),
442
212
              _io_ctx_holder(std::move(io_ctx)),
443
212
              _io_ctx(_io_ctx_holder.get()),
444
212
              _sync_profile(std::move(sync_profile)) {}
445
446
    PrefetchBuffer(PrefetchBuffer&& other)
447
            : _offset(other._offset),
448
              _file_range(other._file_range),
449
              _random_access_ranges(other._random_access_ranges),
450
              _size(other._size),
451
              _whole_buffer_size(other._whole_buffer_size),
452
              _reader(std::move(other._reader)),
453
              _io_ctx_holder(std::move(other._io_ctx_holder)),
454
              _io_ctx(_io_ctx_holder.get()),
455
              _buf(std::move(other._buf)),
456
0
              _sync_profile(std::move(other._sync_profile)) {}
457
458
212
    ~PrefetchBuffer() = default;
459
460
    size_t _offset {0};
461
    // [start_offset, end_offset) is the range that can be prefetched.
462
    // Notice that the reader can read out of [start_offset, end_offset), because FE does not align the file
463
    // according to the format when splitting it.
464
    const PrefetchRange _file_range;
465
    const std::vector<PrefetchRange>* _random_access_ranges = nullptr;
466
    size_t _size {0};
467
    size_t _len {0};
468
    size_t _whole_buffer_size;
469
    io::FileReaderSPtr _reader;
470
    std::shared_ptr<const IOContext> _io_ctx_holder;
471
    const IOContext* _io_ctx = nullptr;
472
    PODArray<char> _buf;
473
    BufferStatus _buffer_status {BufferStatus::RESET};
474
    std::mutex _lock;
475
    std::condition_variable _prefetched;
476
    Status _prefetch_status {Status::OK()};
477
    std::atomic_bool _exceed = false;
478
    std::function<void(PrefetchBuffer&)> _sync_profile;
479
    struct Statistics {
480
        int64_t copy_time {0};
481
        int64_t read_time {0};
482
        int64_t prefetch_request_io {0};
483
        int64_t prefetch_request_bytes {0};
484
        int64_t request_io {0};
485
        int64_t request_bytes {0};
486
    };
487
    Statistics _statis;
488
489
    // @brief: reset the start offset of this buffer to offset
490
    // @param: the new start offset for this buffer
491
    void reset_offset(size_t offset);
492
    // @brief: start to fetch the content between [_offset, _offset + _size)
493
    void prefetch_buffer();
494
    // @brief: used by BufferedReader to read the prefetched data
495
    // @param[off] read start address
496
    // @param[buf] buffer to put the actual content
497
    // @param[buf_len] maximum len trying to read
498
    // @param[bytes_read] actual bytes read
499
    Status read_buffer(size_t off, const char* buf, size_t buf_len, size_t* bytes_read);
500
    // @brief: shut down the buffer until the prior prefetching task is done
501
    void close();
502
    // @brief: to detect whether this buffer contains off
503
    // @param[off] detect offset
504
139
    bool inline contains(size_t off) const { return _offset <= off && off < _offset + _size; }
505
506
0
    void set_random_access_ranges(const std::vector<PrefetchRange>* random_access_ranges) {
507
0
        _random_access_ranges = random_access_ranges;
508
0
    }
509
510
    // binary search the last prefetch buffer that larger or include the offset
511
    int search_read_range(size_t off) const;
512
513
    size_t merge_small_ranges(size_t off, int range_index) const;
514
515
0
    void _collect_profile_at_runtime() override {}
516
517
    void _collect_profile_before_close() override;
518
};
519
520
constexpr int64_t s_max_pre_buffer_size = 4 * 1024 * 1024; // 4MB
521
522
/**
523
 * A buffered reader that prefetch data in the daemon thread pool.
524
 *
525
 * file_range is the range that the file is read.
526
 * random_access_ranges are the column ranges in format, like orc and parquet.
527
 *
528
 * When random_access_ranges is empty:
529
 * The data is prefetched sequentially until the underlying buffers(4 * 4M as default) are full.
530
 * When a buffer is read out, it will fetch data backward in daemon, so the underlying reader should be
531
 * thread-safe, and the access mode of data needs to be sequential.
532
 *
533
 * When random_access_ranges is not empty:
534
 * The data is prefetched order by the random_access_ranges. If some adjacent ranges is small, the underlying reader
535
 * will merge them.
536
 */
537
class PrefetchBufferedReader final : public io::FileReader {
538
public:
539
    PrefetchBufferedReader(RuntimeProfile* profile, io::FileReaderSPtr reader,
540
                           PrefetchRange file_range,
541
                           std::shared_ptr<const IOContext> io_ctx = nullptr,
542
                           int64_t buffer_size = -1L);
543
    ~PrefetchBufferedReader() override;
544
545
    Status close() override;
546
547
0
    const io::Path& path() const override { return _reader->path(); }
548
549
308
    size_t size() const override { return _size; }
550
551
0
    bool closed() const override { return _closed; }
552
553
0
    int64_t mtime() const override { return _reader->mtime(); }
554
555
0
    void set_random_access_ranges(const std::vector<PrefetchRange>* random_access_ranges) {
556
0
        _random_access_ranges = random_access_ranges;
557
0
        for (auto& _pre_buffer : _pre_buffers) {
558
0
            _pre_buffer->set_random_access_ranges(random_access_ranges);
559
0
        }
560
0
    }
561
562
protected:
563
    Status read_at_impl(size_t offset, Slice result, size_t* bytes_read,
564
                        const IOContext* io_ctx) override;
565
566
    void _collect_profile_before_close() override;
567
568
private:
569
    Status _close_internal();
570
352
    size_t get_buffer_pos(int64_t position) const {
571
352
        return (position % _whole_pre_buffer_size) / s_max_pre_buffer_size;
572
352
    }
573
212
    size_t get_buffer_offset(int64_t position) const {
574
212
        return (position / s_max_pre_buffer_size) * s_max_pre_buffer_size;
575
212
    }
576
53
    void reset_all_buffer(size_t position) {
577
265
        for (int64_t i = 0; i < _pre_buffers.size(); i++) {
578
212
            int64_t cur_pos = position + i * s_max_pre_buffer_size;
579
212
            size_t cur_buf_pos = get_buffer_pos(cur_pos);
580
            // reset would do all the prefetch work
581
212
            _pre_buffers[cur_buf_pos]->reset_offset(get_buffer_offset(cur_pos));
582
212
        }
583
53
    }
584
585
    io::FileReaderSPtr _reader;
586
    PrefetchRange _file_range;
587
    const std::vector<PrefetchRange>* _random_access_ranges = nullptr;
588
    std::shared_ptr<const IOContext> _io_ctx_holder;
589
    const IOContext* _io_ctx = nullptr;
590
    std::vector<std::shared_ptr<PrefetchBuffer>> _pre_buffers;
591
    int64_t _whole_pre_buffer_size;
592
    bool _initialized = false;
593
    bool _closed = false;
594
    size_t _size;
595
};
596
597
/**
598
 * A file reader that read the whole file into memory.
599
 * When a file is small(<8MB), InMemoryFileReader can effectively reduce the number of file accesses
600
 * and greatly improve the access speed of small files.
601
 */
602
class InMemoryFileReader final : public io::FileReader {
603
public:
604
    InMemoryFileReader(io::FileReaderSPtr reader);
605
606
    ~InMemoryFileReader() override;
607
608
    Status close() override;
609
610
75.1k
    const io::Path& path() const override { return _reader->path(); }
611
612
111k
    size_t size() const override { return _size; }
613
614
6
    bool closed() const override { return _closed; }
615
616
30.7k
    int64_t mtime() const override { return _reader->mtime(); }
617
618
protected:
619
    Status read_at_impl(size_t offset, Slice result, size_t* bytes_read,
620
                        const IOContext* io_ctx) override;
621
622
    void _collect_profile_before_close() override;
623
624
private:
625
    Status _close_internal();
626
    io::FileReaderSPtr _reader;
627
    std::unique_ptr<char[]> _data;
628
    size_t _size;
629
    bool _closed = false;
630
};
631
632
/**
633
 * Load all the needed data in underlying buffer, so the caller does not need to prepare the data container.
634
 */
635
class BufferedStreamReader {
636
public:
637
    /**
638
     * Return the address of underlying buffer that locates the start of data between [offset, offset + bytes_to_read)
639
     * @param buf the buffer address to save the start address of data
640
     * @param offset start offset ot read in stream
641
     * @param bytes_to_read bytes to read
642
     */
643
    virtual Status read_bytes(const uint8_t** buf, uint64_t offset, const size_t bytes_to_read,
644
                              const IOContext* io_ctx) = 0;
645
    /**
646
     * Save the data address to slice.data, and the slice.size is the bytes to read.
647
     */
648
    virtual Status read_bytes(Slice& slice, uint64_t offset, const IOContext* io_ctx) = 0;
649
156k
    virtual ~BufferedStreamReader() = default;
650
    // return the file path
651
    virtual std::string path() = 0;
652
653
    virtual int64_t mtime() const = 0;
654
};
655
656
class BufferedFileStreamReader : public BufferedStreamReader, public ProfileCollector {
657
public:
658
    BufferedFileStreamReader(io::FileReaderSPtr file, uint64_t offset, uint64_t length,
659
                             size_t max_buf_size);
660
156k
    ~BufferedFileStreamReader() override = default;
661
662
    Status read_bytes(const uint8_t** buf, uint64_t offset, const size_t bytes_to_read,
663
                      const IOContext* io_ctx) override;
664
    Status read_bytes(Slice& slice, uint64_t offset, const IOContext* io_ctx) override;
665
24.4k
    std::string path() override { return _file->path(); }
666
667
24.4k
    int64_t mtime() const override { return _file->mtime(); }
668
669
protected:
670
0
    void _collect_profile_before_close() override {
671
0
        if (_file != nullptr) {
672
0
            _file->collect_profile_before_close();
673
0
        }
674
0
    }
675
676
private:
677
    DorisUniqueBufferPtr<uint8_t> _buf;
678
    io::FileReaderSPtr _file;
679
    uint64_t _file_start_offset;
680
    uint64_t _file_end_offset;
681
682
    uint64_t _buf_start_offset = 0;
683
    uint64_t _buf_end_offset = 0;
684
    size_t _buf_size = 0;
685
    size_t _max_buf_size;
686
};
687
688
} // namespace io
689
690
} // namespace doris