Coverage Report

Created: 2026-07-07 17:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/storage/rowset/beta_rowset_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 "storage/rowset/beta_rowset_writer.h"
19
20
#include <assert.h>
21
// IWYU pragma: no_include <bthread/errno.h>
22
#include <errno.h> // IWYU pragma: keep
23
#include <fmt/format.h>
24
#include <stdio.h>
25
26
#include <chrono>
27
#include <ctime> // time
28
#include <filesystem>
29
#include <memory>
30
#include <mutex>
31
#include <sstream>
32
#include <string>
33
#include <thread>
34
#include <utility>
35
#include <vector>
36
37
// IWYU pragma: no_include <opentelemetry/common/threadlocal.h>
38
#include "common/cast_set.h"
39
#include "common/compiler_util.h" // IWYU pragma: keep
40
#include "common/config.h"
41
#include "common/logging.h"
42
#include "common/status.h"
43
#include "core/block/block.h"
44
#include "core/column/column.h"
45
#include "core/data_type/data_type_factory.hpp"
46
#include "cpp/sync_point.h"
47
#include "io/fs/file_reader.h"
48
#include "io/fs/file_system.h"
49
#include "io/fs/file_writer.h"
50
#include "runtime/thread_context.h"
51
#include "storage/index/inverted/inverted_index_cache.h"
52
#include "storage/index/inverted/inverted_index_desc.h"
53
#include "storage/olap_define.h"
54
#include "storage/rowset/beta_rowset.h"
55
#include "storage/rowset/rowset_factory.h"
56
#include "storage/rowset/rowset_writer.h"
57
#include "storage/rowset/segcompaction.h"
58
#include "storage/schema_change/schema_change.h"
59
#include "storage/segment/segment.h"
60
#include "storage/segment/segment_writer.h"
61
#include "storage/storage_engine.h"
62
#include "storage/tablet/tablet_schema.h"
63
#include "util/debug_points.h"
64
#include "util/pretty_printer.h"
65
#include "util/slice.h"
66
#include "util/stopwatch.hpp"
67
#include "util/time.h"
68
69
namespace doris {
70
using namespace ErrorCode;
71
72
namespace {
73
74
1.61k
bool is_segment_overlapping(const std::vector<KeyBoundsPB>& segments_encoded_key_bounds) {
75
1.61k
    std::string_view last;
76
3.80k
    for (auto&& segment_encode_key : segments_encoded_key_bounds) {
77
3.80k
        auto&& cur_min = segment_encode_key.min_key();
78
3.80k
        auto&& cur_max = segment_encode_key.max_key();
79
3.80k
        if (cur_min <= last) {
80
522
            return true;
81
522
        }
82
3.28k
        last = cur_max;
83
3.28k
    }
84
1.09k
    return false;
85
1.61k
}
86
87
2.61k
bool copy_key_bounds_with_truncation(const KeyBoundsPB& src, KeyBoundsPB* dst) {
88
2.61k
    DCHECK(dst != nullptr);
89
2.61k
    if (config::random_segments_key_bounds_truncation) {
90
0
        dst->CopyFrom(src);
91
0
        return false;
92
0
    }
93
2.61k
    const int32_t truncation_threshold = config::segments_key_bounds_truncation_threshold;
94
2.61k
    if (truncation_threshold <= 0) {
95
744
        dst->CopyFrom(src);
96
744
        return false;
97
744
    }
98
1.87k
    const size_t truncation_size = cast_set<size_t>(truncation_threshold);
99
100
1.87k
    bool truncated = false;
101
3.74k
    auto copy_key = [&](const std::string& key, std::string* stored_key) {
102
3.74k
        if (key.size() > truncation_size) {
103
3.09k
            stored_key->assign(key.data(), truncation_size);
104
3.09k
            truncated = true;
105
3.09k
            return;
106
3.09k
        }
107
654
        stored_key->assign(key.data(), key.size());
108
654
    };
109
1.87k
    copy_key(src.min_key(), dst->mutable_min_key());
110
1.87k
    copy_key(src.max_key(), dst->mutable_max_key());
111
1.87k
    return truncated;
112
2.61k
}
113
114
SegmentStatistics copy_segment_statistics_with_truncated_key_bounds(const SegmentStatistics& src,
115
2.59k
                                                                    bool& key_bounds_truncated) {
116
2.59k
    SegmentStatistics dst;
117
2.59k
    dst.row_num = src.row_num;
118
2.59k
    dst.data_size = src.data_size;
119
2.59k
    dst.index_size = src.index_size;
120
2.59k
    key_bounds_truncated = copy_key_bounds_with_truncation(src.key_bounds, &dst.key_bounds);
121
2.59k
    return dst;
122
2.59k
}
123
124
void build_rowset_meta_with_spec_field(RowsetMeta& rowset_meta,
125
76
                                       const RowsetMeta& spec_rowset_meta) {
126
76
    rowset_meta.set_num_rows(spec_rowset_meta.num_rows());
127
76
    rowset_meta.set_total_disk_size(spec_rowset_meta.total_disk_size());
128
76
    rowset_meta.set_data_disk_size(spec_rowset_meta.data_disk_size());
129
76
    rowset_meta.set_index_disk_size(spec_rowset_meta.index_disk_size());
130
    // TODO write zonemap to meta
131
76
    rowset_meta.set_empty(spec_rowset_meta.num_rows() == 0);
132
76
    rowset_meta.set_creation_time(time(nullptr));
133
76
    rowset_meta.set_num_segments(spec_rowset_meta.num_segments());
134
76
    rowset_meta.set_segments_overlap(spec_rowset_meta.segments_overlap());
135
76
    rowset_meta.set_rowset_state(spec_rowset_meta.rowset_state());
136
76
    rowset_meta.set_segments_key_bounds_truncated(
137
76
            spec_rowset_meta.is_segments_key_bounds_truncated());
138
76
    rowset_meta.set_db_id(spec_rowset_meta.db_id());
139
76
    rowset_meta.set_table_id(spec_rowset_meta.table_id());
140
76
    std::vector<KeyBoundsPB> segments_key_bounds;
141
76
    spec_rowset_meta.get_segments_key_bounds(&segments_key_bounds);
142
    // Preserve source layout: if source was aggregated (size 1), re-aggregating
143
    // the single entry is a no-op that also keeps the flag consistent.
144
76
    rowset_meta.set_segments_key_bounds(segments_key_bounds,
145
76
                                        spec_rowset_meta.is_segments_key_bounds_aggregated());
146
76
    std::vector<uint32_t> num_segment_rows;
147
76
    spec_rowset_meta.get_num_segment_rows(&num_segment_rows);
148
76
    rowset_meta.set_num_segment_rows(num_segment_rows);
149
76
    if (spec_rowset_meta.has_commit_tso()) {
150
32
        rowset_meta.set_commit_tso(spec_rowset_meta.commit_tso());
151
32
    }
152
76
    if (spec_rowset_meta.is_row_binlog()) {
153
0
        rowset_meta.mark_row_binlog();
154
0
    }
155
76
}
156
157
} // namespace
158
159
2.05k
SegmentFileCollection::~SegmentFileCollection() = default;
160
161
5.00k
Status SegmentFileCollection::add(int seg_id, io::FileWriterPtr&& writer) {
162
5.00k
    std::lock_guard lock(_lock);
163
5.00k
    if (_closed) [[unlikely]] {
164
0
        DCHECK(false) << writer->path();
165
0
        return Status::InternalError("add to closed SegmentFileCollection");
166
0
    }
167
168
5.00k
    _file_writers.emplace(seg_id, std::move(writer));
169
5.00k
    return Status::OK();
170
5.00k
}
171
172
126
io::FileWriter* SegmentFileCollection::get(int seg_id) const {
173
126
    std::lock_guard lock(_lock);
174
126
    if (auto it = _file_writers.find(seg_id); it != _file_writers.end()) {
175
126
        return it->second.get();
176
126
    } else {
177
0
        return nullptr;
178
0
    }
179
126
}
180
181
1.95k
Status SegmentFileCollection::close() {
182
1.95k
    {
183
1.95k
        std::lock_guard lock(_lock);
184
1.95k
        if (_closed) [[unlikely]] {
185
0
            DCHECK(false);
186
0
            return Status::InternalError("double close SegmentFileCollection");
187
0
        }
188
1.95k
        _closed = true;
189
1.95k
    }
190
191
4.99k
    for (auto&& [_, writer] : _file_writers) {
192
4.99k
        DBUG_EXECUTE_IF("SegmentFileCollection.close.wait_dat_closed", {
193
4.99k
            auto before_state = writer->state();
194
4.99k
            for (int i = 0; i < 3000 && writer->state() != io::FileWriter::State::CLOSED; ++i) {
195
4.99k
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
196
4.99k
            }
197
4.99k
            LOG(INFO) << "SegmentFileCollection.close.wait_dat_closed path="
198
4.99k
                      << writer->path().native()
199
4.99k
                      << " before_state=" << static_cast<int>(before_state)
200
4.99k
                      << " after_state=" << static_cast<int>(writer->state());
201
4.99k
        });
202
4.99k
        if (writer->state() != io::FileWriter::State::CLOSED) {
203
4.86k
            RETURN_IF_ERROR(writer->close());
204
4.86k
        }
205
4.99k
        TEST_SYNC_POINT_CALLBACK("SegmentFileCollection::close_file_writer", writer.get());
206
4.99k
    }
207
208
1.95k
    return Status::OK();
209
1.95k
}
210
211
0
Result<std::vector<size_t>> SegmentFileCollection::segments_file_size(int seg_id_offset) {
212
0
    std::lock_guard lock(_lock);
213
0
    if (!_closed) [[unlikely]] {
214
0
        DCHECK(false);
215
0
        return ResultError(Status::InternalError("get segments file size without closed"));
216
0
    }
217
218
0
    Status st;
219
0
    std::vector<size_t> seg_file_size(_file_writers.size(), 0);
220
0
    bool succ = std::all_of(_file_writers.begin(), _file_writers.end(), [&](auto&& it) {
221
0
        auto&& [seg_id, writer] = it;
222
223
0
        int idx = seg_id - seg_id_offset;
224
0
        if (idx >= seg_file_size.size()) [[unlikely]] {
225
0
            auto err_msg = fmt::format(
226
0
                    "invalid seg_id={} num_file_writers={} seg_id_offset={} path={}", seg_id,
227
0
                    seg_file_size.size(), seg_id_offset, writer->path().native());
228
0
            DCHECK(false) << err_msg;
229
0
            st = Status::InternalError(err_msg);
230
0
            return false;
231
0
        }
232
233
0
        auto& fsize = seg_file_size[idx];
234
0
        if (fsize != 0) {
235
            // File size should not been set
236
0
            auto err_msg =
237
0
                    fmt::format("duplicate seg_id={} path={}", seg_id, writer->path().native());
238
0
            DCHECK(false) << err_msg;
239
0
            st = Status::InternalError(err_msg);
240
0
            return false;
241
0
        }
242
243
0
        fsize = writer->bytes_appended();
244
0
        if (fsize <= 0) {
245
0
            auto err_msg =
246
0
                    fmt::format("invalid segment fsize={} path={}", fsize, writer->path().native());
247
0
            DCHECK(false) << err_msg;
248
0
            st = Status::InternalError(err_msg);
249
0
            return false;
250
0
        }
251
252
0
        return true;
253
0
    });
254
255
0
    if (succ) {
256
0
        return seg_file_size;
257
0
    }
258
259
0
    return ResultError(st);
260
0
}
261
262
2.05k
InvertedIndexFileCollection::~InvertedIndexFileCollection() = default;
263
264
642
Status InvertedIndexFileCollection::add(int seg_id, IndexFileWriterPtr&& index_writer) {
265
642
    std::lock_guard lock(_lock);
266
642
    if (_inverted_index_file_writers.find(seg_id) != _inverted_index_file_writers.end())
267
0
            [[unlikely]] {
268
0
        DCHECK(false);
269
0
        return Status::InternalError("The seg_id already exists, seg_id is {}", seg_id);
270
0
    }
271
642
    _inverted_index_file_writers.emplace(seg_id, std::move(index_writer));
272
642
    return Status::OK();
273
642
}
274
275
698
Status InvertedIndexFileCollection::begin_close() {
276
698
    std::lock_guard lock(_lock);
277
698
    for (auto&& [id, writer] : _inverted_index_file_writers) {
278
92
        RETURN_IF_ERROR(writer->begin_close());
279
92
        _total_size += writer->get_index_file_total_size();
280
92
    }
281
282
698
    return Status::OK();
283
698
}
284
285
1.95k
Status InvertedIndexFileCollection::finish_close() {
286
1.95k
    std::lock_guard lock(_lock);
287
1.95k
    for (auto&& [id, writer] : _inverted_index_file_writers) {
288
638
        RETURN_IF_ERROR(writer->finish_close());
289
638
    }
290
1.95k
    return Status::OK();
291
1.95k
}
292
293
Result<std::vector<const InvertedIndexFileInfo*>>
294
320
InvertedIndexFileCollection::inverted_index_file_info(int seg_id_offset) {
295
320
    std::lock_guard lock(_lock);
296
297
320
    Status st;
298
320
    std::vector<const InvertedIndexFileInfo*> idx_file_info(_inverted_index_file_writers.size());
299
320
    bool succ = std::all_of(
300
320
            _inverted_index_file_writers.begin(), _inverted_index_file_writers.end(),
301
540
            [&](auto&& it) {
302
540
                auto&& [seg_id, writer] = it;
303
304
540
                int idx = seg_id - seg_id_offset;
305
540
                if (idx >= idx_file_info.size()) [[unlikely]] {
306
0
                    auto err_msg =
307
0
                            fmt::format("invalid seg_id={} num_file_writers={} seg_id_offset={}",
308
0
                                        seg_id, idx_file_info.size(), seg_id_offset);
309
0
                    DCHECK(false) << err_msg;
310
0
                    st = Status::InternalError(err_msg);
311
0
                    return false;
312
0
                }
313
540
                idx_file_info[idx] = _inverted_index_file_writers[seg_id]->get_index_file_info();
314
540
                return true;
315
540
            });
316
317
320
    if (succ) {
318
320
        return idx_file_info;
319
320
    }
320
321
0
    return ResultError(st);
322
320
}
323
324
BaseBetaRowsetWriter::BaseBetaRowsetWriter()
325
2.05k
        : _num_segment(0),
326
2.05k
          _segment_start_id(0),
327
2.05k
          _num_rows_written(0),
328
2.05k
          _total_data_size(0),
329
2.05k
          _total_index_size(0),
330
2.05k
          _segment_creator(_context, _seg_files, _idx_files) {}
331
332
BetaRowsetWriter::BetaRowsetWriter(StorageEngine& engine)
333
2.05k
        : _engine(engine), _segcompaction_worker(std::make_shared<SegcompactionWorker>(this)) {}
334
335
20
RowBinlogRowsetWriter::RowBinlogRowsetWriter(StorageEngine& engine) : BetaRowsetWriter(engine) {}
336
337
2.05k
BaseBetaRowsetWriter::~BaseBetaRowsetWriter() {
338
2.05k
    if (!_already_built && _rowset_meta->is_local()) {
339
        // abnormal exit, remove all files generated
340
20
        auto& fs = io::global_local_filesystem();
341
24
        for (int i = _segment_start_id; i < _segment_creator.next_segment_id(); ++i) {
342
4
            std::string seg_path =
343
4
                    local_segment_path(_context.tablet_path, _context.rowset_id.to_string(), i);
344
            // Even if an error is encountered, these files that have not been cleaned up
345
            // will be cleaned up by the GC background. So here we only print the error
346
            // message when we encounter an error.
347
4
            WARN_IF_ERROR(fs->delete_file(seg_path),
348
4
                          fmt::format("Failed to delete file={}", seg_path));
349
4
        }
350
20
    }
351
2.05k
    if (_calc_delete_bitmap_token) {
352
18
        _calc_delete_bitmap_token->cancel();
353
18
    }
354
2.05k
}
355
356
2.05k
BetaRowsetWriter::~BetaRowsetWriter() {
357
    /* Note that segcompaction is async and in parallel with load job. So we should handle carefully
358
     * when the job is cancelled. Although it is meaningless to continue segcompaction when the job
359
     * is cancelled, the objects involved in the job should be preserved during segcompaction to
360
     * avoid crashs for memory issues. */
361
2.05k
    WARN_IF_ERROR(_wait_flying_segcompaction(), "segment compaction failed");
362
2.05k
}
363
364
2.05k
Status BaseBetaRowsetWriter::init(const RowsetWriterContext& rowset_writer_context) {
365
2.05k
    _context = rowset_writer_context;
366
2.05k
    DCHECK(_context.tablet_schema != nullptr);
367
2.05k
    _rowset_meta.reset(new RowsetMeta);
368
2.05k
    if (_context.storage_resource) {
369
6
        _rowset_meta->set_remote_storage_resource(*_context.storage_resource);
370
6
    }
371
2.05k
    _rowset_meta->set_rowset_id(_context.rowset_id);
372
2.05k
    _rowset_meta->set_partition_id(_context.partition_id);
373
2.05k
    _rowset_meta->set_tablet_id(_context.tablet_id);
374
2.05k
    _rowset_meta->set_db_id(_context.db_id);
375
2.05k
    _rowset_meta->set_table_id(_context.table_id);
376
2.05k
    _rowset_meta->set_index_id(_context.index_id);
377
2.05k
    _rowset_meta->set_tablet_schema_hash(_context.tablet_schema_hash);
378
2.05k
    _rowset_meta->set_rowset_type(_context.rowset_type);
379
2.05k
    _rowset_meta->set_rowset_state(_context.rowset_state);
380
2.05k
    _rowset_meta->set_segments_overlap(_context.segments_overlap);
381
2.05k
    if (_context.rowset_state == PREPARED || _context.rowset_state == COMMITTED) {
382
72
        _is_pending = true;
383
72
        _rowset_meta->set_txn_id(_context.txn_id);
384
72
        _rowset_meta->set_load_id(_context.load_id);
385
1.98k
    } else {
386
1.98k
        _rowset_meta->set_version(_context.version);
387
1.98k
        _rowset_meta->set_newest_write_timestamp(_context.newest_write_timestamp);
388
1.98k
    }
389
2.05k
    _rowset_meta->set_tablet_uid(_context.tablet_uid);
390
2.05k
    _rowset_meta->set_tablet_schema(_context.tablet_schema);
391
2.05k
    _rowset_meta->set_compaction_level(_context.compaction_level);
392
2.05k
    if (_context.write_binlog_opt().enable) {
393
20
        _rowset_meta->mark_row_binlog();
394
20
    }
395
2.05k
    _context.segment_collector = std::make_shared<SegmentCollectorT<BaseBetaRowsetWriter>>(this);
396
2.05k
    _context.file_writer_creator = std::make_shared<FileWriterCreatorT<BaseBetaRowsetWriter>>(this);
397
2.05k
    return Status::OK();
398
2.05k
}
399
400
2.90k
Status BaseBetaRowsetWriter::add_block(const Block* block) {
401
2.90k
    return _segment_creator.add_block(block);
402
2.90k
}
403
404
126
Status BaseBetaRowsetWriter::_generate_delete_bitmap(int32_t segment_id) {
405
126
    SCOPED_RAW_TIMER(&_delete_bitmap_ns);
406
126
    if (_context.is_transient_rowset_writer ||
407
126
        !_context.tablet->enable_unique_key_merge_on_write() ||
408
126
        (_context.partial_update_info && _context.partial_update_info->is_partial_update())) {
409
0
        return Status::OK();
410
0
    }
411
126
    std::vector<RowsetSharedPtr> specified_rowsets;
412
126
    {
413
126
        std::shared_lock meta_rlock(_context.tablet->get_header_lock());
414
126
        specified_rowsets =
415
126
                _context.tablet->get_rowset_by_ids(_context.mow_context->rowset_ids.get());
416
126
    }
417
418
    // Submit the entire delete bitmap calculation process to thread pool for async execution
419
    // This avoids blocking memtable flush thread while waiting for file upload to complete
420
    // The process includes: file_writer->close(), _build_tmp, load_segments, and calc_delete_bitmap
421
126
    return _calc_delete_bitmap_token->submit_func(
422
126
            [this, segment_id, specified_rowsets = std::move(specified_rowsets)]() -> Status {
423
126
                Status st = Status::OK();
424
                // Step 1: Close file_writer (must be done before load_segments)
425
126
                auto* file_writer = _seg_files.get(segment_id);
426
126
                if (file_writer && file_writer->state() != io::FileWriter::State::CLOSED) {
427
126
                    MonotonicStopWatch close_timer;
428
126
                    close_timer.start();
429
126
                    st = file_writer->close();
430
126
                    close_timer.stop();
431
432
126
                    auto close_time_ms = close_timer.elapsed_time_milliseconds();
433
126
                    if (close_time_ms > 1000) {
434
0
                        LOG(INFO) << "file_writer->close() took " << close_time_ms
435
0
                                  << "ms for segment_id=" << segment_id
436
0
                                  << ", tablet_id=" << _context.tablet_id
437
0
                                  << ", rowset_id=" << _context.rowset_id;
438
0
                    }
439
126
                    if (!st.ok()) {
440
0
                        return st;
441
0
                    }
442
126
                }
443
444
126
                OlapStopWatch watch;
445
                // Step 2: Build tmp rowset (needs file_writer to be closed)
446
126
                RowsetSharedPtr rowset_ptr;
447
126
                st = _build_tmp(rowset_ptr);
448
126
                if (!st.ok()) {
449
0
                    return st;
450
0
                }
451
452
                // Step 3: Load segments (needs file_writer to be closed and rowset to be built)
453
126
                auto* beta_rowset = reinterpret_cast<BetaRowset*>(rowset_ptr.get());
454
126
                std::vector<segment_v2::SegmentSharedPtr> segments;
455
126
                st = beta_rowset->load_segments(segment_id, segment_id + 1, &segments);
456
126
                if (!st.ok()) {
457
0
                    return st;
458
0
                }
459
460
                // Step 4: Calculate delete bitmap
461
126
                st = BaseTablet::calc_delete_bitmap(
462
126
                        _context.tablet, rowset_ptr, segments, specified_rowsets,
463
126
                        _context.mow_context->delete_bitmap, _context.mow_context->max_version,
464
126
                        nullptr, nullptr, nullptr);
465
126
                if (!st.ok()) {
466
0
                    return st;
467
0
                }
468
469
126
                size_t total_rows =
470
126
                        std::accumulate(segments.begin(), segments.end(), 0,
471
126
                                        [](size_t sum, const segment_v2::SegmentSharedPtr& s) {
472
126
                                            return sum += s->num_rows();
473
126
                                        });
474
126
                LOG(INFO) << "[Memtable Flush] construct delete bitmap tablet: "
475
126
                          << _context.tablet->tablet_id()
476
126
                          << ", rowset_ids: " << _context.mow_context->rowset_ids->size()
477
126
                          << ", cur max_version: " << _context.mow_context->max_version
478
126
                          << ", transaction_id: " << _context.mow_context->txn_id
479
126
                          << ", delete_bitmap_count: "
480
126
                          << _context.mow_context->delete_bitmap->get_delete_bitmap_count()
481
126
                          << ", delete_bitmap_cardinality: "
482
126
                          << _context.mow_context->delete_bitmap->cardinality()
483
126
                          << ", cost: " << watch.get_elapse_time_us()
484
126
                          << "(us), total rows: " << total_rows;
485
126
                return Status::OK();
486
126
            });
487
126
}
488
489
2.05k
Status BetaRowsetWriter::init(const RowsetWriterContext& rowset_writer_context) {
490
2.05k
    RETURN_IF_ERROR(BaseBetaRowsetWriter::init(rowset_writer_context));
491
2.05k
    if (_segcompaction_worker) {
492
2.05k
        _segcompaction_worker->init_mem_tracker(rowset_writer_context);
493
2.05k
    }
494
2.05k
    if (_context.mow_context != nullptr) {
495
18
        _calc_delete_bitmap_token = _engine.calc_delete_bitmap_executor_for_load()->create_token();
496
18
    }
497
2.05k
    return Status::OK();
498
2.05k
}
499
500
Status BetaRowsetWriter::_load_noncompacted_segment(segment_v2::SegmentSharedPtr& segment,
501
154
                                                    int32_t segment_id) {
502
154
    DCHECK(_rowset_meta->is_local());
503
154
    auto fs = _rowset_meta->fs();
504
154
    if (!fs) {
505
0
        return Status::Error<INIT_FAILED>(
506
0
                "BetaRowsetWriter::_load_noncompacted_segment _rowset_meta->fs get failed");
507
0
    }
508
154
    auto path =
509
154
            local_segment_path(_context.tablet_path, _context.rowset_id.to_string(), segment_id);
510
154
    io::FileReaderOptions reader_options {
511
154
            .cache_type =
512
154
                    _context.write_file_cache
513
154
                            ? (config::enable_file_cache ? io::FileCachePolicy::FILE_BLOCK_CACHE
514
0
                                                         : io::FileCachePolicy::NO_CACHE)
515
154
                            : io::FileCachePolicy::NO_CACHE,
516
154
            .is_doris_table = true,
517
154
            .cache_base_path {},
518
154
            .tablet_id = _rowset_meta->tablet_id(),
519
154
            .storage_resource_id = _rowset_meta->resource_id(),
520
154
    };
521
154
    auto s = segment_v2::Segment::open(fs, path, _rowset_meta->tablet_id(), segment_id, rowset_id(),
522
154
                                       _context.tablet_schema, reader_options, &segment);
523
154
    if (!s.ok()) {
524
0
        LOG(WARNING) << "failed to open segment. " << path << ":" << s;
525
0
        return s;
526
0
    }
527
154
    return Status::OK();
528
154
}
529
530
/* policy of segcompaction target selection:
531
 *  1. skip big segments
532
 *  2. if the consecutive smalls end up with a big, compact the smalls, except
533
 *     single small
534
 *  3. if the consecutive smalls end up with small, compact the smalls if the
535
 *     length is beyond (config::segcompaction_batch_size / 2)
536
 */
537
Status BetaRowsetWriter::_find_longest_consecutive_small_segment(
538
30
        SegCompactionCandidatesSharedPtr& segments) {
539
30
    segments = std::make_shared<SegCompactionCandidates>();
540
    // skip last (maybe active) segment
541
30
    int32_t last_segment = _num_segment - 1;
542
30
    size_t task_bytes = 0;
543
30
    uint32_t task_rows = 0;
544
30
    int32_t segid;
545
30
    for (segid = _segcompacted_point;
546
172
         segid < last_segment && segments->size() < config::segcompaction_batch_size; segid++) {
547
154
        segment_v2::SegmentSharedPtr segment;
548
154
        RETURN_IF_ERROR(_load_noncompacted_segment(segment, segid));
549
154
        const auto segment_rows = segment->num_rows();
550
154
        const auto segment_bytes = segment->file_reader()->size();
551
154
        bool is_large_segment = segment_rows > config::segcompaction_candidate_max_rows ||
552
154
                                segment_bytes > config::segcompaction_candidate_max_bytes;
553
154
        if (is_large_segment) {
554
28
            if (segid == _segcompacted_point) {
555
                // skip large segments at the front
556
16
                auto dst_seg_id = _num_segcompacted.load();
557
16
                RETURN_IF_ERROR(_rename_compacted_segment_plain(_segcompacted_point++));
558
16
                if (_segcompaction_worker->need_convert_delete_bitmap()) {
559
8
                    _segcompaction_worker->convert_segment_delete_bitmap(
560
8
                            _context.mow_context->delete_bitmap, segid, dst_seg_id);
561
8
                }
562
16
                continue;
563
16
            } else {
564
                // stop because we need consecutive segments
565
12
                break;
566
12
            }
567
28
        }
568
126
        bool is_task_full = task_rows + segment_rows > config::segcompaction_task_max_rows ||
569
126
                            task_bytes + segment_bytes > config::segcompaction_task_max_bytes;
570
126
        if (is_task_full) {
571
0
            break;
572
0
        }
573
126
        segments->push_back(segment);
574
126
        task_rows += segment->num_rows();
575
126
        task_bytes += segment->file_reader()->size();
576
126
    }
577
30
    size_t s = segments->size();
578
30
    if (segid == last_segment && s <= (config::segcompaction_batch_size / 2)) {
579
        // we didn't collect enough segments, better to do it in next
580
        // round to compact more at once
581
0
        segments->clear();
582
0
        return Status::OK();
583
0
    }
584
30
    if (s == 1) { // poor bachelor, let it go
585
8
        VLOG_DEBUG << "only one candidate segment";
586
8
        auto src_seg_id = _segcompacted_point.load();
587
8
        auto dst_seg_id = _num_segcompacted.load();
588
8
        RETURN_IF_ERROR(_rename_compacted_segment_plain(_segcompacted_point++));
589
8
        if (_segcompaction_worker->need_convert_delete_bitmap()) {
590
4
            _segcompaction_worker->convert_segment_delete_bitmap(
591
4
                    _context.mow_context->delete_bitmap, src_seg_id, dst_seg_id);
592
4
        }
593
8
        segments->clear();
594
8
        return Status::OK();
595
8
    }
596
22
    if (VLOG_DEBUG_IS_ON) {
597
0
        vlog_buffer.clear();
598
0
        for (auto& segment : (*segments.get())) {
599
0
            fmt::format_to(vlog_buffer, "[id:{} num_rows:{}]", segment->id(), segment->num_rows());
600
0
        }
601
0
        VLOG_DEBUG << "candidate segments num:" << s
602
0
                   << " list of candidates:" << fmt::to_string(vlog_buffer);
603
0
    }
604
22
    return Status::OK();
605
30
}
606
607
22
Status BetaRowsetWriter::_rename_compacted_segments(int64_t begin, int64_t end) {
608
22
    int ret;
609
22
    auto src_seg_path = BetaRowset::local_segment_path_segcompacted(_context.tablet_path,
610
22
                                                                    _context.rowset_id, begin, end);
611
22
    auto dst_seg_path = local_segment_path(_context.tablet_path, _context.rowset_id.to_string(),
612
22
                                           _num_segcompacted);
613
22
    ret = rename(src_seg_path.c_str(), dst_seg_path.c_str());
614
22
    if (ret) {
615
0
        return Status::Error<ROWSET_RENAME_FILE_FAILED>(
616
0
                "failed to rename {} to {}. ret:{}, errno:{}", src_seg_path, dst_seg_path, ret,
617
0
                errno);
618
0
    }
619
22
    RETURN_IF_ERROR(_remove_segment_footer_cache(_num_segcompacted, dst_seg_path));
620
621
    // rename inverted index files
622
22
    RETURN_IF_ERROR(_rename_compacted_indices(begin, end, 0));
623
624
22
    _num_segcompacted++;
625
22
    return Status::OK();
626
22
}
627
628
void BetaRowsetWriter::_clear_statistics_for_deleting_segments_unsafe(uint32_t begin,
629
86
                                                                      uint32_t end) {
630
86
    VLOG_DEBUG << "_segid_statistics_map clear record segid range from:" << begin << " to:" << end;
631
268
    for (uint32_t i = begin; i <= end; ++i) {
632
182
        _segid_statistics_map.erase(i);
633
182
    }
634
86
}
635
636
84
Status BetaRowsetWriter::_rename_compacted_segment_plain(uint32_t seg_id) {
637
84
    if (seg_id == _num_segcompacted) {
638
20
        ++_num_segcompacted;
639
20
        return Status::OK();
640
20
    }
641
642
64
    auto src_seg_path =
643
64
            local_segment_path(_context.tablet_path, _context.rowset_id.to_string(), seg_id);
644
64
    auto dst_seg_path = local_segment_path(_context.tablet_path, _context.rowset_id.to_string(),
645
64
                                           _num_segcompacted);
646
64
    VLOG_DEBUG << "segcompaction skip this segment. rename " << src_seg_path << " to "
647
0
               << dst_seg_path;
648
64
    {
649
64
        std::lock_guard<std::mutex> lock(_segid_statistics_map_mutex);
650
64
        DCHECK_EQ(_segid_statistics_map.find(seg_id) == _segid_statistics_map.end(), false);
651
64
        DCHECK_EQ(_segid_statistics_map.find(_num_segcompacted) == _segid_statistics_map.end(),
652
64
                  true);
653
64
        auto org = _segid_statistics_map[seg_id];
654
64
        _segid_statistics_map.emplace(_num_segcompacted, org);
655
64
        _clear_statistics_for_deleting_segments_unsafe(seg_id, seg_id);
656
64
    }
657
64
    int ret = rename(src_seg_path.c_str(), dst_seg_path.c_str());
658
64
    if (ret) {
659
0
        return Status::Error<ROWSET_RENAME_FILE_FAILED>(
660
0
                "failed to rename {} to {}. ret:{}, errno:{}", src_seg_path, dst_seg_path, ret,
661
0
                errno);
662
0
    }
663
664
64
    RETURN_IF_ERROR(_remove_segment_footer_cache(_num_segcompacted, dst_seg_path));
665
    // rename remaining inverted index files
666
64
    RETURN_IF_ERROR(_rename_compacted_indices(-1, -1, seg_id));
667
668
64
    ++_num_segcompacted;
669
64
    return Status::OK();
670
64
}
671
672
Status BetaRowsetWriter::_remove_segment_footer_cache(const uint32_t seg_id,
673
86
                                                      const std::string& segment_path) {
674
86
    auto* footer_page_cache = ExecEnv::GetInstance()->get_storage_page_cache();
675
86
    if (!footer_page_cache) {
676
0
        return Status::OK();
677
0
    }
678
679
86
    auto fs = _rowset_meta->fs();
680
86
    bool exists = false;
681
86
    RETURN_IF_ERROR(fs->exists(segment_path, &exists));
682
86
    if (exists) {
683
86
        io::FileReaderSPtr file_reader;
684
86
        io::FileReaderOptions reader_options {
685
86
                .cache_type = config::enable_file_cache ? io::FileCachePolicy::FILE_BLOCK_CACHE
686
86
                                                        : io::FileCachePolicy::NO_CACHE,
687
86
                .is_doris_table = true,
688
86
                .cache_base_path = "",
689
86
                .file_size = _rowset_meta->segment_file_size(static_cast<int>(seg_id)),
690
86
                .tablet_id = _rowset_meta->tablet_id(),
691
86
                .storage_resource_id = _rowset_meta->resource_id(),
692
86
        };
693
86
        RETURN_IF_ERROR(fs->open_file(segment_path, &file_reader, &reader_options));
694
86
        DCHECK(file_reader != nullptr);
695
86
        auto cache_key = segment_v2::Segment::get_segment_footer_cache_key(file_reader);
696
86
        footer_page_cache->erase(cache_key, segment_v2::PageTypePB::INDEX_PAGE);
697
86
    }
698
86
    return Status::OK();
699
86
}
700
701
86
Status BetaRowsetWriter::_rename_compacted_indices(int64_t begin, int64_t end, uint64_t seg_id) {
702
86
    int ret;
703
704
86
    auto src_seg_path = begin < 0 ? local_segment_path(_context.tablet_path,
705
64
                                                       _context.rowset_id.to_string(), seg_id)
706
86
                                  : BetaRowset::local_segment_path_segcompacted(
707
22
                                            _context.tablet_path, _context.rowset_id, begin, end);
708
86
    auto src_index_path_prefix = InvertedIndexDescriptor::get_index_file_path_prefix(src_seg_path);
709
86
    auto dst_seg_path = local_segment_path(_context.tablet_path, _context.rowset_id.to_string(),
710
86
                                           _num_segcompacted);
711
86
    auto dst_index_path_prefix = InvertedIndexDescriptor::get_index_file_path_prefix(dst_seg_path);
712
713
86
    if (_context.tablet_schema->get_inverted_index_storage_format() >=
714
86
        InvertedIndexStorageFormatPB::V2) {
715
44
        if (_context.tablet_schema->has_inverted_index() ||
716
44
            _context.tablet_schema->has_ann_index()) {
717
44
            auto src_idx_path =
718
44
                    InvertedIndexDescriptor::get_index_file_path_v2(src_index_path_prefix);
719
44
            auto dst_idx_path =
720
44
                    InvertedIndexDescriptor::get_index_file_path_v2(dst_index_path_prefix);
721
722
44
            ret = rename(src_idx_path.c_str(), dst_idx_path.c_str());
723
44
            if (ret) {
724
0
                return Status::Error<ROWSET_RENAME_FILE_FAILED>(
725
0
                        "failed to rename {} to {}. ret:{}, errno:{}", src_idx_path, dst_idx_path,
726
0
                        ret, errno);
727
0
            }
728
44
        }
729
44
    }
730
    // rename remaining inverted index files
731
300
    for (auto column : _context.tablet_schema->columns()) {
732
300
        auto index_infos = _context.tablet_schema->inverted_indexs(*column);
733
300
        for (const auto& index_info : index_infos) {
734
88
            auto index_id = index_info->index_id();
735
88
            if (_context.tablet_schema->get_inverted_index_storage_format() ==
736
88
                InvertedIndexStorageFormatPB::V1) {
737
0
                auto src_idx_path = InvertedIndexDescriptor::get_index_file_path_v1(
738
0
                        src_index_path_prefix, index_id, index_info->get_index_suffix());
739
0
                auto dst_idx_path = InvertedIndexDescriptor::get_index_file_path_v1(
740
0
                        dst_index_path_prefix, index_id, index_info->get_index_suffix());
741
0
                VLOG_DEBUG << "segcompaction skip this index. rename " << src_idx_path << " to "
742
0
                           << dst_idx_path;
743
0
                ret = rename(src_idx_path.c_str(), dst_idx_path.c_str());
744
0
                if (ret) {
745
0
                    return Status::Error<INVERTED_INDEX_RENAME_FILE_FAILED>(
746
0
                            "failed to rename {} to {}. ret:{}, errno:{}", src_idx_path,
747
0
                            dst_idx_path, ret, errno);
748
0
                }
749
0
            }
750
            // Erase the origin index file cache
751
88
            auto src_idx_cache_key = InvertedIndexDescriptor::get_index_file_cache_key(
752
88
                    src_index_path_prefix, index_id, index_info->get_index_suffix());
753
88
            auto dst_idx_cache_key = InvertedIndexDescriptor::get_index_file_cache_key(
754
88
                    dst_index_path_prefix, index_id, index_info->get_index_suffix());
755
88
            RETURN_IF_ERROR(InvertedIndexSearcherCache::instance()->erase(src_idx_cache_key));
756
88
            RETURN_IF_ERROR(InvertedIndexSearcherCache::instance()->erase(dst_idx_cache_key));
757
88
        }
758
300
    }
759
86
    return Status::OK();
760
86
}
761
762
2.59k
Status BetaRowsetWriter::_segcompaction_if_necessary() {
763
2.59k
    Status status = Status::OK();
764
    // if not doing segcompaction, just check segment number
765
2.59k
    if (!config::enable_segcompaction || !_context.enable_segcompaction ||
766
2.59k
        _context.tablet_schema->num_variant_columns() > 0) {
767
2.35k
        return _check_segment_number_limit(_num_segment);
768
2.35k
    }
769
    // leave _is_doing_segcompaction as the last condition
770
    // otherwise _segcompacting_cond will never get notified
771
242
    if (_is_doing_segcompaction.exchange(true)) {
772
0
        return status;
773
0
    }
774
242
    if (_segcompaction_status.load() != OK) {
775
0
        status = Status::Error<SEGCOMPACTION_FAILED>(
776
0
                "BetaRowsetWriter::_segcompaction_if_necessary meet invalid state, error code: {}",
777
0
                _segcompaction_status.load());
778
242
    } else {
779
242
        status = _check_segment_number_limit(_num_segcompacted);
780
242
    }
781
242
    if (status.ok() && (_num_segment - _segcompacted_point) >= config::segcompaction_batch_size) {
782
30
        SegCompactionCandidatesSharedPtr segments;
783
30
        status = _find_longest_consecutive_small_segment(segments);
784
30
        if (LIKELY(status.ok()) && (!segments->empty())) {
785
22
            LOG(INFO) << "submit segcompaction task, tablet_id:" << _context.tablet_id
786
22
                      << " rowset_id:" << _context.rowset_id << " segment num:" << _num_segment
787
22
                      << ", segcompacted_point:" << _segcompacted_point;
788
22
            status = _engine.submit_seg_compaction_task(_segcompaction_worker, segments);
789
22
            if (status.ok()) {
790
22
                return status;
791
22
            }
792
22
        }
793
30
    }
794
220
    {
795
220
        std::lock_guard lk(_is_doing_segcompaction_lock);
796
220
        _is_doing_segcompaction = false;
797
220
        _segcompacting_cond.notify_all();
798
220
    }
799
220
    return status;
800
242
}
801
802
1.25k
Status BetaRowsetWriter::_segcompaction_rename_last_segments() {
803
1.25k
    DCHECK_EQ(_is_doing_segcompaction, false);
804
1.25k
    if (!config::enable_segcompaction) {
805
288
        return Status::OK();
806
288
    }
807
970
    if (_segcompaction_status.load() != OK) {
808
0
        return Status::Error<SEGCOMPACTION_FAILED>(
809
0
                "BetaRowsetWriter::_segcompaction_rename_last_segments meet invalid state, error "
810
0
                "code: {}",
811
0
                _segcompaction_status.load());
812
0
    }
813
970
    if (!is_segcompacted() || _segcompacted_point == _num_segment) {
814
        // no need if never segcompact before or all segcompacted
815
952
        return Status::OK();
816
952
    }
817
    // currently we only rename remaining segments to reduce wait time
818
    // so that transaction can be committed ASAP
819
18
    VLOG_DEBUG << "segcompaction last few segments";
820
78
    for (int32_t segid = _segcompacted_point; segid < _num_segment; segid++) {
821
60
        auto dst_segid = _num_segcompacted.load();
822
60
        RETURN_IF_ERROR(_rename_compacted_segment_plain(_segcompacted_point++));
823
60
        if (_segcompaction_worker->need_convert_delete_bitmap()) {
824
32
            _segcompaction_worker->convert_segment_delete_bitmap(
825
32
                    _context.mow_context->delete_bitmap, segid, dst_segid);
826
32
        }
827
60
    }
828
18
    return Status::OK();
829
18
}
830
831
20
Status BaseBetaRowsetWriter::add_rowset(RowsetSharedPtr rowset) {
832
20
    assert(rowset->rowset_meta()->rowset_type() == BETA_ROWSET);
833
20
    RETURN_IF_ERROR(rowset->link_files_to(_context.tablet_path, _context.rowset_id));
834
20
    _num_rows_written += rowset->num_rows();
835
20
    const auto& rowset_meta = rowset->rowset_meta();
836
20
    auto index_size = rowset_meta->index_disk_size();
837
20
    auto total_size = rowset_meta->total_disk_size();
838
20
    auto data_size = rowset_meta->data_disk_size();
839
    // corrupted index size caused by bug before 2.1.5 or 3.0.0 version
840
    // try to get real index size from disk.
841
20
    if (index_size < 0 || index_size > total_size * 2) {
842
0
        LOG(ERROR) << "invalid index size:" << index_size << " total size:" << total_size
843
0
                   << " data size:" << data_size << " tablet:" << rowset_meta->tablet_id()
844
0
                   << " rowset:" << rowset_meta->rowset_id();
845
0
        index_size = 0;
846
0
        auto st = rowset->get_inverted_index_size(&index_size);
847
0
        if (!st.ok()) {
848
0
            if (!st.is<NOT_FOUND>()) {
849
0
                LOG(ERROR) << "failed to get inverted index size. res=" << st;
850
0
                return st;
851
0
            }
852
0
        }
853
0
    }
854
20
    _total_data_size += data_size;
855
20
    _total_index_size += index_size;
856
20
    _num_segment += cast_set<int32_t>(rowset->num_segments());
857
    // append key_bounds to current rowset
858
20
    RETURN_IF_ERROR(rowset->get_segments_key_bounds(&_segments_encoded_key_bounds));
859
20
    rowset->get_num_segment_rows(&_segment_num_rows);
860
20
    _segments_key_bounds_truncated = rowset->rowset_meta()->is_segments_key_bounds_truncated();
861
862
    // TODO update zonemap
863
20
    if (rowset->rowset_meta()->has_delete_predicate()) {
864
0
        _rowset_meta->set_delete_predicate(rowset->rowset_meta()->delete_predicate());
865
0
    }
866
    // Update the tablet schema in the rowset metadata if the tablet schema contains a variant.
867
    // During the build process, _context.tablet_schema will be used as the rowset schema.
868
    // This situation may arise in the event of a linked schema change. If this schema is not set,
869
    // the subcolumns of the variant will be lost.
870
20
    if (_context.tablet_schema->num_variant_columns() > 0 && rowset->tablet_schema() != nullptr) {
871
0
        _context.tablet_schema = rowset->tablet_schema();
872
0
    }
873
20
    return Status::OK();
874
20
}
875
876
0
Status BaseBetaRowsetWriter::add_rowset_for_linked_schema_change(RowsetSharedPtr rowset) {
877
    // TODO use schema_mapping to transfer zonemap
878
0
    return add_rowset(rowset);
879
0
}
880
881
2.26k
Status BaseBetaRowsetWriter::flush() {
882
2.26k
    return _segment_creator.flush();
883
2.26k
}
884
885
24
Status BaseBetaRowsetWriter::flush_memtable(Block* block, int32_t segment_id, int64_t* flush_size) {
886
24
    if (block->rows() == 0) {
887
0
        return Status::OK();
888
0
    }
889
890
24
    {
891
24
        SCOPED_RAW_TIMER(&_segment_writer_ns);
892
24
        RETURN_IF_ERROR(_segment_creator.flush_single_block(block, segment_id, flush_size));
893
24
    }
894
24
    return Status::OK();
895
24
}
896
897
12
Status BaseBetaRowsetWriter::flush_single_block(const Block* block) {
898
12
    return _segment_creator.flush_single_block(block);
899
12
}
900
901
2.05k
Status BetaRowsetWriter::_wait_flying_segcompaction() {
902
2.05k
    std::unique_lock<std::mutex> l(_is_doing_segcompaction_lock);
903
2.05k
    uint64_t begin_wait = GetCurrentTimeMicros();
904
2.05k
    while (_is_doing_segcompaction) {
905
        // change sync wait to async?
906
0
        _segcompacting_cond.wait(l);
907
0
    }
908
2.05k
    uint64_t elapsed = GetCurrentTimeMicros() - begin_wait;
909
2.05k
    if (elapsed >= MICROS_PER_SEC) {
910
0
        LOG(INFO) << "wait flying segcompaction finish time:" << elapsed << "us";
911
0
    }
912
2.05k
    if (_segcompaction_status.load() != OK) {
913
0
        return Status::Error<SEGCOMPACTION_FAILED>(
914
0
                "BetaRowsetWriter meet invalid state, error code: {}",
915
0
                _segcompaction_status.load());
916
0
    }
917
2.05k
    return Status::OK();
918
2.05k
}
919
920
76
RowsetSharedPtr BaseBetaRowsetWriter::manual_build(const RowsetMetaSharedPtr& spec_rowset_meta) {
921
76
    if (_rowset_meta->newest_write_timestamp() == -1) {
922
0
        _rowset_meta->set_newest_write_timestamp(UnixSeconds());
923
0
    }
924
925
76
    build_rowset_meta_with_spec_field(*_rowset_meta, *spec_rowset_meta);
926
76
    RowsetSharedPtr rowset;
927
76
    auto status = RowsetFactory::create_rowset(_context.tablet_schema, _context.tablet_path,
928
76
                                               _rowset_meta, &rowset);
929
76
    if (!status.ok()) {
930
0
        LOG(WARNING) << "rowset init failed when build new rowset, res=" << status;
931
0
        return nullptr;
932
0
    }
933
76
    _already_built = true;
934
76
    return rowset;
935
76
}
936
937
1.25k
Status BaseBetaRowsetWriter::_close_file_writers() {
938
    // Flush and close segment files
939
1.25k
    RETURN_NOT_OK_STATUS_WITH_WARN(_segment_creator.close(),
940
1.25k
                                   "failed to close segment creator when build new rowset");
941
1.25k
    return Status::OK();
942
1.25k
}
943
944
1.25k
Status BetaRowsetWriter::_close_file_writers() {
945
1.25k
    RETURN_IF_ERROR(BaseBetaRowsetWriter::_close_file_writers());
946
    // if _segment_start_id is not zero, that means it's a transient rowset writer for
947
    // MoW partial update, don't need to do segment compaction.
948
1.25k
    if (_segment_start_id == 0) {
949
1.25k
        if (_segcompaction_worker->cancel()) {
950
1.25k
            std::lock_guard lk(_is_doing_segcompaction_lock);
951
1.25k
            _is_doing_segcompaction = false;
952
1.25k
            _segcompacting_cond.notify_all();
953
1.25k
        } else {
954
0
            RETURN_NOT_OK_STATUS_WITH_WARN(_wait_flying_segcompaction(),
955
0
                                           "segcompaction failed when build new rowset");
956
0
        }
957
1.25k
        RETURN_NOT_OK_STATUS_WITH_WARN(_segcompaction_rename_last_segments(),
958
1.25k
                                       "rename last segments failed when build new rowset");
959
        // segcompaction worker would do file wrier's close function in compact_segments
960
1.25k
        if (auto& seg_comp_file_writer = _segcompaction_worker->get_file_writer();
961
1.25k
            nullptr != seg_comp_file_writer &&
962
1.25k
            seg_comp_file_writer->state() != io::FileWriter::State::CLOSED) {
963
0
            RETURN_NOT_OK_STATUS_WITH_WARN(seg_comp_file_writer->close(),
964
0
                                           "close segment compaction worker failed");
965
0
        }
966
        // process delete bitmap for mow table
967
1.25k
        if (is_segcompacted() && _segcompaction_worker->need_convert_delete_bitmap()) {
968
8
            auto converted_delete_bitmap = _segcompaction_worker->get_converted_delete_bitmap();
969
            // which means the segment compaction is triggerd
970
8
            if (converted_delete_bitmap != nullptr) {
971
8
                RowsetIdUnorderedSet rowsetids;
972
8
                rowsetids.insert(rowset_id());
973
8
                context().tablet->add_sentinel_mark_to_delete_bitmap(converted_delete_bitmap.get(),
974
8
                                                                     rowsetids);
975
8
                context().mow_context->delete_bitmap->remove({rowset_id(), 0, 0},
976
8
                                                             {rowset_id(), UINT32_MAX, INT64_MAX});
977
8
                context().mow_context->delete_bitmap->merge(*converted_delete_bitmap);
978
8
            }
979
8
        }
980
1.25k
    }
981
1.25k
    return Status::OK();
982
1.25k
}
983
984
1.95k
Status BetaRowsetWriter::build(RowsetSharedPtr& rowset) {
985
1.95k
    if (_calc_delete_bitmap_token != nullptr) {
986
18
        RETURN_IF_ERROR(_calc_delete_bitmap_token->wait());
987
18
    }
988
1.95k
    RETURN_IF_ERROR(_close_file_writers());
989
1.95k
    const auto total_segment_num = _num_segment - _segcompacted_point + 1 + _num_segcompacted;
990
1.95k
    RETURN_NOT_OK_STATUS_WITH_WARN(_check_segment_number_limit(total_segment_num),
991
1.95k
                                   "too many segments when build new rowset");
992
1.95k
    RETURN_IF_ERROR(_build_rowset_meta(_rowset_meta.get(), true));
993
1.95k
    if (_is_pending) {
994
60
        _rowset_meta->set_rowset_state(COMMITTED);
995
1.89k
    } else {
996
1.89k
        _rowset_meta->set_rowset_state(VISIBLE);
997
1.89k
    }
998
999
1.95k
    if (_rowset_meta->newest_write_timestamp() == -1) {
1000
1.43k
        _rowset_meta->set_newest_write_timestamp(UnixSeconds());
1001
1.43k
    }
1002
1003
1.95k
    _rowset_meta->set_tablet_schema(_context.tablet_schema);
1004
1005
    // If segment compaction occurs, the idx file info will become inaccurate.
1006
1.95k
    if ((_context.tablet_schema->has_inverted_index() || _context.tablet_schema->has_ann_index()) &&
1007
1.95k
        _num_segcompacted == 0) {
1008
320
        if (auto idx_files_info = _idx_files.inverted_index_file_info(_segment_start_id);
1009
320
            !idx_files_info.has_value()) [[unlikely]] {
1010
0
            LOG(ERROR) << "expected inverted index files info, but none presents: "
1011
0
                       << idx_files_info.error();
1012
320
        } else {
1013
320
            _rowset_meta->add_inverted_index_files_info(idx_files_info.value());
1014
320
        }
1015
320
    }
1016
1017
1.95k
    RETURN_NOT_OK_STATUS_WITH_WARN(
1018
1.95k
            RowsetFactory::create_rowset(_context.tablet_schema, _context.tablet_path, _rowset_meta,
1019
1.95k
                                         &rowset),
1020
1.95k
            "rowset init failed when build new rowset");
1021
1.95k
    _already_built = true;
1022
1.95k
    return Status::OK();
1023
1.95k
}
1024
1025
0
int64_t BaseBetaRowsetWriter::_num_seg() const {
1026
0
    return _num_segment;
1027
0
}
1028
1029
2.08k
int64_t BetaRowsetWriter::_num_seg() const {
1030
2.08k
    return is_segcompacted() ? _num_segcompacted : _num_segment;
1031
2.08k
}
1032
1033
2.08k
Status BaseBetaRowsetWriter::_build_rowset_meta(RowsetMeta* rowset_meta, bool check_segment_num) {
1034
2.08k
    int64_t num_rows_written = 0;
1035
2.08k
    int64_t total_data_size = 0;
1036
2.08k
    int64_t total_index_size = 0;
1037
2.08k
    std::vector<KeyBoundsPB> segments_encoded_key_bounds;
1038
2.08k
    std::vector<uint32_t> segment_rows;
1039
2.08k
    std::optional<bool> segments_key_bounds_truncated;
1040
2.08k
    {
1041
2.08k
        std::lock_guard<std::mutex> lock(_segid_statistics_map_mutex);
1042
3.19k
        for (const auto& itr : _segid_statistics_map) {
1043
3.19k
            num_rows_written += itr.second.row_num;
1044
3.19k
            total_data_size += itr.second.data_size;
1045
3.19k
            total_index_size += itr.second.index_size;
1046
3.19k
            segments_encoded_key_bounds.push_back(itr.second.key_bounds);
1047
            // segcompaction don't modify _segment_num_rows, so we need to get segment rows from _segid_statistics_map for load
1048
3.19k
            segment_rows.push_back(cast_set<uint32_t>(itr.second.row_num));
1049
3.19k
        }
1050
2.08k
        segments_key_bounds_truncated = _segments_key_bounds_truncated;
1051
2.08k
    }
1052
2.08k
    if (segment_rows.empty()) {
1053
        // vertical compaction and linked schema change will not record segment statistics,
1054
        // it will record segment rows in _segment_num_rows
1055
674
        RETURN_IF_ERROR(get_segment_num_rows(&segment_rows));
1056
674
    }
1057
1058
2.40k
    for (auto& key_bound : _segments_encoded_key_bounds) {
1059
2.40k
        segments_encoded_key_bounds.push_back(key_bound);
1060
2.40k
    }
1061
2.08k
    if (segments_key_bounds_truncated.has_value()) {
1062
952
        rowset_meta->set_segments_key_bounds_truncated(segments_key_bounds_truncated.value());
1063
952
    }
1064
2.08k
    rowset_meta->set_num_segment_rows(segment_rows);
1065
    // segment key bounds are empty in old version(before version 1.2.x). So we should not modify
1066
    // the overlap property when key bounds are empty.
1067
    // for mow table with cluster keys, the overlap is used for cluster keys,
1068
    // the key_bounds is primary keys
1069
2.08k
    if (!segments_encoded_key_bounds.empty() &&
1070
2.08k
        !is_segment_overlapping(segments_encoded_key_bounds) &&
1071
2.08k
        _context.tablet_schema->cluster_key_uids().empty()) {
1072
1.09k
        rowset_meta->set_segments_overlap(NONOVERLAPPING);
1073
1.09k
    }
1074
1075
2.08k
    auto segment_num = _num_seg();
1076
2.08k
    if (check_segment_num && config::check_segment_when_build_rowset_meta) {
1077
0
        auto segments_encoded_key_bounds_size = segments_encoded_key_bounds.size();
1078
0
        if (segments_encoded_key_bounds_size != segment_num) {
1079
0
            return Status::InternalError(
1080
0
                    "segments_encoded_key_bounds_size should  equal to _num_seg, "
1081
0
                    "segments_encoded_key_bounds_size "
1082
0
                    "is: {}, _num_seg is: {}",
1083
0
                    segments_encoded_key_bounds_size, segment_num);
1084
0
        }
1085
0
        if (segment_rows.size() != segment_num) {
1086
0
            return Status::InternalError(
1087
0
                    "segment_rows size should equal to _num_seg, segment_rows size is: {}, "
1088
0
                    "_num_seg is {}, tablet={}, rowset={}, txn={}",
1089
0
                    segment_rows.size(), segment_num, _context.tablet_id,
1090
0
                    _context.rowset_id.to_string(), _context.txn_id);
1091
0
        }
1092
0
    }
1093
1094
2.08k
    rowset_meta->set_num_segments(segment_num);
1095
2.08k
    rowset_meta->set_num_rows(num_rows_written + _num_rows_written);
1096
2.08k
    rowset_meta->set_total_disk_size(total_data_size + _total_data_size + total_index_size +
1097
2.08k
                                     _total_index_size);
1098
2.08k
    rowset_meta->set_data_disk_size(total_data_size + _total_data_size);
1099
2.08k
    rowset_meta->set_index_disk_size(total_index_size + _total_index_size);
1100
2.08k
    bool aggregate_key_bounds = config::enable_aggregate_non_mow_key_bounds &&
1101
2.08k
                                !_context.enable_unique_key_merge_on_write;
1102
2.08k
    rowset_meta->set_segments_key_bounds(segments_encoded_key_bounds, aggregate_key_bounds);
1103
    // TODO write zonemap to meta
1104
2.08k
    rowset_meta->set_empty((num_rows_written + _num_rows_written) == 0);
1105
2.08k
    rowset_meta->set_creation_time(time(nullptr));
1106
2.08k
    return Status::OK();
1107
2.08k
}
1108
1109
126
Status BaseBetaRowsetWriter::_build_tmp(RowsetSharedPtr& rowset_ptr) {
1110
126
    Status status;
1111
126
    std::shared_ptr<RowsetMeta> tmp_rs_meta = std::make_shared<RowsetMeta>();
1112
126
    tmp_rs_meta->init(_rowset_meta.get());
1113
1114
126
    status = _build_rowset_meta(tmp_rs_meta.get());
1115
126
    if (!status.ok()) {
1116
0
        LOG(WARNING) << "failed to build rowset meta, res=" << status;
1117
0
        return status;
1118
0
    }
1119
1120
126
    status = RowsetFactory::create_rowset(_context.tablet_schema, _context.tablet_path, tmp_rs_meta,
1121
126
                                          &rowset_ptr);
1122
126
    DBUG_EXECUTE_IF("BaseBetaRowsetWriter::_build_tmp.create_rowset_failed",
1123
126
                    { status = Status::InternalError("create rowset failed"); });
1124
126
    if (!status.ok()) {
1125
0
        LOG(WARNING) << "rowset init failed when build new rowset, res=" << status;
1126
0
        return status;
1127
0
    }
1128
126
    return Status::OK();
1129
126
}
1130
1131
Status BaseBetaRowsetWriter::_create_file_writer(const std::string& path,
1132
                                                 io::FileWriterPtr& file_writer,
1133
5.70k
                                                 FileType file_type) {
1134
5.70k
    io::FileWriterOptions opts = _context.get_file_writer_options(file_type);
1135
5.70k
    Status st = _context.fs()->create_file(path, &file_writer, &opts);
1136
5.70k
    if (!st.ok()) {
1137
0
        LOG(WARNING) << "failed to create writable file. path=" << path << ", err: " << st;
1138
0
        return st;
1139
0
    }
1140
1141
5.70k
    DCHECK(file_writer != nullptr);
1142
5.70k
    TEST_SYNC_POINT_CALLBACK("BaseBetaRowsetWriter::_create_file_writer", &path, &file_type,
1143
5.70k
                             file_writer.get(), &opts);
1144
5.70k
    return Status::OK();
1145
5.70k
}
1146
1147
Status BaseBetaRowsetWriter::create_file_writer(uint32_t segment_id, io::FileWriterPtr& file_writer,
1148
5.66k
                                                FileType file_type) {
1149
5.66k
    auto segment_path = _context.segment_path(segment_id);
1150
5.66k
    if (file_type == FileType::INVERTED_INDEX_FILE) {
1151
618
        std::string prefix =
1152
618
                std::string {InvertedIndexDescriptor::get_index_file_path_prefix(segment_path)};
1153
618
        std::string index_path = InvertedIndexDescriptor::get_index_file_path_v2(prefix);
1154
618
        return _create_file_writer(index_path, file_writer, file_type);
1155
5.04k
    } else if (file_type == FileType::SEGMENT_FILE) {
1156
5.04k
        return _create_file_writer(segment_path, file_writer, file_type);
1157
5.04k
    }
1158
0
    return Status::Error<ErrorCode::INTERNAL_ERROR>(
1159
0
            fmt::format("failed to create file = {}, file type = {}", segment_path, file_type));
1160
5.66k
}
1161
1162
Status BaseBetaRowsetWriter::create_index_file_writer(uint32_t segment_id,
1163
642
                                                      IndexFileWriterPtr* index_file_writer) {
1164
642
    RETURN_IF_ERROR(RowsetWriter::create_index_file_writer(segment_id, index_file_writer));
1165
    // used for inverted index format v1
1166
642
    (*index_file_writer)
1167
642
            ->set_file_writer_opts(_context.get_file_writer_options(FileType::INVERTED_INDEX_FILE));
1168
642
    TEST_SYNC_POINT_CALLBACK("BaseBetaRowsetWriter::create_inverted_index_file_writer", &segment_id,
1169
642
                             index_file_writer->get());
1170
642
    return Status::OK();
1171
642
}
1172
1173
Status BetaRowsetWriter::create_segment_writer_for_segcompaction(
1174
24
        std::unique_ptr<segment_v2::SegmentWriter>* writer, int64_t begin, int64_t end) {
1175
24
    DCHECK(begin >= 0 && end >= 0);
1176
24
    std::string path = BetaRowset::local_segment_path_segcompacted(_context.tablet_path,
1177
24
                                                                   _context.rowset_id, begin, end);
1178
24
    io::FileWriterPtr file_writer;
1179
24
    RETURN_IF_ERROR(_create_file_writer(path, file_writer, FileType::SEGMENT_FILE));
1180
1181
24
    IndexFileWriterPtr index_file_writer;
1182
24
    if (_context.tablet_schema->has_inverted_index() || _context.tablet_schema->has_ann_index()) {
1183
16
        io::FileWriterPtr idx_file_writer;
1184
16
        std::string prefix(InvertedIndexDescriptor::get_index_file_path_prefix(path));
1185
16
        if (_context.tablet_schema->get_inverted_index_storage_format() !=
1186
16
            InvertedIndexStorageFormatPB::V1) {
1187
16
            std::string index_path = InvertedIndexDescriptor::get_index_file_path_v2(prefix);
1188
16
            RETURN_IF_ERROR(_create_file_writer(index_path, idx_file_writer,
1189
16
                                                FileType::INVERTED_INDEX_FILE));
1190
16
        }
1191
16
        index_file_writer = std::make_unique<IndexFileWriter>(
1192
16
                _context.fs(), prefix, _context.rowset_id.to_string(), _num_segcompacted,
1193
16
                _context.tablet_schema->get_inverted_index_storage_format(),
1194
16
                std::move(idx_file_writer), true /* can_use_ram_dir */, _context.tablet_id);
1195
16
        index_file_writer->set_file_writer_opts(
1196
16
                _context.get_file_writer_options(FileType::INVERTED_INDEX_FILE));
1197
16
    }
1198
1199
24
    segment_v2::SegmentWriterOptions writer_options;
1200
24
    writer_options.enable_unique_key_merge_on_write = _context.enable_unique_key_merge_on_write;
1201
24
    writer_options.rowset_ctx = &_context;
1202
24
    writer_options.write_type = _context.write_type;
1203
24
    writer_options.write_type = DataWriteType::TYPE_COMPACTION;
1204
24
    writer_options.max_rows_per_segment = _context.max_rows_per_segment;
1205
24
    writer_options.mow_ctx = _context.mow_context;
1206
1207
24
    *writer = std::make_unique<segment_v2::SegmentWriter>(
1208
24
            file_writer.get(), _num_segcompacted, _context.tablet_schema, _context.tablet,
1209
24
            _context.data_dir, writer_options, index_file_writer.get());
1210
24
    if (auto& seg_writer = _segcompaction_worker->get_file_writer();
1211
24
        seg_writer != nullptr && seg_writer->state() != io::FileWriter::State::CLOSED) {
1212
0
        RETURN_IF_ERROR(_segcompaction_worker->get_file_writer()->close());
1213
0
    }
1214
24
    _segcompaction_worker->get_file_writer().reset(file_writer.release());
1215
24
    if (auto& idx_file_writer = _segcompaction_worker->get_inverted_index_file_writer();
1216
24
        idx_file_writer != nullptr) {
1217
0
        RETURN_IF_ERROR(idx_file_writer->begin_close());
1218
0
        RETURN_IF_ERROR(idx_file_writer->finish_close());
1219
0
    }
1220
24
    _segcompaction_worker->get_inverted_index_file_writer().reset(index_file_writer.release());
1221
24
    return Status::OK();
1222
24
}
1223
1224
0
Status BaseBetaRowsetWriter::_check_segment_number_limit(size_t segnum) {
1225
0
    DBUG_EXECUTE_IF("BetaRowsetWriter._check_segment_number_limit_too_many_segments",
1226
0
                    { segnum = dp->param("segnum", 1024); });
1227
0
    if (UNLIKELY(segnum > config::max_segment_num_per_rowset)) {
1228
0
        return Status::Error<TOO_MANY_SEGMENTS>(
1229
0
                "too many segments in rowset. tablet_id:{}, rowset_id:{}, max:{}, "
1230
0
                "_num_segment:{}, rowset_num_rows:{}. Please check if the bucket number is too "
1231
0
                "small or if the data is skewed.",
1232
0
                _context.tablet_id, _context.rowset_id.to_string(),
1233
0
                config::max_segment_num_per_rowset, _num_segment, get_rowset_num_rows());
1234
0
    }
1235
0
    return Status::OK();
1236
0
}
1237
1238
4.55k
Status BetaRowsetWriter::_check_segment_number_limit(size_t segnum) {
1239
4.55k
    DBUG_EXECUTE_IF("BetaRowsetWriter._check_segment_number_limit_too_many_segments",
1240
4.55k
                    { segnum = dp->param("segnum", 1024); });
1241
4.55k
    if (UNLIKELY(segnum > config::max_segment_num_per_rowset)) {
1242
0
        return Status::Error<TOO_MANY_SEGMENTS>(
1243
0
                "too many segments in rowset. tablet_id:{}, rowset_id:{}, max:{}, _num_segment:{}, "
1244
0
                "_segcompacted_point:{}, _num_segcompacted:{}, rowset_num_rows:{}. Please check if "
1245
0
                "the bucket number is too small or if the data is skewed.",
1246
0
                _context.tablet_id, _context.rowset_id.to_string(),
1247
0
                config::max_segment_num_per_rowset, _num_segment, _segcompacted_point,
1248
0
                _num_segcompacted, get_rowset_num_rows());
1249
0
    }
1250
4.55k
    return Status::OK();
1251
4.55k
}
1252
1253
2.59k
Status BaseBetaRowsetWriter::add_segment(uint32_t segment_id, const SegmentStatistics& segstat) {
1254
2.59k
    uint32_t segid_offset = segment_id - _segment_start_id;
1255
2.59k
    bool key_bounds_truncated = false;
1256
2.59k
    SegmentStatistics stored_segstat =
1257
2.59k
            copy_segment_statistics_with_truncated_key_bounds(segstat, key_bounds_truncated);
1258
2.59k
    {
1259
2.59k
        std::lock_guard<std::mutex> lock(_segid_statistics_map_mutex);
1260
2.59k
        CHECK_EQ(_segid_statistics_map.find(segment_id) == _segid_statistics_map.end(), true);
1261
2.59k
        _segid_statistics_map.emplace(segment_id, std::move(stored_segstat));
1262
2.59k
        if (segment_id >= _segment_num_rows.size()) {
1263
2.59k
            _segment_num_rows.resize(segment_id + 1);
1264
2.59k
        }
1265
2.59k
        _segment_num_rows[segid_offset] = cast_set<uint32_t>(segstat.row_num);
1266
2.59k
        if (key_bounds_truncated) {
1267
1.55k
            _segments_key_bounds_truncated = true;
1268
1.55k
        }
1269
2.59k
    }
1270
2.59k
    VLOG_DEBUG << "_segid_statistics_map add new record. segment_id:" << segment_id
1271
0
               << " row_num:" << segstat.row_num << " data_size:" << segstat.data_size
1272
0
               << " index_size:" << segstat.index_size;
1273
1274
2.59k
    {
1275
2.59k
        std::lock_guard<std::mutex> lock(_segment_set_mutex);
1276
2.59k
        _segment_set.add(segid_offset);
1277
5.19k
        while (_segment_set.contains(_num_segment)) {
1278
2.59k
            _num_segment++;
1279
2.59k
        }
1280
2.59k
    }
1281
1282
2.59k
    if (_context.mow_context != nullptr) {
1283
126
        RETURN_IF_ERROR(_generate_delete_bitmap(segment_id));
1284
126
    }
1285
2.59k
    return Status::OK();
1286
2.59k
}
1287
1288
2.59k
Status BetaRowsetWriter::add_segment(uint32_t segment_id, const SegmentStatistics& segstat) {
1289
2.59k
    RETURN_IF_ERROR(BaseBetaRowsetWriter::add_segment(segment_id, segstat));
1290
2.59k
    return _segcompaction_if_necessary();
1291
2.59k
}
1292
1293
Status BetaRowsetWriter::flush_segment_writer_for_segcompaction(
1294
        std::unique_ptr<segment_v2::SegmentWriter>* writer, uint64_t index_size,
1295
22
        KeyBoundsPB& key_bounds) {
1296
22
    uint32_t segid = (*writer)->get_segment_id();
1297
22
    uint32_t row_num = (*writer)->row_count();
1298
22
    uint64_t segment_size;
1299
1300
22
    auto s = (*writer)->finalize_footer(&segment_size);
1301
22
    if (!s.ok()) {
1302
0
        return Status::Error<WRITER_DATA_WRITE_ERROR>("failed to finalize segment: {}",
1303
0
                                                      s.to_string());
1304
0
    }
1305
22
    int64_t inverted_index_file_size = 0;
1306
22
    RETURN_IF_ERROR((*writer)->close_inverted_index(&inverted_index_file_size));
1307
1308
22
    SegmentStatistics segstat;
1309
22
    segstat.row_num = row_num;
1310
22
    segstat.data_size = segment_size;
1311
22
    segstat.index_size = inverted_index_file_size;
1312
22
    bool key_bounds_truncated = copy_key_bounds_with_truncation(key_bounds, &segstat.key_bounds);
1313
22
    {
1314
22
        std::lock_guard<std::mutex> lock(_segid_statistics_map_mutex);
1315
22
        CHECK_EQ(_segid_statistics_map.find(segid) == _segid_statistics_map.end(), true);
1316
22
        _segid_statistics_map.emplace(segid, std::move(segstat));
1317
22
        if (key_bounds_truncated) {
1318
4
            _segments_key_bounds_truncated = true;
1319
4
        }
1320
22
    }
1321
22
    VLOG_DEBUG << "_segid_statistics_map add new record. segid:" << segid << " row_num:" << row_num
1322
0
               << " data_size:" << PrettyPrinter::print_bytes(segment_size)
1323
0
               << " index_size:" << PrettyPrinter::print_bytes(inverted_index_file_size);
1324
1325
22
    writer->reset();
1326
1327
22
    return Status::OK();
1328
22
}
1329
1330
} // namespace doris