Coverage Report

Created: 2026-10-29 18:51

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/storage/rowset/rowset_writer_context.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 <glog/logging.h>
22
23
#include <functional>
24
#include <memory>
25
#include <mutex>
26
#include <optional>
27
#include <string_view>
28
#include <unordered_map>
29
#include <vector>
30
31
#include "cloud/config.h"
32
#include "common/status.h"
33
#include "io/fs/encrypted_fs_factory.h"
34
#include "io/fs/file_system.h"
35
#include "io/fs/file_writer.h"
36
#include "io/fs/local_file_system.h"
37
#include "io/fs/packed_file_system.h"
38
#include "runtime/exec_env.h"
39
#include "storage/changelog.h"
40
#include "storage/olap_define.h"
41
#include "storage/partial_update_info.h"
42
#include "storage/segment/historical_row_retriever.h"
43
#include "storage/segment/variant/variant_compaction_paths.h"
44
#include "storage/storage_policy.h"
45
#include "storage/tablet/tablet.h"
46
#include "storage/tablet/tablet_schema.h"
47
48
namespace doris {
49
50
class RowsetWriterContextBuilder;
51
using RowsetWriterContextBuilderSharedPtr = std::shared_ptr<RowsetWriterContextBuilder>;
52
class DataDir;
53
class Tablet;
54
class FileWriterCreator;
55
class SegmentCollector;
56
57
namespace segment_v2 {
58
struct HistoricalRowRetrieverContext;
59
}
60
61
struct RowsetWriterContext {
62
3.44k
    RowsetWriterContext() : schema_lock(new std::mutex) {
63
3.44k
        load_id.set_hi(0);
64
3.44k
        load_id.set_lo(0);
65
3.44k
    }
66
67
    RowsetId rowset_id;
68
    int64_t db_id {0};
69
    int64_t table_id {0};
70
    int64_t tablet_id {0};
71
    int32_t tablet_schema_hash {0};
72
    int64_t index_id {0};
73
    int64_t partition_id {0};
74
    RowsetTypePB rowset_type {BETA_ROWSET};
75
76
    TabletSchemaSPtr tablet_schema;
77
    // Set only by compaction, alongside its extended tablet_schema.
78
    VariantCompactionPathsSPtr variant_compaction_paths;
79
    // Immutable inverted-index file format inherited from the owner tablet.
80
    std::optional<InvertedIndexStorageFormatPB> inverted_index_storage_format;
81
    // Whether the owner tablet persists the format in its top-level metadata.
82
    // This is derived from TabletMeta and keeps rowset metadata consistent with it.
83
    bool persist_inverted_index_storage_format = false;
84
    // PREPARED/COMMITTED for pending rowset
85
    // VISIBLE for non-pending rowset
86
    RowsetStatePB rowset_state {PREPARED};
87
    // properties for non-pending rowset
88
    Version version {0, 0};
89
90
    // properties for pending rowset
91
    int64_t txn_id {0};
92
    int64_t txn_expiration {0}; // For cloud mode
93
    PUniqueId load_id;
94
    std::shared_ptr<AtomicStatus> load_cancel_status;
95
    TabletUid tablet_uid {0, 0};
96
    // indicate whether the data among segments is overlapping.
97
    // default is OVERLAP_UNKNOWN.
98
    SegmentsOverlapPB segments_overlap {OVERLAP_UNKNOWN};
99
    // segment file use uint32 to represent row number, therefore the maximum is UINT32_MAX.
100
    // the default is set to INT32_MAX to avoid overflow issue when casting from uint32_t to int.
101
    // test cases can change this value to control flush timing
102
    uint32_t max_rows_per_segment = INT32_MAX;
103
    // not owned, point to the data dir of this rowset
104
    // for checking disk capacity when write data to disk.
105
    // ATTN: not support for RowsetConvertor.
106
    // (because it hard to refactor, and RowsetConvertor will be deprecated in future)
107
    DataDir* data_dir = nullptr;
108
109
    int64_t newest_write_timestamp = -1;
110
    bool enable_unique_key_merge_on_write = false;
111
    // store column_unique_id to do index compaction
112
    std::set<int32_t> columns_to_do_index_compaction;
113
    // SNII only: (column_unique_id, index_id) pairs whose postings are produced
114
    // by index compaction. The segment writer raw-builds every OTHER SNII index
115
    // of the column, so one eligible and one new index on the same column can
116
    // coexist in a single pass. V2/V3 keep columns_to_do_index_compaction:
117
    // their per-column CLucene directories cannot split an index off a column.
118
    std::set<std::pair<int32_t, int64_t>> snii_indexes_to_do_compaction;
119
120
3.80k
    bool has_index_compaction() const {
121
3.80k
        return !columns_to_do_index_compaction.empty() || !snii_indexes_to_do_compaction.empty();
122
3.80k
    }
123
124
    DataWriteType write_type = DataWriteType::TYPE_DEFAULT;
125
    // need to figure out the sub type of compaction
126
    ReaderType compaction_type = ReaderType::UNKNOWN;
127
    BaseTabletSPtr tablet = nullptr;
128
129
    std::shared_ptr<MowContext> mow_context;
130
    std::shared_ptr<FileWriterCreator> file_writer_creator;
131
    std::shared_ptr<SegmentCollector> segment_collector;
132
133
    // memtable_on_sink_support_index_v2 = true, we will create SinkFileWriter to send inverted index file
134
    bool memtable_on_sink_support_index_v2 = false;
135
136
    /// begin file cache opts
137
    bool write_file_cache = false;
138
    bool is_hot_data = false;
139
    // Absolute timestamp (seconds since epoch) after which the cache blocks written by
140
    // this rowset stop being TTL protected; 0 means no TTL. Always set it from
141
    // BaseTablet::file_cache_ttl_expiration_time() so every writer agrees with the
142
    // deadline BlockFileCacheTtlMgr sweeps by.
143
    uint64_t file_cache_expiration_time = 0;
144
    uint64_t approximate_bytes_to_write = 0;
145
    // If true, compaction output only writes index files to file cache, not data files
146
    bool compaction_output_write_index_only = false;
147
    /// end file cache opts
148
149
    // segcompaction for this RowsetWriter, only enabled when importing data
150
    bool enable_segcompaction = false;
151
152
    std::shared_ptr<PartialUpdateInfo> partial_update_info;
153
154
    bool is_transient_rowset_writer = false;
155
156
    segment_v2::HistoricalRowRetrieverContext make_historical_row_retriever_context();
157
158
    // Intent flag: caller can actively turn merge-file feature on/off for this rowset.
159
    // This describes whether we *want* to try small-file merging.
160
    bool allow_packed_file = true;
161
162
    // Physical id of the first segment in this rowset. It can be nonzero for writers that
163
    // allocate segment ids from a configured range.
164
    int64_t first_segment_id = 0;
165
166
    // Effective flag: whether this context actually ends up using MergeFileSystem for writes.
167
    // This is decided inside fs() based on enable_merge_file plus other conditions
168
    // (cloud mode, S3 filesystem, V1 inverted index, global config, etc.), and once
169
    // set to true it remains stable even if config::enable_merge_file changes later.
170
    mutable bool packed_file_active = false;
171
172
    // Cached FileSystem instance to ensure consistency across multiple fs() calls.
173
    // This prevents creating multiple MergeFileSystem instances and ensures
174
    // packed_file_active flag remains consistent.
175
    mutable io::FileSystemSPtr _cached_fs = nullptr;
176
177
3
    void set_first_segment_id(int64_t segment_id) {
178
3
        DORIS_CHECK_GE(segment_id, 0);
179
3
        DORIS_CHECK(_cached_fs == nullptr);
180
3
        first_segment_id = segment_id;
181
3
    }
182
183
    // For collect segment statistics for compaction
184
    std::vector<RowsetReaderSharedPtr> input_rs_readers;
185
186
    // TODO(lihangyu) remove this lock
187
    // In semi-structure senario tablet_schema will be updated concurrently,
188
    // this lock need to be held when update.Use shared_ptr to avoid delete copy contructor
189
    std::shared_ptr<std::mutex> schema_lock;
190
191
    int64_t compaction_level = 0;
192
193
    // For local rowset
194
    std::string tablet_path;
195
196
    // For remote rowset
197
    std::optional<StorageResource> storage_resource;
198
199
    std::optional<EncryptionAlgorithmPB> encrypt_algorithm;
200
201
    std::string job_id;
202
203
    // Per-segment LSNs allocated before memtable flush. The same storage feeds
204
    // both the base row LSN column and changelog LSN column.
205
    std::shared_ptr<segment_v2::SegmentAllocatedLsnMap> allocated_lsn_map = nullptr;
206
    bool _need_allocate_lsn = false;
207
208
    void insert_segment_allocated_lsns(int64_t segment_id,
209
61
                                       ConstAllocatedLsnVectorSharedPtr allocated_lsns) {
210
61
        DCHECK(allocated_lsn_map != nullptr);
211
61
        allocated_lsn_map->insert_segment_allocated_lsns(segment_id, std::move(allocated_lsns));
212
61
    }
213
214
49
    void remove_segment_allocated_lsns(int64_t segment_id) {
215
49
        DCHECK(allocated_lsn_map != nullptr);
216
49
        allocated_lsn_map->remove_segment(segment_id);
217
49
    }
218
219
54
    ConstAllocatedLsnVectorSharedPtr get_segment_allocated_lsns(int64_t segment_id) const {
220
54
        DCHECK(allocated_lsn_map != nullptr);
221
54
        return allocated_lsn_map->get_segment_allocated_lsns(segment_id);
222
54
    }
223
224
67
    bool need_allocated_lsn() const { return _need_allocate_lsn; }
225
226
9.00k
    bool is_local_rowset() const { return !storage_resource; }
227
228
7.30k
    std::string segment_path(int seg_id) const {
229
7.30k
        if (is_local_rowset()) {
230
7.25k
            return local_segment_path(tablet_path, rowset_id.to_string(), seg_id);
231
7.25k
        } else {
232
44
            return storage_resource->remote_segment_path(tablet_id, rowset_id.to_string(), seg_id);
233
44
        }
234
7.30k
    }
235
236
4.31k
    io::FileSystemSPtr fs() const {
237
        // Return cached instance if available to ensure consistency across multiple calls
238
4.31k
        if (_cached_fs != nullptr) {
239
3.24k
            return _cached_fs;
240
3.24k
        }
241
242
1.07k
        auto fs = [this]() -> io::FileSystemSPtr {
243
1.07k
            if (is_local_rowset()) {
244
1.06k
                return io::global_local_filesystem();
245
1.06k
            } else {
246
9
                return storage_resource->fs;
247
9
            }
248
1.07k
        }();
249
250
1.07k
        bool is_s3_fs = fs->type() == io::FileSystemType::S3;
251
252
1.07k
        auto algorithm = encrypt_algorithm;
253
254
1.07k
        if (!algorithm.has_value()) {
255
#ifndef BE_TEST
256
            constexpr std::string_view msg =
257
                    "RowsetWriterContext::determine_encryption is not called when creating this "
258
                    "RowsetWriterContext, it will result in encrypted rowsets left unencrypted";
259
            auto st = Status::InternalError(msg);
260
261
            LOG(WARNING) << st;
262
            DCHECK(false) << st;
263
#else
264
917
            algorithm = EncryptionAlgorithmPB::PLAINTEXT;
265
917
#endif
266
917
        }
267
268
        // Apply packed file system first for write path if enabled
269
        // Create empty index_map for write path
270
        // Index information will be populated after write completes
271
1.07k
        bool has_v1_inverted_index = tablet_schema != nullptr &&
272
1.07k
                                     tablet_schema->has_inverted_index() &&
273
1.07k
                                     tablet_schema->get_inverted_index_storage_format() ==
274
230
                                             InvertedIndexStorageFormatPB::V1;
275
276
1.07k
        if (has_v1_inverted_index && allow_packed_file && config::enable_packed_file) {
277
9
            static constexpr std::string_view kMsg =
278
9
                    "Disable packed file for V1 inverted index tablet to avoid missing index "
279
9
                    "metadata (temporary workaround)";
280
9
            LOG(INFO) << kMsg << ", tablet_id=" << tablet_id << ", rowset_id=" << rowset_id;
281
9
        }
282
283
        // Only enable merge file for S3 file system, not for HDFS or other remote file systems
284
1.07k
        packed_file_active = allow_packed_file && config::is_cloud_mode() &&
285
1.07k
                             config::enable_packed_file && !has_v1_inverted_index && is_s3_fs;
286
287
1.07k
        if (packed_file_active) {
288
0
            io::PackedAppendContext append_info;
289
0
            append_info.tablet_id = tablet_id;
290
0
            append_info.rowset_id = rowset_id.to_string();
291
0
            append_info.first_segment_id = first_segment_id;
292
0
            append_info.txn_id = txn_id;
293
0
            append_info.expiration_time = file_cache_expiration_time;
294
0
            fs = std::make_shared<io::PackedFileSystem>(fs, append_info);
295
0
        }
296
297
        // Then apply encryption on top
298
1.07k
        if (algorithm.has_value()) {
299
1.07k
            fs = io::make_file_system(fs, algorithm.value());
300
1.07k
        }
301
302
        // Cache the result to ensure consistency across multiple calls
303
1.07k
        _cached_fs = fs;
304
1.07k
        return fs;
305
4.31k
    }
306
307
0
    io::FileSystem& fs_ref() const { return *fs(); }
308
309
4.27k
    io::FileWriterOptions get_file_writer_options(FileType file_type = FileType::SEGMENT_FILE) {
310
4.27k
        io::FileWriterOptions opts {.write_file_cache = write_file_cache,
311
4.27k
                                    .is_cold_data = is_hot_data,
312
4.27k
                                    .file_cache_expiration_time = file_cache_expiration_time,
313
4.27k
                                    .approximate_bytes_to_write = approximate_bytes_to_write};
314
315
4.27k
        if (config::enable_file_cache_write_index_file_only) {
316
20
            opts.allow_adaptive_file_cache_write = false;
317
20
            opts.approximate_bytes_to_write = 0;
318
20
            opts.write_file_cache = file_type == FileType::INVERTED_INDEX_FILE;
319
20
            return opts;
320
20
        }
321
322
4.25k
        if (compaction_output_write_index_only && file_type == FileType::SEGMENT_FILE) {
323
4
            opts.write_file_cache = false;
324
4
            opts.allow_adaptive_file_cache_write = false;
325
4
            opts.approximate_bytes_to_write = 0;
326
4
        }
327
328
4.25k
        return opts;
329
4.27k
    }
330
331
    struct ChangelogOptions {
332
    public:
333
        bool enable = false;
334
335
35
        void set_need_before(bool need_before) {
336
35
            this->_need_before = need_before;
337
35
            _segment_write_changelog_opt.write_before = need_before;
338
35
        }
339
340
171
        segment_v2::SegmentWriteChangelogOptions& write_changelog_config() {
341
171
            return _segment_write_changelog_opt;
342
171
        }
343
344
60
        const segment_v2::SegmentWriteChangelogOptions& write_changelog_config() const {
345
60
            return _segment_write_changelog_opt;
346
60
        }
347
348
    private:
349
        bool _need_before = false;
350
        segment_v2::SegmentWriteChangelogOptions _segment_write_changelog_opt;
351
    } _write_changelog_opt;
352
353
3.69k
    ChangelogOptions& write_changelog_opt() { return _write_changelog_opt; }
354
355
4.69k
    const ChangelogOptions& write_changelog_opt() const { return _write_changelog_opt; }
356
};
357
358
inline segment_v2::HistoricalRowRetrieverContext
359
73
RowsetWriterContext::make_historical_row_retriever_context() {
360
73
    return segment_v2::HistoricalRowRetrieverContext {
361
73
            .tablet = tablet,
362
73
            .tablet_schema = tablet_schema,
363
73
            .rowset_writer_ctx = this,
364
73
            .partial_update_info = partial_update_info,
365
73
            .is_transient_rowset_writer = is_transient_rowset_writer,
366
73
            .write_type = write_type};
367
73
}
368
369
} // namespace doris