Coverage Report

Created: 2026-03-19 20:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/exec/spill/spill_file_writer.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_writer.h"
19
20
#include "agent/be_exec_version_manager.h"
21
#include "common/config.h"
22
#include "common/status.h"
23
#include "exec/spill/spill_file.h"
24
#include "exec/spill/spill_file_manager.h"
25
#include "io/fs/local_file_system.h"
26
#include "io/fs/local_file_writer.h"
27
#include "runtime/exec_env.h"
28
#include "runtime/query_context.h"
29
#include "runtime/runtime_state.h"
30
#include "runtime/thread_context.h"
31
32
namespace doris {
33
#include "common/compile_check_begin.h"
34
35
SpillFileWriter::SpillFileWriter(const std::shared_ptr<SpillFile>& spill_file, RuntimeState* state,
36
                                 RuntimeProfile* profile, SpillDataDir* data_dir,
37
                                 const std::string& spill_dir)
38
295
        : _spill_file_wptr(spill_file),
39
295
          _data_dir(data_dir),
40
295
          _spill_dir(spill_dir),
41
295
          _max_part_size(config::spill_file_part_size_bytes),
42
295
          _resource_ctx(state->get_query_ctx()->resource_ctx()) {
43
    // Common counters
44
295
    RuntimeProfile* common_profile = profile->get_child("CommonCounters");
45
295
    DCHECK(common_profile != nullptr);
46
295
    _memory_used_counter = common_profile->get_counter("MemoryUsage");
47
48
    // Register this writer as the active writer for the SpillFile.
49
295
    spill_file->_active_writer = this;
50
51
    // Custom (spill-specific) counters
52
295
    RuntimeProfile* custom_profile = profile->get_child("CustomCounters");
53
295
    _write_file_timer = custom_profile->get_counter("SpillWriteFileTime");
54
295
    _serialize_timer = custom_profile->get_counter("SpillWriteSerializeBlockTime");
55
295
    _write_block_counter = custom_profile->get_counter("SpillWriteBlockCount");
56
295
    _write_block_bytes_counter = custom_profile->get_counter("SpillWriteBlockBytes");
57
295
    _write_file_total_size = custom_profile->get_counter("SpillWriteFileBytes");
58
295
    _write_file_current_size = custom_profile->get_counter("SpillWriteFileCurrentBytes");
59
295
    _write_rows_counter = custom_profile->get_counter("SpillWriteRows");
60
295
    _total_file_count = custom_profile->get_counter("SpillWriteFileTotalCount");
61
295
}
62
63
295
SpillFileWriter::~SpillFileWriter() {
64
295
    if (_closed) {
65
268
        return;
66
268
    }
67
27
    Status st = close();
68
27
    if (!st.ok()) {
69
15
        LOG(WARNING) << "SpillFileWriter::~SpillFileWriter() failed: " << st.to_string()
70
15
                     << ", spill_dir=" << _spill_dir;
71
15
    }
72
27
}
73
74
259
Status SpillFileWriter::_open_next_part() {
75
259
    _current_part_path = _spill_dir + "/" + std::to_string(_current_part_index);
76
    // Create the spill directory lazily on first part
77
259
    if (_current_part_index == 0) {
78
249
        RETURN_IF_ERROR(io::global_local_filesystem()->create_directory(_spill_dir));
79
249
    }
80
259
    RETURN_IF_ERROR(io::global_local_filesystem()->create_file(_current_part_path, &_file_writer));
81
259
    COUNTER_UPDATE(_total_file_count, 1);
82
259
    return Status::OK();
83
259
}
84
85
304
Status SpillFileWriter::_close_current_part(const std::shared_ptr<SpillFile>& spill_file) {
86
304
    if (!_file_writer) {
87
45
        return Status::OK();
88
45
    }
89
90
    // Write footer: block offsets + max_sub_block_size + block_count
91
259
    _part_meta.append((const char*)&_part_max_sub_block_size, sizeof(_part_max_sub_block_size));
92
259
    _part_meta.append((const char*)&_part_written_blocks, sizeof(_part_written_blocks));
93
94
259
    {
95
259
        SCOPED_TIMER(_write_file_timer);
96
259
        RETURN_IF_ERROR(_file_writer->append(_part_meta));
97
259
    }
98
99
259
    int64_t meta_size = _part_meta.size();
100
259
    _part_written_bytes += meta_size;
101
259
    _total_written_bytes += meta_size;
102
259
    COUNTER_UPDATE(_write_file_total_size, meta_size);
103
259
    if (_resource_ctx) {
104
259
        _resource_ctx->io_context()->update_spill_write_bytes_to_local_storage(meta_size);
105
259
    }
106
259
    if (_write_file_current_size) {
107
179
        COUNTER_UPDATE(_write_file_current_size, meta_size);
108
179
    }
109
259
    _data_dir->update_spill_data_usage(meta_size);
110
259
    ExecEnv::GetInstance()->spill_file_mgr()->update_spill_write_bytes(meta_size);
111
    // Incrementally update SpillFile's accounting so gc() can always
112
    // decrement the correct amount, even if close() is never called.
113
259
    if (spill_file) {
114
244
        spill_file->update_written_bytes(meta_size);
115
244
    }
116
117
259
    RETURN_IF_ERROR(_file_writer->close());
118
244
    _file_writer.reset();
119
120
    // Advance to next part
121
244
    ++_current_part_index;
122
244
    ++_total_parts;
123
244
    if (spill_file) {
124
244
        spill_file->increment_part_count();
125
244
    }
126
244
    _part_written_blocks = 0;
127
244
    _part_written_bytes = 0;
128
244
    _part_max_sub_block_size = 0;
129
244
    _part_meta.clear();
130
131
244
    return Status::OK();
132
259
}
133
134
458
Status SpillFileWriter::_rotate_if_needed(const std::shared_ptr<SpillFile>& spill_file) {
135
458
    if (_file_writer && _part_written_bytes >= _max_part_size) {
136
10
        RETURN_IF_ERROR(_close_current_part(spill_file));
137
10
    }
138
458
    return Status::OK();
139
458
}
140
141
461
Status SpillFileWriter::write_block(RuntimeState* state, const Block& block) {
142
461
    DCHECK(!_closed);
143
144
    // Lock the SpillFile to ensure it is still alive. If it has already been
145
    // destroyed (gc'd), we must not write any more data because the disk
146
    // accounting would be out of sync.
147
461
    auto spill_file = _spill_file_wptr.lock();
148
461
    if (!spill_file) {
149
0
        return Status::Error<INTERNAL_ERROR>(
150
0
                "SpillFile has been destroyed, cannot write more data, spill_dir={}", _spill_dir);
151
0
    }
152
153
    // Lazily open the first part
154
461
    if (!_file_writer) {
155
259
        RETURN_IF_ERROR(_open_next_part());
156
259
    }
157
158
461
    DBUG_EXECUTE_IF("fault_inject::spill_file::spill_block", {
159
461
        return Status::Error<INTERNAL_ERROR>("fault_inject spill_file spill_block failed");
160
461
    });
161
162
458
    auto rows = block.rows();
163
458
    COUNTER_UPDATE(_write_rows_counter, rows);
164
458
    COUNTER_UPDATE(_write_block_bytes_counter, block.bytes());
165
166
458
    RETURN_IF_ERROR(_write_internal(block, spill_file));
167
168
    // Auto-rotate if current part is full
169
458
    return _rotate_if_needed(spill_file);
170
458
}
171
172
313
Status SpillFileWriter::close() {
173
313
    if (_closed) {
174
18
        return Status::OK();
175
18
    }
176
295
    _closed = true;
177
178
295
    DBUG_EXECUTE_IF("fault_inject::spill_file::spill_eof", {
179
295
        return Status::Error<INTERNAL_ERROR>("fault_inject spill_file spill_eof failed");
180
295
    });
181
182
294
    auto spill_file = _spill_file_wptr.lock();
183
294
    RETURN_IF_ERROR(_close_current_part(spill_file));
184
185
279
    if (spill_file) {
186
279
        if (spill_file->_active_writer != this) {
187
0
            return Status::Error<INTERNAL_ERROR>(
188
0
                    "SpillFileWriter close() called but not registered as active writer, possible "
189
0
                    "double close or logic error");
190
0
        }
191
279
        spill_file->finish_writing();
192
279
    }
193
194
279
    return Status::OK();
195
279
}
196
197
Status SpillFileWriter::_write_internal(const Block& block,
198
458
                                        const std::shared_ptr<SpillFile>& spill_file) {
199
458
    size_t uncompressed_bytes = 0, compressed_bytes = 0;
200
201
458
    Status status;
202
458
    std::string buff;
203
458
    int64_t buff_size {0};
204
205
458
    if (block.rows() > 0) {
206
441
        {
207
441
            PBlock pblock;
208
441
            SCOPED_TIMER(_serialize_timer);
209
441
            int64_t compressed_time = 0;
210
441
            status = block.serialize(
211
441
                    BeExecVersionManager::get_newest_version(), &pblock, &uncompressed_bytes,
212
441
                    &compressed_bytes, &compressed_time,
213
441
                    segment_v2::CompressionTypePB::ZSTD); // ZSTD for better compression ratio
214
441
            RETURN_IF_ERROR(status);
215
441
            int64_t pblock_mem = pblock.ByteSizeLong();
216
441
            COUNTER_UPDATE(_memory_used_counter, pblock_mem);
217
441
            Defer defer {[&]() { COUNTER_UPDATE(_memory_used_counter, -pblock_mem); }};
218
441
            if (!pblock.SerializeToString(&buff)) {
219
0
                return Status::Error<ErrorCode::SERIALIZE_PROTOBUF_ERROR>(
220
0
                        "serialize spill data error. [path={}]", _current_part_path);
221
0
            }
222
441
            buff_size = buff.size();
223
441
            COUNTER_UPDATE(_memory_used_counter, buff_size);
224
441
            Defer defer2 {[&]() { COUNTER_UPDATE(_memory_used_counter, -buff_size); }};
225
441
        }
226
441
        if (_data_dir->reach_capacity_limit(buff_size)) {
227
0
            return Status::Error<ErrorCode::DISK_REACH_CAPACITY_LIMIT>(
228
0
                    "spill data total size exceed limit, path: {}, size limit: {}, spill data "
229
0
                    "size: {}",
230
0
                    _data_dir->path(),
231
0
                    PrettyPrinter::print_bytes(_data_dir->get_spill_data_limit()),
232
0
                    PrettyPrinter::print_bytes(_data_dir->get_spill_data_bytes()));
233
0
        }
234
235
441
        {
236
441
            Defer defer {[&]() {
237
441
                if (status.ok()) {
238
441
                    _data_dir->update_spill_data_usage(buff_size);
239
441
                    ExecEnv::GetInstance()->spill_file_mgr()->update_spill_write_bytes(buff_size);
240
241
441
                    _part_max_sub_block_size =
242
441
                            std::max(_part_max_sub_block_size, (size_t)buff_size);
243
244
441
                    _part_meta.append((const char*)&_part_written_bytes, sizeof(size_t));
245
441
                    COUNTER_UPDATE(_write_file_total_size, buff_size);
246
441
                    if (_resource_ctx) {
247
441
                        _resource_ctx->io_context()->update_spill_write_bytes_to_local_storage(
248
441
                                buff_size);
249
441
                    }
250
441
                    if (_write_file_current_size) {
251
313
                        COUNTER_UPDATE(_write_file_current_size, buff_size);
252
313
                    }
253
441
                    COUNTER_UPDATE(_write_block_counter, 1);
254
441
                    _part_written_bytes += buff_size;
255
441
                    _total_written_bytes += buff_size;
256
441
                    ++_part_written_blocks;
257
                    // Incrementally update SpillFile so gc() can always
258
                    // decrement the correct amount from _data_dir.
259
441
                    spill_file->update_written_bytes(buff_size);
260
441
                }
261
441
            }};
262
441
            {
263
441
                SCOPED_TIMER(_write_file_timer);
264
441
                status = _file_writer->append(buff);
265
441
                RETURN_IF_ERROR(status);
266
441
            }
267
441
        }
268
441
    }
269
270
458
    return status;
271
458
}
272
273
} // namespace doris