Coverage Report

Created: 2026-10-24 10:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/format/parquet/vparquet_page_reader.cpp
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
#include "format/parquet/vparquet_page_reader.h"
19
20
#include <fmt/format.h>
21
#include <gen_cpp/parquet_types.h>
22
#include <stddef.h>
23
#include <stdint.h>
24
25
#include <algorithm>
26
27
#include "common/compiler_util.h" // IWYU pragma: keep
28
#include "common/config.h"
29
#include "format/parquet/parquet_common.h"
30
#include "io/fs/buffered_reader.h"
31
#include "runtime/runtime_profile.h"
32
#include "storage/cache/page_cache.h"
33
#include "util/slice.h"
34
#include "util/thrift_util.h"
35
36
namespace doris {
37
namespace io {
38
struct IOContext;
39
} // namespace io
40
} // namespace doris
41
42
namespace doris {
43
static constexpr size_t INIT_PAGE_HEADER_SIZE = 128;
44
45
286
void ParquetPageCacheKeyBuilder::init(const std::string& path, int64_t mtime) {
46
286
    _file_key_prefix = fmt::format("{}::{}", path, mtime);
47
286
}
48
49
template <bool IN_COLLECTION, bool OFFSET_INDEX>
50
PageReader<IN_COLLECTION, OFFSET_INDEX>::PageReader(io::BufferedStreamReader* reader,
51
                                                    io::IOContext* io_ctx, uint64_t offset,
52
                                                    uint64_t length, size_t total_rows,
53
                                                    const tparquet::ColumnMetaData& metadata,
54
                                                    const ParquetPageReadContext& page_read_ctx,
55
                                                    const tparquet::OffsetIndex* offset_index)
56
286
        : _reader(reader),
57
286
          _io_ctx(io_ctx),
58
286
          _offset(offset),
59
286
          _start_offset(offset),
60
286
          _end_offset(offset + length),
61
286
          _total_rows(total_rows),
62
286
          _metadata(metadata),
63
286
          _page_read_ctx(page_read_ctx),
64
286
          _offset_index(offset_index) {
65
286
    _next_header_offset = _offset;
66
286
    _state = INITIALIZED;
67
286
    _page_cache_key_builder.init(_reader->path(), _reader->mtime());
68
69
286
    if constexpr (OFFSET_INDEX) {
70
6
        _end_row = _offset_index->page_locations.size() >= 2
71
6
                           ? _offset_index->page_locations[1].first_row_index
72
6
                           : _total_rows;
73
6
    }
74
286
}
Unexecuted instantiation: _ZN5doris10PageReaderILb1ELb1EEC2EPNS_2io20BufferedStreamReaderEPNS2_9IOContextEmmmRKN8tparquet14ColumnMetaDataERKNS_22ParquetPageReadContextEPKNS7_11OffsetIndexE
_ZN5doris10PageReaderILb1ELb0EEC2EPNS_2io20BufferedStreamReaderEPNS2_9IOContextEmmmRKN8tparquet14ColumnMetaDataERKNS_22ParquetPageReadContextEPKNS7_11OffsetIndexE
Line
Count
Source
56
4
        : _reader(reader),
57
4
          _io_ctx(io_ctx),
58
4
          _offset(offset),
59
4
          _start_offset(offset),
60
4
          _end_offset(offset + length),
61
4
          _total_rows(total_rows),
62
4
          _metadata(metadata),
63
4
          _page_read_ctx(page_read_ctx),
64
4
          _offset_index(offset_index) {
65
4
    _next_header_offset = _offset;
66
4
    _state = INITIALIZED;
67
4
    _page_cache_key_builder.init(_reader->path(), _reader->mtime());
68
69
    if constexpr (OFFSET_INDEX) {
70
        _end_row = _offset_index->page_locations.size() >= 2
71
                           ? _offset_index->page_locations[1].first_row_index
72
                           : _total_rows;
73
    }
74
4
}
_ZN5doris10PageReaderILb0ELb1EEC2EPNS_2io20BufferedStreamReaderEPNS2_9IOContextEmmmRKN8tparquet14ColumnMetaDataERKNS_22ParquetPageReadContextEPKNS7_11OffsetIndexE
Line
Count
Source
56
6
        : _reader(reader),
57
6
          _io_ctx(io_ctx),
58
6
          _offset(offset),
59
6
          _start_offset(offset),
60
6
          _end_offset(offset + length),
61
6
          _total_rows(total_rows),
62
6
          _metadata(metadata),
63
6
          _page_read_ctx(page_read_ctx),
64
6
          _offset_index(offset_index) {
65
6
    _next_header_offset = _offset;
66
6
    _state = INITIALIZED;
67
6
    _page_cache_key_builder.init(_reader->path(), _reader->mtime());
68
69
6
    if constexpr (OFFSET_INDEX) {
70
6
        _end_row = _offset_index->page_locations.size() >= 2
71
6
                           ? _offset_index->page_locations[1].first_row_index
72
6
                           : _total_rows;
73
6
    }
74
6
}
_ZN5doris10PageReaderILb0ELb0EEC2EPNS_2io20BufferedStreamReaderEPNS2_9IOContextEmmmRKN8tparquet14ColumnMetaDataERKNS_22ParquetPageReadContextEPKNS7_11OffsetIndexE
Line
Count
Source
56
276
        : _reader(reader),
57
276
          _io_ctx(io_ctx),
58
276
          _offset(offset),
59
276
          _start_offset(offset),
60
276
          _end_offset(offset + length),
61
276
          _total_rows(total_rows),
62
276
          _metadata(metadata),
63
276
          _page_read_ctx(page_read_ctx),
64
276
          _offset_index(offset_index) {
65
276
    _next_header_offset = _offset;
66
276
    _state = INITIALIZED;
67
276
    _page_cache_key_builder.init(_reader->path(), _reader->mtime());
68
69
    if constexpr (OFFSET_INDEX) {
70
        _end_row = _offset_index->page_locations.size() >= 2
71
                           ? _offset_index->page_locations[1].first_row_index
72
                           : _total_rows;
73
    }
74
276
}
75
76
template <bool IN_COLLECTION, bool OFFSET_INDEX>
77
567
Status PageReader<IN_COLLECTION, OFFSET_INDEX>::parse_page_header() {
78
567
    if (_state == HEADER_PARSED) {
79
148
        return Status::OK();
80
148
    }
81
419
    if (UNLIKELY(_offset < _start_offset || _offset >= _end_offset)) {
82
0
        return Status::IOError("Out-of-bounds Access");
83
0
    }
84
419
    if (UNLIKELY(_offset != _next_header_offset)) {
85
0
        return Status::IOError("Wrong header position, should seek to a page header first");
86
0
    }
87
419
    if (UNLIKELY(_state != INITIALIZED)) {
88
0
        return Status::IOError("Should skip or load current page to get next page");
89
0
    }
90
91
419
    _page_statistics.page_read_counter += 1;
92
93
    // Parse page header from file; header bytes are saved for possible cache insertion
94
419
    const uint8_t* page_header_buf = nullptr;
95
419
    size_t max_size = _end_offset - _offset;
96
419
    size_t header_size = std::min(INIT_PAGE_HEADER_SIZE, max_size);
97
419
    const size_t MAX_PAGE_HEADER_SIZE = config::parquet_header_max_size_mb << 20;
98
419
    uint32_t real_header_size = 0;
99
100
    // Try a header-only lookup in the page cache. Cached pages store
101
    // header + optional v2 levels + uncompressed payload, so we can
102
    // parse the page header directly from the cached bytes and avoid
103
    // a file read for the header.
104
419
    if (_page_read_ctx.enable_parquet_file_page_cache && !config::disable_storage_page_cache &&
105
419
        StoragePageCache::instance() != nullptr) {
106
400
        PageCacheHandle handle;
107
400
        StoragePageCache::CacheKey key = make_page_cache_key(static_cast<int64_t>(_offset));
108
400
        if (StoragePageCache::instance()->lookup(key, &handle, segment_v2::DATA_PAGE)) {
109
            // Parse header directly from cached data
110
128
            _page_cache_handle = std::move(handle);
111
128
            Slice s = _page_cache_handle.data();
112
128
            real_header_size = cast_set<uint32_t>(s.size);
113
128
            SCOPED_RAW_TIMER(&_page_statistics.decode_header_time);
114
128
            auto st = deserialize_thrift_msg(reinterpret_cast<const uint8_t*>(s.data),
115
128
                                             &real_header_size, true, &_cur_page_header);
116
128
            if (!st.ok()) return st;
117
            // Increment page cache counters for a true cache hit on header+payload
118
128
            _page_statistics.page_cache_hit_counter += 1;
119
            // Detect whether the cached payload is compressed or decompressed and record
120
128
            bool is_cache_payload_decompressed =
121
128
                    should_cache_decompressed(&_cur_page_header, _metadata);
122
123
128
            if (is_cache_payload_decompressed) {
124
128
                _page_statistics.page_cache_decompressed_hit_counter += 1;
125
128
            } else {
126
0
                _page_statistics.page_cache_compressed_hit_counter += 1;
127
0
            }
128
129
128
            _is_cache_payload_decompressed = is_cache_payload_decompressed;
130
131
128
            if constexpr (OFFSET_INDEX == false) {
132
128
                if (is_header_v2()) {
133
13
                    _end_row = _start_row + _cur_page_header.data_page_header_v2.num_rows;
134
114
                } else if constexpr (!IN_COLLECTION) {
135
114
                    _end_row = _start_row + _cur_page_header.data_page_header.num_values;
136
114
                }
137
128
            }
138
139
            // Save header bytes for later use (e.g., to insert updated cache entries)
140
128
            _header_buf.assign(s.data, s.data + real_header_size);
141
128
            _last_header_size = real_header_size;
142
128
            _page_statistics.parse_page_header_num++;
143
128
            _offset += real_header_size;
144
128
            _next_header_offset = _offset + _cur_page_header.compressed_page_size;
145
128
            _state = HEADER_PARSED;
146
128
            return Status::OK();
147
272
        } else {
148
272
            _page_statistics.page_cache_missing_counter += 1;
149
            // Clear any existing cache handle on miss to avoid holding stale handle
150
272
            _page_cache_handle = PageCacheHandle();
151
272
        }
152
400
    }
153
    // NOTE: page cache lookup for *decompressed* page data is handled in
154
    // ColumnChunkReader::load_page_data(). PageReader should only be
155
    // responsible for parsing the header bytes from the file and saving
156
    // them in `_header_buf` for possible later insertion into the cache.
157
292
    while (true) {
158
292
        if (UNLIKELY(_io_ctx && _io_ctx->should_stop)) {
159
1
            return Status::EndOfFile("stop");
160
1
        }
161
291
        header_size = std::min(header_size, max_size);
162
291
        {
163
291
            SCOPED_RAW_TIMER(&_page_statistics.read_page_header_time);
164
291
            RETURN_IF_ERROR(_reader->read_bytes(&page_header_buf, _offset, header_size, _io_ctx));
165
291
        }
166
290
        real_header_size = cast_set<uint32_t>(header_size);
167
290
        SCOPED_RAW_TIMER(&_page_statistics.decode_header_time);
168
290
        auto st =
169
290
                deserialize_thrift_msg(page_header_buf, &real_header_size, true, &_cur_page_header);
170
290
        if (st.ok()) {
171
289
            break;
172
289
        }
173
1
        if (_offset + header_size >= _end_offset || real_header_size > MAX_PAGE_HEADER_SIZE) {
174
0
            return Status::IOError(
175
0
                    "Failed to deserialize parquet page header. offset: {}, "
176
0
                    "header size: {}, end offset: {}, real header size: {}",
177
0
                    _offset, header_size, _end_offset, real_header_size);
178
0
        }
179
1
        header_size <<= 2;
180
1
    }
181
182
289
    if constexpr (OFFSET_INDEX == false) {
183
277
        if (is_header_v2()) {
184
90
            _end_row = _start_row + _cur_page_header.data_page_header_v2.num_rows;
185
185
        } else if constexpr (!IN_COLLECTION) {
186
185
            _end_row = _start_row + _cur_page_header.data_page_header.num_values;
187
185
        }
188
277
    }
189
190
    // Save header bytes for possible cache insertion later
191
289
    _header_buf.assign(page_header_buf, page_header_buf + real_header_size);
192
289
    _last_header_size = real_header_size;
193
289
    _page_statistics.parse_page_header_num++;
194
289
    _offset += real_header_size;
195
289
    _next_header_offset = _offset + _cur_page_header.compressed_page_size;
196
289
    _state = HEADER_PARSED;
197
289
    return Status::OK();
198
291
}
Unexecuted instantiation: _ZN5doris10PageReaderILb1ELb1EE17parse_page_headerEv
_ZN5doris10PageReaderILb1ELb0EE17parse_page_headerEv
Line
Count
Source
77
7
Status PageReader<IN_COLLECTION, OFFSET_INDEX>::parse_page_header() {
78
7
    if (_state == HEADER_PARSED) {
79
3
        return Status::OK();
80
3
    }
81
4
    if (UNLIKELY(_offset < _start_offset || _offset >= _end_offset)) {
82
0
        return Status::IOError("Out-of-bounds Access");
83
0
    }
84
4
    if (UNLIKELY(_offset != _next_header_offset)) {
85
0
        return Status::IOError("Wrong header position, should seek to a page header first");
86
0
    }
87
4
    if (UNLIKELY(_state != INITIALIZED)) {
88
0
        return Status::IOError("Should skip or load current page to get next page");
89
0
    }
90
91
4
    _page_statistics.page_read_counter += 1;
92
93
    // Parse page header from file; header bytes are saved for possible cache insertion
94
4
    const uint8_t* page_header_buf = nullptr;
95
4
    size_t max_size = _end_offset - _offset;
96
4
    size_t header_size = std::min(INIT_PAGE_HEADER_SIZE, max_size);
97
4
    const size_t MAX_PAGE_HEADER_SIZE = config::parquet_header_max_size_mb << 20;
98
4
    uint32_t real_header_size = 0;
99
100
    // Try a header-only lookup in the page cache. Cached pages store
101
    // header + optional v2 levels + uncompressed payload, so we can
102
    // parse the page header directly from the cached bytes and avoid
103
    // a file read for the header.
104
4
    if (_page_read_ctx.enable_parquet_file_page_cache && !config::disable_storage_page_cache &&
105
4
        StoragePageCache::instance() != nullptr) {
106
3
        PageCacheHandle handle;
107
3
        StoragePageCache::CacheKey key = make_page_cache_key(static_cast<int64_t>(_offset));
108
3
        if (StoragePageCache::instance()->lookup(key, &handle, segment_v2::DATA_PAGE)) {
109
            // Parse header directly from cached data
110
1
            _page_cache_handle = std::move(handle);
111
1
            Slice s = _page_cache_handle.data();
112
1
            real_header_size = cast_set<uint32_t>(s.size);
113
1
            SCOPED_RAW_TIMER(&_page_statistics.decode_header_time);
114
1
            auto st = deserialize_thrift_msg(reinterpret_cast<const uint8_t*>(s.data),
115
1
                                             &real_header_size, true, &_cur_page_header);
116
1
            if (!st.ok()) return st;
117
            // Increment page cache counters for a true cache hit on header+payload
118
1
            _page_statistics.page_cache_hit_counter += 1;
119
            // Detect whether the cached payload is compressed or decompressed and record
120
1
            bool is_cache_payload_decompressed =
121
1
                    should_cache_decompressed(&_cur_page_header, _metadata);
122
123
1
            if (is_cache_payload_decompressed) {
124
1
                _page_statistics.page_cache_decompressed_hit_counter += 1;
125
1
            } else {
126
0
                _page_statistics.page_cache_compressed_hit_counter += 1;
127
0
            }
128
129
1
            _is_cache_payload_decompressed = is_cache_payload_decompressed;
130
131
1
            if constexpr (OFFSET_INDEX == false) {
132
1
                if (is_header_v2()) {
133
0
                    _end_row = _start_row + _cur_page_header.data_page_header_v2.num_rows;
134
1
                } else if constexpr (!IN_COLLECTION) {
135
1
                    _end_row = _start_row + _cur_page_header.data_page_header.num_values;
136
1
                }
137
1
            }
138
139
            // Save header bytes for later use (e.g., to insert updated cache entries)
140
1
            _header_buf.assign(s.data, s.data + real_header_size);
141
1
            _last_header_size = real_header_size;
142
1
            _page_statistics.parse_page_header_num++;
143
1
            _offset += real_header_size;
144
1
            _next_header_offset = _offset + _cur_page_header.compressed_page_size;
145
1
            _state = HEADER_PARSED;
146
1
            return Status::OK();
147
2
        } else {
148
2
            _page_statistics.page_cache_missing_counter += 1;
149
            // Clear any existing cache handle on miss to avoid holding stale handle
150
2
            _page_cache_handle = PageCacheHandle();
151
2
        }
152
3
    }
153
    // NOTE: page cache lookup for *decompressed* page data is handled in
154
    // ColumnChunkReader::load_page_data(). PageReader should only be
155
    // responsible for parsing the header bytes from the file and saving
156
    // them in `_header_buf` for possible later insertion into the cache.
157
3
    while (true) {
158
3
        if (UNLIKELY(_io_ctx && _io_ctx->should_stop)) {
159
1
            return Status::EndOfFile("stop");
160
1
        }
161
2
        header_size = std::min(header_size, max_size);
162
2
        {
163
2
            SCOPED_RAW_TIMER(&_page_statistics.read_page_header_time);
164
2
            RETURN_IF_ERROR(_reader->read_bytes(&page_header_buf, _offset, header_size, _io_ctx));
165
2
        }
166
2
        real_header_size = cast_set<uint32_t>(header_size);
167
2
        SCOPED_RAW_TIMER(&_page_statistics.decode_header_time);
168
2
        auto st =
169
2
                deserialize_thrift_msg(page_header_buf, &real_header_size, true, &_cur_page_header);
170
2
        if (st.ok()) {
171
2
            break;
172
2
        }
173
0
        if (_offset + header_size >= _end_offset || real_header_size > MAX_PAGE_HEADER_SIZE) {
174
0
            return Status::IOError(
175
0
                    "Failed to deserialize parquet page header. offset: {}, "
176
0
                    "header size: {}, end offset: {}, real header size: {}",
177
0
                    _offset, header_size, _end_offset, real_header_size);
178
0
        }
179
0
        header_size <<= 2;
180
0
    }
181
182
2
    if constexpr (OFFSET_INDEX == false) {
183
2
        if (is_header_v2()) {
184
0
            _end_row = _start_row + _cur_page_header.data_page_header_v2.num_rows;
185
2
        } else if constexpr (!IN_COLLECTION) {
186
2
            _end_row = _start_row + _cur_page_header.data_page_header.num_values;
187
2
        }
188
2
    }
189
190
    // Save header bytes for possible cache insertion later
191
2
    _header_buf.assign(page_header_buf, page_header_buf + real_header_size);
192
2
    _last_header_size = real_header_size;
193
2
    _page_statistics.parse_page_header_num++;
194
2
    _offset += real_header_size;
195
2
    _next_header_offset = _offset + _cur_page_header.compressed_page_size;
196
2
    _state = HEADER_PARSED;
197
2
    return Status::OK();
198
3
}
_ZN5doris10PageReaderILb0ELb1EE17parse_page_headerEv
Line
Count
Source
77
12
Status PageReader<IN_COLLECTION, OFFSET_INDEX>::parse_page_header() {
78
12
    if (_state == HEADER_PARSED) {
79
0
        return Status::OK();
80
0
    }
81
12
    if (UNLIKELY(_offset < _start_offset || _offset >= _end_offset)) {
82
0
        return Status::IOError("Out-of-bounds Access");
83
0
    }
84
12
    if (UNLIKELY(_offset != _next_header_offset)) {
85
0
        return Status::IOError("Wrong header position, should seek to a page header first");
86
0
    }
87
12
    if (UNLIKELY(_state != INITIALIZED)) {
88
0
        return Status::IOError("Should skip or load current page to get next page");
89
0
    }
90
91
12
    _page_statistics.page_read_counter += 1;
92
93
    // Parse page header from file; header bytes are saved for possible cache insertion
94
12
    const uint8_t* page_header_buf = nullptr;
95
12
    size_t max_size = _end_offset - _offset;
96
12
    size_t header_size = std::min(INIT_PAGE_HEADER_SIZE, max_size);
97
12
    const size_t MAX_PAGE_HEADER_SIZE = config::parquet_header_max_size_mb << 20;
98
12
    uint32_t real_header_size = 0;
99
100
    // Try a header-only lookup in the page cache. Cached pages store
101
    // header + optional v2 levels + uncompressed payload, so we can
102
    // parse the page header directly from the cached bytes and avoid
103
    // a file read for the header.
104
12
    if (_page_read_ctx.enable_parquet_file_page_cache && !config::disable_storage_page_cache &&
105
12
        StoragePageCache::instance() != nullptr) {
106
6
        PageCacheHandle handle;
107
6
        StoragePageCache::CacheKey key = make_page_cache_key(static_cast<int64_t>(_offset));
108
6
        if (StoragePageCache::instance()->lookup(key, &handle, segment_v2::DATA_PAGE)) {
109
            // Parse header directly from cached data
110
0
            _page_cache_handle = std::move(handle);
111
0
            Slice s = _page_cache_handle.data();
112
0
            real_header_size = cast_set<uint32_t>(s.size);
113
0
            SCOPED_RAW_TIMER(&_page_statistics.decode_header_time);
114
0
            auto st = deserialize_thrift_msg(reinterpret_cast<const uint8_t*>(s.data),
115
0
                                             &real_header_size, true, &_cur_page_header);
116
0
            if (!st.ok()) return st;
117
            // Increment page cache counters for a true cache hit on header+payload
118
0
            _page_statistics.page_cache_hit_counter += 1;
119
            // Detect whether the cached payload is compressed or decompressed and record
120
0
            bool is_cache_payload_decompressed =
121
0
                    should_cache_decompressed(&_cur_page_header, _metadata);
122
123
0
            if (is_cache_payload_decompressed) {
124
0
                _page_statistics.page_cache_decompressed_hit_counter += 1;
125
0
            } else {
126
0
                _page_statistics.page_cache_compressed_hit_counter += 1;
127
0
            }
128
129
0
            _is_cache_payload_decompressed = is_cache_payload_decompressed;
130
131
            if constexpr (OFFSET_INDEX == false) {
132
                if (is_header_v2()) {
133
                    _end_row = _start_row + _cur_page_header.data_page_header_v2.num_rows;
134
                } else if constexpr (!IN_COLLECTION) {
135
                    _end_row = _start_row + _cur_page_header.data_page_header.num_values;
136
                }
137
            }
138
139
            // Save header bytes for later use (e.g., to insert updated cache entries)
140
0
            _header_buf.assign(s.data, s.data + real_header_size);
141
0
            _last_header_size = real_header_size;
142
0
            _page_statistics.parse_page_header_num++;
143
0
            _offset += real_header_size;
144
0
            _next_header_offset = _offset + _cur_page_header.compressed_page_size;
145
0
            _state = HEADER_PARSED;
146
0
            return Status::OK();
147
6
        } else {
148
6
            _page_statistics.page_cache_missing_counter += 1;
149
            // Clear any existing cache handle on miss to avoid holding stale handle
150
6
            _page_cache_handle = PageCacheHandle();
151
6
        }
152
6
    }
153
    // NOTE: page cache lookup for *decompressed* page data is handled in
154
    // ColumnChunkReader::load_page_data(). PageReader should only be
155
    // responsible for parsing the header bytes from the file and saving
156
    // them in `_header_buf` for possible later insertion into the cache.
157
12
    while (true) {
158
12
        if (UNLIKELY(_io_ctx && _io_ctx->should_stop)) {
159
0
            return Status::EndOfFile("stop");
160
0
        }
161
12
        header_size = std::min(header_size, max_size);
162
12
        {
163
12
            SCOPED_RAW_TIMER(&_page_statistics.read_page_header_time);
164
12
            RETURN_IF_ERROR(_reader->read_bytes(&page_header_buf, _offset, header_size, _io_ctx));
165
12
        }
166
12
        real_header_size = cast_set<uint32_t>(header_size);
167
12
        SCOPED_RAW_TIMER(&_page_statistics.decode_header_time);
168
12
        auto st =
169
12
                deserialize_thrift_msg(page_header_buf, &real_header_size, true, &_cur_page_header);
170
12
        if (st.ok()) {
171
12
            break;
172
12
        }
173
0
        if (_offset + header_size >= _end_offset || real_header_size > MAX_PAGE_HEADER_SIZE) {
174
0
            return Status::IOError(
175
0
                    "Failed to deserialize parquet page header. offset: {}, "
176
0
                    "header size: {}, end offset: {}, real header size: {}",
177
0
                    _offset, header_size, _end_offset, real_header_size);
178
0
        }
179
0
        header_size <<= 2;
180
0
    }
181
182
    if constexpr (OFFSET_INDEX == false) {
183
        if (is_header_v2()) {
184
            _end_row = _start_row + _cur_page_header.data_page_header_v2.num_rows;
185
        } else if constexpr (!IN_COLLECTION) {
186
            _end_row = _start_row + _cur_page_header.data_page_header.num_values;
187
        }
188
    }
189
190
    // Save header bytes for possible cache insertion later
191
12
    _header_buf.assign(page_header_buf, page_header_buf + real_header_size);
192
12
    _last_header_size = real_header_size;
193
12
    _page_statistics.parse_page_header_num++;
194
12
    _offset += real_header_size;
195
12
    _next_header_offset = _offset + _cur_page_header.compressed_page_size;
196
12
    _state = HEADER_PARSED;
197
12
    return Status::OK();
198
12
}
_ZN5doris10PageReaderILb0ELb0EE17parse_page_headerEv
Line
Count
Source
77
548
Status PageReader<IN_COLLECTION, OFFSET_INDEX>::parse_page_header() {
78
548
    if (_state == HEADER_PARSED) {
79
145
        return Status::OK();
80
145
    }
81
403
    if (UNLIKELY(_offset < _start_offset || _offset >= _end_offset)) {
82
0
        return Status::IOError("Out-of-bounds Access");
83
0
    }
84
403
    if (UNLIKELY(_offset != _next_header_offset)) {
85
0
        return Status::IOError("Wrong header position, should seek to a page header first");
86
0
    }
87
403
    if (UNLIKELY(_state != INITIALIZED)) {
88
0
        return Status::IOError("Should skip or load current page to get next page");
89
0
    }
90
91
403
    _page_statistics.page_read_counter += 1;
92
93
    // Parse page header from file; header bytes are saved for possible cache insertion
94
403
    const uint8_t* page_header_buf = nullptr;
95
403
    size_t max_size = _end_offset - _offset;
96
403
    size_t header_size = std::min(INIT_PAGE_HEADER_SIZE, max_size);
97
403
    const size_t MAX_PAGE_HEADER_SIZE = config::parquet_header_max_size_mb << 20;
98
403
    uint32_t real_header_size = 0;
99
100
    // Try a header-only lookup in the page cache. Cached pages store
101
    // header + optional v2 levels + uncompressed payload, so we can
102
    // parse the page header directly from the cached bytes and avoid
103
    // a file read for the header.
104
403
    if (_page_read_ctx.enable_parquet_file_page_cache && !config::disable_storage_page_cache &&
105
403
        StoragePageCache::instance() != nullptr) {
106
391
        PageCacheHandle handle;
107
391
        StoragePageCache::CacheKey key = make_page_cache_key(static_cast<int64_t>(_offset));
108
391
        if (StoragePageCache::instance()->lookup(key, &handle, segment_v2::DATA_PAGE)) {
109
            // Parse header directly from cached data
110
127
            _page_cache_handle = std::move(handle);
111
127
            Slice s = _page_cache_handle.data();
112
127
            real_header_size = cast_set<uint32_t>(s.size);
113
127
            SCOPED_RAW_TIMER(&_page_statistics.decode_header_time);
114
127
            auto st = deserialize_thrift_msg(reinterpret_cast<const uint8_t*>(s.data),
115
127
                                             &real_header_size, true, &_cur_page_header);
116
127
            if (!st.ok()) return st;
117
            // Increment page cache counters for a true cache hit on header+payload
118
127
            _page_statistics.page_cache_hit_counter += 1;
119
            // Detect whether the cached payload is compressed or decompressed and record
120
127
            bool is_cache_payload_decompressed =
121
127
                    should_cache_decompressed(&_cur_page_header, _metadata);
122
123
127
            if (is_cache_payload_decompressed) {
124
127
                _page_statistics.page_cache_decompressed_hit_counter += 1;
125
127
            } else {
126
0
                _page_statistics.page_cache_compressed_hit_counter += 1;
127
0
            }
128
129
127
            _is_cache_payload_decompressed = is_cache_payload_decompressed;
130
131
127
            if constexpr (OFFSET_INDEX == false) {
132
127
                if (is_header_v2()) {
133
13
                    _end_row = _start_row + _cur_page_header.data_page_header_v2.num_rows;
134
114
                } else if constexpr (!IN_COLLECTION) {
135
114
                    _end_row = _start_row + _cur_page_header.data_page_header.num_values;
136
114
                }
137
127
            }
138
139
            // Save header bytes for later use (e.g., to insert updated cache entries)
140
127
            _header_buf.assign(s.data, s.data + real_header_size);
141
127
            _last_header_size = real_header_size;
142
127
            _page_statistics.parse_page_header_num++;
143
127
            _offset += real_header_size;
144
127
            _next_header_offset = _offset + _cur_page_header.compressed_page_size;
145
127
            _state = HEADER_PARSED;
146
127
            return Status::OK();
147
264
        } else {
148
264
            _page_statistics.page_cache_missing_counter += 1;
149
            // Clear any existing cache handle on miss to avoid holding stale handle
150
264
            _page_cache_handle = PageCacheHandle();
151
264
        }
152
391
    }
153
    // NOTE: page cache lookup for *decompressed* page data is handled in
154
    // ColumnChunkReader::load_page_data(). PageReader should only be
155
    // responsible for parsing the header bytes from the file and saving
156
    // them in `_header_buf` for possible later insertion into the cache.
157
277
    while (true) {
158
277
        if (UNLIKELY(_io_ctx && _io_ctx->should_stop)) {
159
0
            return Status::EndOfFile("stop");
160
0
        }
161
277
        header_size = std::min(header_size, max_size);
162
277
        {
163
277
            SCOPED_RAW_TIMER(&_page_statistics.read_page_header_time);
164
277
            RETURN_IF_ERROR(_reader->read_bytes(&page_header_buf, _offset, header_size, _io_ctx));
165
277
        }
166
276
        real_header_size = cast_set<uint32_t>(header_size);
167
276
        SCOPED_RAW_TIMER(&_page_statistics.decode_header_time);
168
276
        auto st =
169
276
                deserialize_thrift_msg(page_header_buf, &real_header_size, true, &_cur_page_header);
170
276
        if (st.ok()) {
171
275
            break;
172
275
        }
173
1
        if (_offset + header_size >= _end_offset || real_header_size > MAX_PAGE_HEADER_SIZE) {
174
0
            return Status::IOError(
175
0
                    "Failed to deserialize parquet page header. offset: {}, "
176
0
                    "header size: {}, end offset: {}, real header size: {}",
177
0
                    _offset, header_size, _end_offset, real_header_size);
178
0
        }
179
1
        header_size <<= 2;
180
1
    }
181
182
275
    if constexpr (OFFSET_INDEX == false) {
183
275
        if (is_header_v2()) {
184
90
            _end_row = _start_row + _cur_page_header.data_page_header_v2.num_rows;
185
185
        } else if constexpr (!IN_COLLECTION) {
186
185
            _end_row = _start_row + _cur_page_header.data_page_header.num_values;
187
185
        }
188
275
    }
189
190
    // Save header bytes for possible cache insertion later
191
275
    _header_buf.assign(page_header_buf, page_header_buf + real_header_size);
192
275
    _last_header_size = real_header_size;
193
275
    _page_statistics.parse_page_header_num++;
194
275
    _offset += real_header_size;
195
275
    _next_header_offset = _offset + _cur_page_header.compressed_page_size;
196
275
    _state = HEADER_PARSED;
197
275
    return Status::OK();
198
276
}
199
200
template <bool IN_COLLECTION, bool OFFSET_INDEX>
201
280
Status PageReader<IN_COLLECTION, OFFSET_INDEX>::get_page_data(Slice& slice) {
202
280
    if (UNLIKELY(_state != HEADER_PARSED)) {
203
0
        return Status::IOError("Should generate page header first to load current page data");
204
0
    }
205
280
    if (UNLIKELY(_io_ctx && _io_ctx->should_stop)) {
206
0
        return Status::EndOfFile("stop");
207
0
    }
208
280
    slice.size = _cur_page_header.compressed_page_size;
209
280
    RETURN_IF_ERROR(_reader->read_bytes(slice, _offset, _io_ctx));
210
279
    _offset += slice.size;
211
279
    _state = DATA_LOADED;
212
279
    return Status::OK();
213
280
}
Unexecuted instantiation: _ZN5doris10PageReaderILb1ELb1EE13get_page_dataERNS_5SliceE
_ZN5doris10PageReaderILb1ELb0EE13get_page_dataERNS_5SliceE
Line
Count
Source
201
2
Status PageReader<IN_COLLECTION, OFFSET_INDEX>::get_page_data(Slice& slice) {
202
2
    if (UNLIKELY(_state != HEADER_PARSED)) {
203
0
        return Status::IOError("Should generate page header first to load current page data");
204
0
    }
205
2
    if (UNLIKELY(_io_ctx && _io_ctx->should_stop)) {
206
0
        return Status::EndOfFile("stop");
207
0
    }
208
2
    slice.size = _cur_page_header.compressed_page_size;
209
2
    RETURN_IF_ERROR(_reader->read_bytes(slice, _offset, _io_ctx));
210
2
    _offset += slice.size;
211
2
    _state = DATA_LOADED;
212
2
    return Status::OK();
213
2
}
_ZN5doris10PageReaderILb0ELb1EE13get_page_dataERNS_5SliceE
Line
Count
Source
201
8
Status PageReader<IN_COLLECTION, OFFSET_INDEX>::get_page_data(Slice& slice) {
202
8
    if (UNLIKELY(_state != HEADER_PARSED)) {
203
0
        return Status::IOError("Should generate page header first to load current page data");
204
0
    }
205
8
    if (UNLIKELY(_io_ctx && _io_ctx->should_stop)) {
206
0
        return Status::EndOfFile("stop");
207
0
    }
208
8
    slice.size = _cur_page_header.compressed_page_size;
209
8
    RETURN_IF_ERROR(_reader->read_bytes(slice, _offset, _io_ctx));
210
8
    _offset += slice.size;
211
8
    _state = DATA_LOADED;
212
8
    return Status::OK();
213
8
}
_ZN5doris10PageReaderILb0ELb0EE13get_page_dataERNS_5SliceE
Line
Count
Source
201
270
Status PageReader<IN_COLLECTION, OFFSET_INDEX>::get_page_data(Slice& slice) {
202
270
    if (UNLIKELY(_state != HEADER_PARSED)) {
203
0
        return Status::IOError("Should generate page header first to load current page data");
204
0
    }
205
270
    if (UNLIKELY(_io_ctx && _io_ctx->should_stop)) {
206
0
        return Status::EndOfFile("stop");
207
0
    }
208
270
    slice.size = _cur_page_header.compressed_page_size;
209
270
    RETURN_IF_ERROR(_reader->read_bytes(slice, _offset, _io_ctx));
210
269
    _offset += slice.size;
211
269
    _state = DATA_LOADED;
212
269
    return Status::OK();
213
270
}
214
215
template class PageReader<true, true>;
216
template class PageReader<true, false>;
217
template class PageReader<false, true>;
218
template class PageReader<false, false>;
219
220
} // namespace doris