Coverage Report

Created: 2026-07-24 22:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/exec/spill/spill_file_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 "exec/spill/spill_file_reader.h"
19
20
#include <glog/logging.h>
21
22
#include <algorithm>
23
24
#include "common/cast_set.h"
25
#include "common/exception.h"
26
#include "core/block/block.h"
27
#include "exec/spill/spill_file_manager.h"
28
#include "io/file_factory.h"
29
#include "io/fs/file_reader.h"
30
#include "io/fs/local_file_system.h"
31
#include "runtime/exec_env.h"
32
#include "runtime/query_context.h"
33
#include "runtime/runtime_state.h"
34
#include "util/debug_points.h"
35
#include "util/slice.h"
36
namespace doris {
37
namespace io {
38
class FileSystem;
39
} // namespace io
40
41
SpillFileReader::SpillFileReader(RuntimeState* state, RuntimeProfile* profile,
42
                                 std::string spill_dir, size_t part_count)
43
370
        : _spill_dir(std::move(spill_dir)),
44
370
          _part_count(part_count),
45
370
          _resource_ctx(state->get_query_ctx()->resource_ctx()) {
46
    // Internalize counter setup
47
370
    RuntimeProfile* custom_profile = profile->get_child("CustomCounters");
48
370
    DCHECK(custom_profile != nullptr);
49
370
    _read_file_timer = custom_profile->get_counter("SpillReadFileTime");
50
370
    _deserialize_timer = custom_profile->get_counter("SpillReadDerializeBlockTime");
51
370
    _read_block_count = custom_profile->get_counter("SpillReadBlockCount");
52
370
    _read_block_data_size = custom_profile->get_counter("SpillReadBlockBytes");
53
370
    _read_file_size = custom_profile->get_counter("SpillReadFileBytes");
54
370
    _read_rows_count = custom_profile->get_counter("SpillReadRows");
55
370
    _read_file_count = custom_profile->get_counter("SpillReadFileCount");
56
370
}
57
58
372
Status SpillFileReader::open() {
59
372
    if (_is_open || _part_count == 0) {
60
46
        return Status::OK();
61
46
    }
62
326
    RETURN_IF_ERROR(_open_part(0));
63
316
    _is_open = true;
64
316
    return Status::OK();
65
326
}
66
67
352
Status SpillFileReader::_open_part(size_t part_index) {
68
352
    _close_current_part();
69
70
352
    _current_part_index = part_index;
71
352
    _part_opened = true;
72
352
    std::string part_path = _spill_dir + "/" + std::to_string(part_index);
73
74
352
    SCOPED_TIMER(_read_file_timer);
75
352
    COUNTER_UPDATE(_read_file_count, 1);
76
352
    RETURN_IF_ERROR(io::global_local_filesystem()->open_file(part_path, &_file_reader));
77
78
342
    size_t file_size = _file_reader->size();
79
342
    DCHECK(file_size >= 16); // max_sub_block_size + block count
80
81
342
    Slice result((char*)&_part_block_count, sizeof(size_t));
82
83
    // read block count
84
342
    size_t bytes_read = 0;
85
342
    RETURN_IF_ERROR(_file_reader->read_at(file_size - sizeof(size_t), result, &bytes_read));
86
342
    DCHECK(bytes_read == 8);
87
88
    // read max sub block size
89
342
    bytes_read = 0;
90
342
    result.data = (char*)&_part_max_sub_block_size;
91
342
    RETURN_IF_ERROR(_file_reader->read_at(file_size - sizeof(size_t) * 2, result, &bytes_read));
92
342
    DCHECK(bytes_read == 8);
93
94
    // The buffer is used for two purposes:
95
    // 1. Reading the block start offsets array (needs _part_block_count * sizeof(size_t) bytes)
96
    // 2. Reading a single block's serialized data (needs up to _part_max_sub_block_size bytes)
97
    // We must ensure the buffer is large enough for either case, so take the maximum.
98
342
    size_t buff_size = std::max(_part_block_count * sizeof(size_t), _part_max_sub_block_size);
99
342
    if (buff_size > _read_buff.size()) {
100
342
        _read_buff.reserve(buff_size);
101
342
    }
102
103
    // Read the block start offsets array from the end of the file.
104
    // The file layout (from end backwards) is:
105
    //   [block count (size_t)]
106
    //   [max sub block size (size_t)]
107
    //   [block start offsets array (_part_block_count * size_t)]
108
    // So the offsets array starts at:
109
    //   file_size - (_part_block_count + 2) * sizeof(size_t)
110
342
    size_t read_offset = file_size - (_part_block_count + 2) * sizeof(size_t);
111
342
    result.data = _read_buff.data();
112
342
    result.size = _part_block_count * sizeof(size_t);
113
114
342
    RETURN_IF_ERROR(_file_reader->read_at(read_offset, result, &bytes_read));
115
342
    DCHECK(bytes_read == _part_block_count * sizeof(size_t));
116
117
342
    _block_start_offsets.resize(_part_block_count + 1);
118
1.05k
    for (size_t i = 0; i < _part_block_count; ++i) {
119
712
        _block_start_offsets[i] = *(size_t*)(result.data + i * sizeof(size_t));
120
712
    }
121
342
    _block_start_offsets[_part_block_count] = file_size - (_part_block_count + 2) * sizeof(size_t);
122
123
342
    _part_read_block_index = 0;
124
342
    return Status::OK();
125
342
}
126
127
978
void SpillFileReader::_close_current_part() {
128
978
    if (_file_reader) {
129
342
        (void)_file_reader->close();
130
342
        _file_reader.reset();
131
342
    }
132
978
    _part_block_count = 0;
133
978
    _part_read_block_index = 0;
134
978
    _part_max_sub_block_size = 0;
135
978
    _block_start_offsets.clear();
136
978
    _part_opened = false;
137
978
}
138
139
954
Status SpillFileReader::read(Block* block, bool* eos) {
140
954
    DBUG_EXECUTE_IF("fault_inject::spill_file::read_next_block", {
141
954
        return Status::InternalError("fault_inject spill_file read_next_block failed");
142
954
    });
143
948
    block->clear_column_data();
144
145
948
    if (_part_count == 0) {
146
46
        *eos = true;
147
46
        return Status::OK();
148
46
    }
149
150
    // Advance to next part if current part is exhausted
151
922
    while (_part_read_block_index >= _part_block_count) {
152
264
        size_t next_part = _part_opened ? _current_part_index + 1 : 0;
153
264
        if (next_part >= _part_count) {
154
244
            *eos = true;
155
244
            return Status::OK();
156
244
        }
157
20
        RETURN_IF_ERROR(_open_part(next_part));
158
20
    }
159
160
658
    size_t bytes_to_read = _block_start_offsets[_part_read_block_index + 1] -
161
658
                           _block_start_offsets[_part_read_block_index];
162
163
658
    if (bytes_to_read == 0) {
164
0
        ++_part_read_block_index;
165
0
        *eos = false;
166
0
        return Status::OK();
167
0
    }
168
169
658
    Slice result(_read_buff.data(), bytes_to_read);
170
658
    size_t bytes_read = 0;
171
658
    {
172
658
        SCOPED_TIMER(_read_file_timer);
173
658
        RETURN_IF_ERROR(_file_reader->read_at(_block_start_offsets[_part_read_block_index], result,
174
658
                                              &bytes_read));
175
658
    }
176
658
    DCHECK(bytes_read == bytes_to_read);
177
178
658
    if (bytes_read > 0) {
179
658
        COUNTER_UPDATE(_read_file_size, bytes_read);
180
658
        ExecEnv::GetInstance()->spill_file_mgr()->update_spill_read_bytes(bytes_read);
181
658
        if (_resource_ctx) {
182
658
            _resource_ctx->io_context()->update_spill_read_bytes_from_local_storage(bytes_read);
183
658
        }
184
658
        COUNTER_UPDATE(_read_block_count, 1);
185
658
        {
186
658
            SCOPED_TIMER(_deserialize_timer);
187
658
            if (!_pb_block.ParseFromArray(result.data, cast_set<int>(result.size))) {
188
0
                return Status::InternalError("Failed to read spilled block");
189
0
            }
190
658
            size_t uncompressed_size = 0;
191
658
            int64_t uncompressed_time = 0;
192
658
            RETURN_IF_ERROR(block->deserialize(_pb_block, &uncompressed_size, &uncompressed_time));
193
658
        }
194
658
        COUNTER_UPDATE(_read_block_data_size, block->bytes());
195
658
        COUNTER_UPDATE(_read_rows_count, block->rows());
196
658
    } else {
197
0
        block->clear_column_data();
198
0
    }
199
200
658
    ++_part_read_block_index;
201
658
    *eos = false;
202
658
    return Status::OK();
203
658
}
204
205
4
Status SpillFileReader::seek(size_t block_index) {
206
4
    return _seek_to_block(block_index);
207
4
}
208
209
4
Status SpillFileReader::_seek_to_block(size_t block_index) {
210
4
    if (_part_count == 0) {
211
0
        return Status::OK();
212
0
    }
213
214
4
    size_t remaining = block_index;
215
6
    for (size_t part_index = 0; part_index < _part_count; ++part_index) {
216
4
        RETURN_IF_ERROR(_open_part(part_index));
217
4
        if (remaining < _part_block_count) {
218
2
            _part_read_block_index = remaining;
219
2
            return Status::OK();
220
2
        }
221
2
        remaining -= _part_block_count;
222
2
    }
223
224
    // block_index is out of range: position reader at EOS.
225
2
    RETURN_IF_ERROR(_open_part(_part_count - 1));
226
2
    _part_read_block_index = _part_block_count;
227
2
    return Status::OK();
228
2
}
229
230
626
Status SpillFileReader::close() {
231
626
    _close_current_part();
232
626
    _is_open = false;
233
626
    return Status::OK();
234
626
}
235
236
} // namespace doris