Coverage Report

Created: 2026-10-23 20:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/storage/segment/vertical_segment_writer.h
Line
Count
Source
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
#pragma once
19
20
#include <gen_cpp/olap_file.pb.h>
21
#include <gen_cpp/segment_v2.pb.h>
22
23
#include <cstddef>
24
#include <cstdint>
25
#include <map>
26
#include <memory> // unique_ptr
27
#include <string>
28
#include <unordered_set>
29
#include <utility>
30
#include <vector>
31
32
#include "common/status.h" // Status
33
#include "storage/index/index_file_writer.h"
34
#include "storage/key/row_key_encoder.h"
35
#include "storage/olap_define.h"
36
#include "storage/segment/column_writer.h"
37
#include "storage/segment/segment_index_file_cache_loader.h"
38
#include "storage/tablet/tablet.h"
39
#include "storage/tablet/tablet_schema.h"
40
#include "util/faststring.h"
41
#include "util/slice.h"
42
43
namespace doris {
44
class Block;
45
46
// TODO(lingbin): Should be a conf that can be dynamically adjusted, or a member in the context
47
const uint32_t MAX_SEGMENT_SIZE = static_cast<uint32_t>(OLAP_MAX_COLUMN_SEGMENT_FILE_SIZE *
48
                                                        OLAP_COLUMN_FILE_SEGMENT_SIZE_SCALE);
49
class DataDir;
50
class MemTracker;
51
class ShortKeyIndexBuilder;
52
class PrimaryKeyIndexBuilder;
53
struct RowsetWriterContext;
54
55
namespace io {
56
class FileWriter;
57
class FileSystem;
58
} // namespace io
59
namespace segment_v2 {
60
class IndexFileWriter;
61
class VariantStatsCaculator;
62
63
class DerivedColumnGenerator;
64
// Matches block_transform.h: at most one derived column (the row-store column)
65
// for each flush, held as a {cid, generator} pair; null generator means none.
66
using DerivedColumn = std::pair<uint32_t, std::shared_ptr<const DerivedColumnGenerator>>;
67
68
struct VerticalSegmentWriterOptions {
69
    uint32_t num_rows_per_block = 1024;
70
    // Caps the rows in one segment. Only unit tests set it, to control how many
71
    // segments a write produces; production leaves it unlimited.
72
    uint32_t max_rows_per_segment = UINT32_MAX;
73
    bool enable_unique_key_merge_on_write = false;
74
    CompressionTypePB compression_type = UNKNOWN_COMPRESSION;
75
76
    RowsetWriterContext* rowset_ctx = nullptr;
77
    DataWriteType write_type = DataWriteType::TYPE_DEFAULT;
78
};
79
80
// A segment is written one column group at a time:
81
//
82
//      for (column_group : column_groups) {
83
//          writer.init(column_group, has_key);
84
//          writer.append_block(block, ...);   // any number of times
85
//          writer.finalize_columns(&index_size);
86
//      }
87
//      writer.finalize_footer(&file_size);
88
//
89
// Vertical compaction and segcompaction write several groups; init() is
90
// shorthand for one whole-schema group. finalize_columns() writes the
91
// data pages the group still buffers, then its indexes, and closes the group.
92
//
93
// write_block() takes a whole-schema group's rows all at once and writes each
94
// column's pages as soon as that column is done, so it opens the group itself
95
// and needs no init():
96
//
97
//      writer.set_derived_column(derived_column);   // only if there is one
98
//      writer.write_block(block, 0, num_rows);      // opens the group itself
99
//      writer.finalize_columns(&index_size);
100
//      writer.finalize_footer(&file_size);
101
class VerticalSegmentWriter {
102
public:
103
    explicit VerticalSegmentWriter(io::FileWriter* file_writer, uint32_t segment_id,
104
                                   TabletSchemaSPtr tablet_schema, BaseTabletSPtr tablet,
105
                                   DataDir* data_dir, const VerticalSegmentWriterOptions& opts,
106
                                   IndexFileWriter* index_file_writer);
107
    ~VerticalSegmentWriter();
108
109
    VerticalSegmentWriter(const VerticalSegmentWriter&) = delete;
110
    const VerticalSegmentWriter& operator=(const VerticalSegmentWriter&) = delete;
111
112
    Status init();
113
114
    // Opens a column group; the footer keeps every group's column metas. Pass
115
    // has_key=true for the group with the key columns, which comes first. A
116
    // keyless schema has no such group, so its first value group takes it.
117
    Status init(const std::vector<uint32_t>& col_ids, bool has_key);
118
119
    // Feeds the open group. Its column writers buffer the data pages until
120
    // finalize_columns(), so more rows can always follow.
121
    Status append_block(const Block* block, size_t row_pos, size_t num_rows);
122
123
    // Opens a whole-schema group and writes it in one call, one column at a time,
124
    // so only one column's pages are in memory.
125
    Status write_block(const Block* block, size_t row_pos, size_t num_rows);
126
127
    // Sets the row-store column for the next write_block, which pumps it from its
128
    // generator in batches instead of reading it from the block. append_block
129
    // ignores it.
130
193
    void set_derived_column(DerivedColumn derived_column) {
131
193
        _derived_column = std::move(derived_column);
132
193
    }
133
134
0
    [[nodiscard]] std::string data_dir_path() const {
135
0
        return _data_dir == nullptr ? "" : _data_dir->path();
136
0
    }
137
138
    // rows fed to the open group; zero again once the group is closed
139
8.64k
    [[nodiscard]] uint32_t num_rows_written() const { return _num_rows_written; }
140
141
    // the segment's row count, settled when the group holding the key columns is finalized
142
5.58k
    [[nodiscard]] uint32_t row_count() const { return _row_count; }
143
3.90k
    [[nodiscard]] uint32_t segment_id() const { return _segment_id; }
144
145
    // Size readings the append_block feed rolls a segment on: how many more
146
    // rows fit, what the segment holds right now, and the cluster key MOW
147
    // primary keys still waiting for the finalize-time sort.
148
    int64_t max_row_to_add(size_t row_avg_size_in_bytes);
149
    uint64_t estimate_segment_size();
150
0
    [[nodiscard]] uint64_t primary_keys_size() const { return _primary_keys_size; }
151
152
    // Closes the open group: its buffered data pages (skipped after write_block),
153
    // its index sections, and the key indexes if it holds the key columns. Ends
154
    // with clear(), so the next init() can follow.
155
    Status finalize_columns(uint64_t* index_size);
156
    // Ends the segment, once every group is in.
157
    Status finalize_footer(uint64_t* segment_file_size,
158
                           SegmentIndexFileCacheInfo* index_file_cache_info = nullptr);
159
160
    Slice min_encoded_key();
161
    Slice max_encoded_key();
162
163
    void clear();
164
165
2.46k
    Status close_inverted_index(int64_t* inverted_index_file_size) {
166
        // no inverted index
167
2.46k
        if (_index_file_writer == nullptr) {
168
2.10k
            *inverted_index_file_size = 0;
169
2.10k
            return Status::OK();
170
2.10k
        }
171
361
        RETURN_IF_ERROR(_index_file_writer->begin_close());
172
361
        *inverted_index_file_size = _index_file_writer->get_index_file_total_size();
173
361
        return Status::OK();
174
361
    }
175
176
private:
177
    friend class TestVerticalSegmentWriter;
178
    void _abandon_index_staging();
179
    void _init_column_meta(ColumnMetaPB* meta, uint32_t column_id, const TabletColumn& column,
180
                           const ColumnWriterOptions& opts);
181
    // pos is the column's place in the open group, which is what the column
182
    // writers are indexed by; cid is its schema column id, which only the
183
    // footer meta records.
184
    Status _create_column_writer(size_t pos, uint32_t cid, const TabletSchemaSPtr& tablet_schema);
185
    // Opens a column group without its column writers: init() then creates
186
    // them all at once, write_block() one at a time.
187
    Status _open_group(const std::vector<uint32_t>& col_ids, bool has_key);
188
    Status _create_writers(const TabletSchemaSPtr& tablet_schema,
189
                           const std::vector<uint32_t>& col_ids);
190
    std::vector<uint32_t> _all_column_ids() const;
191
    // bytes the key index builders hold
192
    uint64_t _key_index_size();
193
    // cluster key MOW: sorts the collected primary keys, hands them to the index
194
    // builder and releases them. The builder decides what reaches the file.
195
    Status _sort_primary_keys_into_index();
196
    Status _set_row_count();
197
    // Closes the open group's column writers and writes their data pages, after
198
    // checking the data dir has room for them.
199
    Status _finalize_columns_data();
200
    Status _write_data();
201
    Status _write_ordinal_index();
202
    Status _write_zone_map();
203
    Status _write_inverted_index();
204
    Status _write_ann_index();
205
    Status _write_bloom_filter_index();
206
    Status _write_short_key_index();
207
    Status _write_primary_key_index();
208
    Status _write_footer();
209
    Status _write_raw_data(const std::vector<Slice>& slices);
210
    void _set_min_max_key(const Slice& key);
211
    void _set_min_key(const Slice& key);
212
    void _set_max_key(const Slice& key);
213
    Status _append_generated_column(const DerivedColumnGenerator& generator, const Block& block,
214
                                    size_t row_pos, size_t num_rows, uint32_t cid);
215
    // Remembers the column if it is a cluster key. _generate_key_index
216
    // needs them in cluster key order, which is not the group's order.
217
    void _collect_cluster_key_column(uint32_t cid, const IColumn* column,
218
                                     std::map<uint32_t, const IColumn*>* cluster_key_columns);
219
    // The key columns are whole block columns, so rows are numbered from the
220
    // block's start: the batch is [row_pos, row_pos + num_rows).
221
    Status _generate_key_index(std::vector<const IColumn*>& key_columns, const IColumn* seq_column,
222
                               size_t row_pos, size_t num_rows,
223
                               const std::map<uint32_t, const IColumn*>& cluster_key_columns);
224
    Status _generate_primary_key_index(const std::vector<const IColumn*>& primary_key_columns,
225
                                       const IColumn* seq_column, size_t row_pos, size_t num_rows,
226
                                       bool need_sort);
227
    Status _generate_short_key_index(std::vector<const IColumn*>& key_columns, size_t row_pos,
228
                                     size_t num_rows, const std::vector<size_t>& short_key_pos);
229
    Status _check_column_writer_disk_capacity(size_t cid);
230
    Status _finalize_column_writer_and_update_meta(size_t cid);
231
232
396k
    bool _is_mow() {
233
396k
        return _tablet_schema->keys_type() == UNIQUE_KEYS && _opts.enable_unique_key_merge_on_write;
234
396k
    }
235
204k
    bool _is_mow_with_cluster_key() {
236
204k
        return _is_mow() && !_tablet_schema->cluster_key_uids().empty();
237
204k
    }
238
239
private:
240
    uint32_t _segment_id;
241
    TabletSchemaSPtr _tablet_schema;
242
    BaseTabletSPtr _tablet;
243
    DataDir* _data_dir = nullptr;
244
    VerticalSegmentWriterOptions _opts;
245
246
    // Not owned. owned by RowsetWriter or SegmentFlusher
247
    io::FileWriter* _file_writer = nullptr;
248
    // Not owned. owned by RowsetWriter or SegmentFlusher
249
    IndexFileWriter* _index_file_writer = nullptr;
250
251
    SegmentFooterPB _footer;
252
    SegmentIndexFileCacheInfo _index_file_cache_info;
253
    size_t _num_short_key_columns;
254
255
    std::unique_ptr<ShortKeyIndexBuilder> _short_key_index_builder;
256
    std::unique_ptr<PrimaryKeyIndexBuilder> _primary_key_index_builder;
257
    std::vector<std::unique_ptr<ColumnWriter>> _column_writers;
258
    std::unique_ptr<MemTracker> _mem_tracker;
259
260
    // used for building short key index or primary key index during vectorized write.
261
    // NOTE: must stay declared after _tablet_schema and _opts, the constructor
262
    // init list reads both through _is_mow().
263
    RowKeyEncoder _key_encoder;
264
    size_t _short_key_row_pos = 0;
265
266
    // The open column group: the columns it holds, and whether it is the one
267
    // carrying the key columns. A whole-schema group (init() or write_block) is
268
    // the degenerate case -- every column, has_key=true.
269
    std::vector<uint32_t> _column_ids;
270
    bool _has_key = true;
271
    // _num_rows_written means row count already written in this current column group
272
    uint32_t _num_rows_written = 0;
273
274
    // _row_count means total row count of this segment
275
    // In vertical compaction row count is recorded when key columns group finish
276
    //  and _num_rows_written will be updated in value column group
277
    uint32_t _row_count = 0;
278
279
    bool _is_first_row = true;
280
    faststring _min_key;
281
    faststring _max_key;
282
283
    // Cluster key MOW only: primary keys collected here until the group's rows
284
    // are all in, then _sort_primary_keys_into_index() hands them over. The
285
    // rowset writer rolls a segment on their byte count.
286
    std::vector<std::string> _primary_keys;
287
    uint64_t _primary_keys_size = 0;
288
    // variant statistics calculator for efficient stats collection; only the
289
    // compaction write type feeds it
290
    std::unique_ptr<VariantStatsCaculator> _variant_stats_calculator;
291
292
    // the derived column the transform chain hands off to write_block's bounded pump
293
    DerivedColumn _derived_column;
294
295
    // true once write_block has written the open group's data pages, so
296
    // finalize_columns() does not write them again; clear() resets it
297
    bool _columns_data_flushed = false;
298
};
299
300
} // namespace segment_v2
301
} // namespace doris