Coverage Report

Created: 2026-08-03 13:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/storage/rowset/rowset_meta.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/rowset_meta.h"
19
20
#include <gen_cpp/olap_file.pb.h>
21
#include <glog/logging.h>
22
23
#include <algorithm>
24
#include <iterator>
25
#include <memory>
26
#include <random>
27
28
#include "cloud/cloud_storage_engine.h"
29
#include "common/logging.h"
30
#include "common/status.h"
31
#include "cpp/sync_point.h"
32
#include "exec/common/variant_util.h"
33
#include "google/protobuf/util/message_differencer.h"
34
#include "io/fs/encrypted_fs_factory.h"
35
#include "io/fs/file_system.h"
36
#include "io/fs/file_writer.h"
37
#include "io/fs/local_file_system.h"
38
#include "io/fs/packed_file_manager.h"
39
#include "io/fs/packed_file_system.h"
40
#include "json2pb/json_to_pb.h"
41
#include "json2pb/pb_to_json.h"
42
#include "runtime/exec_env.h"
43
#include "storage/olap_common.h"
44
#include "storage/storage_policy.h"
45
#include "storage/tablet/base_tablet.h"
46
#include "storage/tablet/tablet_fwd.h"
47
#include "storage/tablet/tablet_schema.h"
48
#include "storage/tablet/tablet_schema_cache.h"
49
#include "util/lru_cache.h"
50
51
namespace doris {
52
53
746k
RowsetMeta::~RowsetMeta() {
54
746k
    if (_handle) {
55
733k
        TabletSchemaCache::instance()->release(_handle);
56
733k
    }
57
746k
}
58
59
67.8k
bool RowsetMeta::init(std::string_view pb_rowset_meta) {
60
67.8k
    bool ret = _deserialize_from_pb(pb_rowset_meta);
61
67.8k
    if (!ret) {
62
1
        return false;
63
1
    }
64
67.8k
    _init();
65
67.8k
    return true;
66
67.8k
}
67
68
36.4k
bool RowsetMeta::init(const RowsetMeta* rowset_meta) {
69
36.4k
    RowsetMetaPB rowset_meta_pb;
70
36.4k
    rowset_meta->to_rowset_pb(&rowset_meta_pb);
71
36.4k
    return init_from_pb(rowset_meta_pb);
72
36.4k
}
73
74
873k
bool RowsetMeta::init_from_pb(const RowsetMetaPB& rowset_meta_pb) {
75
873k
    if (rowset_meta_pb.has_tablet_schema()) {
76
864k
        set_tablet_schema(rowset_meta_pb.tablet_schema());
77
864k
    }
78
    // Release ownership of TabletSchemaPB from `rowset_meta_pb` and then set it back to `rowset_meta_pb`,
79
    // this won't break const semantics of `rowset_meta_pb`, because `rowset_meta_pb` is not changed
80
    // before and after call this method.
81
873k
    auto& mut_rowset_meta_pb = const_cast<RowsetMetaPB&>(rowset_meta_pb);
82
873k
    auto* schema = mut_rowset_meta_pb.release_tablet_schema();
83
873k
    _rowset_meta_pb = mut_rowset_meta_pb;
84
873k
    mut_rowset_meta_pb.set_allocated_tablet_schema(schema);
85
873k
    _init();
86
873k
    return true;
87
873k
}
88
89
78
bool RowsetMeta::init_from_json(const std::string& json_rowset_meta) {
90
78
    bool ret = json2pb::JsonToProtoMessage(json_rowset_meta, &_rowset_meta_pb);
91
78
    if (!ret) {
92
1
        return false;
93
1
    }
94
77
    _init();
95
77
    return true;
96
78
}
97
98
0
bool RowsetMeta::json_rowset_meta(std::string* json_rowset_meta) {
99
0
    json2pb::Pb2JsonOptions json_options;
100
0
    json_options.pretty_json = true;
101
0
    bool ret = json2pb::ProtoMessageToJson(_rowset_meta_pb, json_rowset_meta, json_options);
102
0
    return ret;
103
0
}
104
105
1.80M
io::FileSystemSPtr RowsetMeta::physical_fs() {
106
1.80M
    if (is_local()) {
107
93.9k
        return io::global_local_filesystem();
108
93.9k
    }
109
110
1.70M
    auto storage_resource = remote_storage_resource();
111
1.71M
    if (storage_resource) {
112
1.71M
        return storage_resource.value()->fs;
113
18.4E
    } else {
114
18.4E
        LOG(WARNING) << storage_resource.error();
115
18.4E
        return nullptr;
116
18.4E
    }
117
1.70M
}
118
119
1.79M
io::FileSystemSPtr RowsetMeta::fs() {
120
1.79M
    auto fs = physical_fs();
121
122
1.79M
#ifndef BE_TEST
123
1.79M
    auto algorithm = _determine_encryption_once.call([this]() -> Result<EncryptionAlgorithmPB> {
124
98.2k
        auto maybe_tablet = ExecEnv::get_tablet(tablet_id());
125
98.2k
        if (!maybe_tablet) {
126
0
            LOG(WARNING) << "get tablet failed: " << maybe_tablet.error();
127
0
            return ResultError(maybe_tablet.error());
128
0
        }
129
98.2k
        auto tablet = maybe_tablet.value();
130
98.2k
        return tablet->tablet_meta()->encryption_algorithm();
131
98.2k
    });
132
1.79M
    if (!algorithm.has_value()) {
133
        // TODO: return a Result<FileSystemSPtr> in this method?
134
0
        return nullptr;
135
0
    }
136
137
    // Apply packed file system first if enabled and index_map is not empty
138
1.79M
    io::FileSystemSPtr wrapped = fs;
139
1.79M
    if (_rowset_meta_pb.packed_slice_locations_size() > 0) {
140
0
        std::unordered_map<std::string, io::PackedSliceLocation> index_map;
141
0
        for (const auto& [path, index_pb] : _rowset_meta_pb.packed_slice_locations()) {
142
0
            io::PackedSliceLocation index;
143
0
            index.packed_file_path = index_pb.packed_file_path();
144
0
            index.offset = index_pb.offset();
145
0
            index.size = index_pb.size();
146
0
            index.packed_file_size =
147
0
                    index_pb.has_packed_file_size() ? index_pb.packed_file_size() : -1;
148
0
            index.tablet_id = tablet_id();
149
0
            index.rowset_id = _rowset_id.to_string();
150
0
            index.resource_id = wrapped->id();
151
0
            index_map[path] = index;
152
0
        }
153
0
        if (!index_map.empty()) {
154
0
            io::PackedAppendContext append_info;
155
0
            append_info.tablet_id = tablet_id();
156
0
            append_info.rowset_id = _rowset_id.to_string();
157
0
            append_info.txn_id = txn_id();
158
0
            wrapped = std::make_shared<io::PackedFileSystem>(wrapped, index_map, append_info);
159
0
        }
160
0
    }
161
162
    // Then apply encryption on top
163
1.79M
    wrapped = io::make_file_system(wrapped, algorithm.value());
164
1.79M
    return wrapped;
165
#else
166
    return fs;
167
#endif
168
1.79M
}
169
170
3.50M
Result<const StorageResource*> RowsetMeta::remote_storage_resource() {
171
3.50M
    if (is_local()) {
172
0
        return ResultError(Status::InternalError<false>(
173
0
                "local rowset has no storage resource. tablet_id={} rowset_id={}", tablet_id(),
174
0
                _rowset_id.to_string()));
175
0
    }
176
177
3.50M
    if (!_storage_resource.fs) {
178
25.6k
        if (auto storage_resource = get_storage_resource(resource_id())) {
179
25.6k
            _storage_resource = std::move(storage_resource->first);
180
25.6k
        } else {
181
50
            if (config::is_cloud_mode()) {
182
                // When creating a new cluster or creating a storage resource, BE may not sync storage resource,
183
                // at the moment a query is coming, the BetaRowsetReader call loadSegment and use this method
184
                // to get the storage resource, so we need to sync storage resource here.
185
50
                ExecEnv::GetInstance()->storage_engine().to_cloud().sync_storage_vault();
186
50
                if (auto retry_resource = get_storage_resource(resource_id())) {
187
0
                    _storage_resource = std::move(retry_resource->first);
188
0
                    return &_storage_resource;
189
0
                }
190
50
            }
191
3
            return ResultError(Status::InternalError<false>(
192
3
                    "cannot find storage resource. resource_id={}", resource_id()));
193
3
        }
194
25.6k
    }
195
3.50M
    return &_storage_resource;
196
3.50M
}
197
198
195k
void RowsetMeta::set_remote_storage_resource(StorageResource resource) {
199
195k
    _storage_resource = std::move(resource);
200
195k
    _rowset_meta_pb.set_resource_id(_storage_resource.fs->id());
201
195k
}
202
203
1.30M
bool RowsetMeta::has_variant_type_in_schema() const {
204
1.30M
    return _schema && _schema->num_variant_columns() > 0;
205
1.30M
}
206
207
1.30M
void RowsetMeta::to_rowset_pb(RowsetMetaPB* rs_meta_pb, bool skip_schema) const {
208
1.30M
    *rs_meta_pb = _rowset_meta_pb;
209
1.30M
    if (_schema) [[likely]] {
210
1.29M
        rs_meta_pb->set_schema_version(_schema->schema_version());
211
1.29M
        if (!skip_schema) {
212
            // For cloud, separate tablet schema from rowset meta to reduce persistent size.
213
1.04M
            _schema->to_schema_pb(rs_meta_pb->mutable_tablet_schema());
214
1.04M
        }
215
1.29M
    }
216
1.30M
    rs_meta_pb->set_has_variant_type_in_schema(has_variant_type_in_schema());
217
1.30M
}
218
219
783k
RowsetMetaPB RowsetMeta::get_rowset_pb(bool skip_schema) const {
220
783k
    RowsetMetaPB rowset_meta_pb;
221
783k
    to_rowset_pb(&rowset_meta_pb, skip_schema);
222
783k
    return rowset_meta_pb;
223
783k
}
224
225
520k
void RowsetMeta::set_tablet_schema(const TabletSchemaSPtr& tablet_schema) {
226
520k
    if (_handle) {
227
261k
        TabletSchemaCache::instance()->release(_handle);
228
261k
    }
229
520k
    auto pair = TabletSchemaCache::instance()->insert(tablet_schema->to_key());
230
520k
    _handle = pair.first;
231
520k
    _schema = pair.second;
232
520k
}
233
234
931k
void RowsetMeta::set_tablet_schema(const TabletSchemaPB& tablet_schema) {
235
931k
    if (_handle) {
236
0
        TabletSchemaCache::instance()->release(_handle);
237
0
    }
238
931k
    auto pair = TabletSchemaCache::instance()->insert(
239
931k
            TabletSchema::deterministic_string_serialize(tablet_schema));
240
931k
    _handle = pair.first;
241
931k
    _schema = pair.second;
242
931k
}
243
244
67.8k
bool RowsetMeta::_deserialize_from_pb(std::string_view value) {
245
67.8k
    if (!_rowset_meta_pb.ParseFromArray(value.data(), cast_set<int32_t>(value.size()))) {
246
1
        _rowset_meta_pb.Clear();
247
1
        return false;
248
1
    }
249
67.8k
    if (_rowset_meta_pb.has_tablet_schema()) {
250
67.8k
        set_tablet_schema(_rowset_meta_pb.tablet_schema());
251
67.8k
        _rowset_meta_pb.set_allocated_tablet_schema(nullptr);
252
67.8k
    }
253
67.8k
    return true;
254
67.8k
}
255
256
6
bool RowsetMeta::_serialize_to_pb(std::string* value) {
257
6
    if (value == nullptr) {
258
0
        return false;
259
0
    }
260
6
    RowsetMetaPB rowset_meta_pb = _rowset_meta_pb;
261
6
    if (_schema) {
262
0
        _schema->to_schema_pb(rowset_meta_pb.mutable_tablet_schema());
263
0
    }
264
6
    return rowset_meta_pb.SerializeToString(value);
265
6
}
266
267
941k
void RowsetMeta::_init() {
268
941k
    if (_rowset_meta_pb.rowset_id() > 0) {
269
3.69k
        _rowset_id.init(_rowset_meta_pb.rowset_id());
270
937k
    } else {
271
937k
        _rowset_id.init(_rowset_meta_pb.rowset_id_v2());
272
937k
    }
273
941k
    _validate_segment_ids();
274
941k
    update_metadata_size();
275
941k
}
276
277
982k
void RowsetMeta::_validate_segment_ids() const {
278
982k
    if (!has_segment_ids()) {
279
938k
        return;
280
938k
    }
281
44.4k
    DORIS_CHECK_EQ(_rowset_meta_pb.segment_ids_size(), _rowset_meta_pb.num_segments());
282
44.4k
    int64_t prev_segment_id = -1;
283
45.6k
    for (const auto segment_id : _rowset_meta_pb.segment_ids()) {
284
45.6k
        DORIS_CHECK_GE(segment_id, 0);
285
45.6k
        DORIS_CHECK_GT(segment_id, prev_segment_id);
286
45.6k
        prev_segment_id = segment_id;
287
45.6k
    }
288
44.4k
}
289
290
43.1k
void RowsetMeta::set_segment_ids(const std::vector<int64_t>& segment_ids) {
291
43.1k
    _rowset_meta_pb.mutable_segment_ids()->Assign(segment_ids.begin(), segment_ids.end());
292
43.1k
    set_num_segments(cast_set<int64_t>(segment_ids.size()));
293
43.1k
    _validate_segment_ids();
294
43.1k
}
295
296
37.2k
size_t RowsetMeta::position_of(int64_t seg_id) const {
297
37.2k
    DORIS_CHECK_GE(seg_id, 0);
298
37.2k
    if (!has_segment_ids()) {
299
885
        DORIS_CHECK_LT(seg_id, num_segments());
300
885
        return cast_set<size_t>(seg_id);
301
885
    }
302
36.3k
    const auto& segment_ids = _rowset_meta_pb.segment_ids();
303
36.3k
    auto it = std::lower_bound(segment_ids.begin(), segment_ids.end(), seg_id);
304
36.3k
    DORIS_CHECK(it != segment_ids.end());
305
36.3k
    DORIS_CHECK_EQ(*it, seg_id);
306
36.3k
    return cast_set<size_t>(std::distance(segment_ids.begin(), it));
307
37.2k
}
308
309
199k
void RowsetMeta::add_segments_file_size(const std::vector<size_t>& seg_file_size) {
310
199k
    _rowset_meta_pb.set_enable_segments_file_size(true);
311
199k
    for (auto fsize : seg_file_size) {
312
63.3k
        _rowset_meta_pb.add_segments_file_size(fsize);
313
63.3k
    }
314
199k
}
315
316
1.92M
int64_t RowsetMeta::segment_file_size_by_pos(size_t pos) const {
317
18.4E
    DCHECK(_rowset_meta_pb.segments_file_size().empty() ||
318
18.4E
           _rowset_meta_pb.segments_file_size_size() > cast_set<int>(pos))
319
18.4E
            << _rowset_meta_pb.segments_file_size_size() << ' ' << pos;
320
1.92M
    return _rowset_meta_pb.enable_segments_file_size()
321
1.92M
                   ? (_rowset_meta_pb.segments_file_size_size() > cast_set<int>(pos)
322
1.82M
                              ? _rowset_meta_pb.segments_file_size(cast_set<int>(pos))
323
18.4E
                              : -1)
324
1.92M
                   : -1;
325
1.92M
}
326
327
void RowsetMeta::set_segments_key_bounds(const std::vector<KeyBoundsPB>& segments_key_bounds,
328
298k
                                         bool aggregate_into_single) {
329
298k
    _rowset_meta_pb.clear_segments_key_bounds();
330
298k
    bool did_aggregate = aggregate_into_single && !segments_key_bounds.empty();
331
298k
    if (did_aggregate) {
332
45.8k
        const std::string* overall_min = &segments_key_bounds.front().min_key();
333
45.8k
        const std::string* overall_max = &segments_key_bounds.front().max_key();
334
48.3k
        for (const KeyBoundsPB& key_bounds : segments_key_bounds) {
335
48.3k
            if (key_bounds.min_key() < *overall_min) {
336
4
                overall_min = &key_bounds.min_key();
337
4
            }
338
48.3k
            if (key_bounds.max_key() > *overall_max) {
339
1.83k
                overall_max = &key_bounds.max_key();
340
1.83k
            }
341
48.3k
        }
342
45.8k
        KeyBoundsPB* aggregated = _rowset_meta_pb.add_segments_key_bounds();
343
45.8k
        aggregated->set_min_key(*overall_min);
344
45.8k
        aggregated->set_max_key(*overall_max);
345
252k
    } else {
346
252k
        for (const KeyBoundsPB& key_bounds : segments_key_bounds) {
347
80.4k
            KeyBoundsPB* new_key_bounds = _rowset_meta_pb.add_segments_key_bounds();
348
80.4k
            *new_key_bounds = key_bounds;
349
80.4k
        }
350
252k
    }
351
298k
    set_segments_key_bounds_aggregated(did_aggregate);
352
353
298k
    int32_t truncation_threshold = config::segments_key_bounds_truncation_threshold;
354
298k
    if (config::random_segments_key_bounds_truncation) {
355
0
        std::mt19937 generator(std::random_device {}());
356
0
        std::uniform_int_distribution<int> distribution(-10, 40);
357
0
        truncation_threshold = distribution(generator);
358
0
    }
359
298k
    bool really_do_truncation {false};
360
298k
    if (truncation_threshold > 0) {
361
297k
        for (auto& segment_key_bounds : *_rowset_meta_pb.mutable_segments_key_bounds()) {
362
125k
            if (segment_key_bounds.min_key().size() > truncation_threshold) {
363
7.27k
                really_do_truncation = true;
364
7.27k
                segment_key_bounds.mutable_min_key()->resize(truncation_threshold);
365
7.27k
            }
366
125k
            if (segment_key_bounds.max_key().size() > truncation_threshold) {
367
7.29k
                really_do_truncation = true;
368
7.29k
                segment_key_bounds.mutable_max_key()->resize(truncation_threshold);
369
7.29k
            }
370
125k
        }
371
297k
    }
372
298k
    set_segments_key_bounds_truncated(really_do_truncation || is_segments_key_bounds_truncated());
373
298k
}
374
375
3.73k
void RowsetMeta::merge_rowset_meta(const RowsetMeta& other) {
376
3.73k
    set_num_segments(num_segments() + other.num_segments());
377
3.73k
    set_num_rows(num_rows() + other.num_rows());
378
3.73k
    set_data_disk_size(data_disk_size() + other.data_disk_size());
379
3.73k
    set_total_disk_size(total_disk_size() + other.total_disk_size());
380
3.73k
    set_index_disk_size(index_disk_size() + other.index_disk_size());
381
3.73k
    set_total_disk_size(data_disk_size() + index_disk_size());
382
3.73k
    set_segments_key_bounds_truncated(is_segments_key_bounds_truncated() ||
383
3.73k
                                      other.is_segments_key_bounds_truncated());
384
    // merge_rowset_meta is used in the MOW partial-update publish path, which relies
385
    // on per-segment bounds. Aggregation should never be enabled for MOW rowsets,
386
    // so we do not expect either side to be aggregated here.
387
18.4E
    DCHECK(!is_segments_key_bounds_aggregated() && !other.is_segments_key_bounds_aggregated())
388
18.4E
            << "merge_rowset_meta encountered aggregated key bounds";
389
3.73k
    if (_rowset_meta_pb.num_segment_rows_size() > 0) {
390
1.69k
        if (other.num_segments() > 0) {
391
21
            if (other._rowset_meta_pb.num_segment_rows_size() > 0) {
392
21
                for (auto row_count : other._rowset_meta_pb.num_segment_rows()) {
393
21
                    _rowset_meta_pb.add_num_segment_rows(row_count);
394
21
                }
395
21
            } else {
396
                // This may happen when a partial update load commits in high version doirs_be
397
                // and publishes with new segments in low version doris_be. In this case, just clear
398
                // all num_segment_rows.
399
0
                _rowset_meta_pb.clear_num_segment_rows();
400
0
            }
401
21
        }
402
1.69k
    }
403
3.73k
    for (auto&& key_bound : other.get_segments_key_bounds()) {
404
21
        add_segment_key_bounds(key_bound);
405
21
    }
406
3.73k
    if (_rowset_meta_pb.enable_segments_file_size() &&
407
3.73k
        other._rowset_meta_pb.enable_segments_file_size()) {
408
3.28k
        for (auto fsize : other.segments_file_size()) {
409
21
            _rowset_meta_pb.add_segments_file_size(fsize);
410
21
        }
411
3.28k
    }
412
3.73k
    if (_rowset_meta_pb.enable_inverted_index_file_info() &&
413
3.73k
        other._rowset_meta_pb.enable_inverted_index_file_info()) {
414
192
        for (auto finfo : other.inverted_index_file_info()) {
415
0
            InvertedIndexFileInfo* new_file_info = _rowset_meta_pb.add_inverted_index_file_info();
416
0
            *new_file_info = finfo;
417
0
        }
418
192
    }
419
    // In partial update the rowset schema maybe updated when table contains variant type, so we need the newest schema to be updated
420
    // Otherwise the schema is stale and lead to wrong data read
421
3.73k
    TEST_SYNC_POINT_RETURN_WITH_VOID("RowsetMeta::merge_rowset_meta:skip_schema_merge");
422
3.73k
    if (tablet_schema()->num_variant_columns() > 0) {
423
        // merge extracted columns
424
103
        TabletSchemaSPtr merged_schema;
425
103
        static_cast<void>(variant_util::get_least_common_schema(
426
103
                {tablet_schema(), other.tablet_schema()}, nullptr, merged_schema));
427
103
        if (*_schema != *merged_schema) {
428
0
            set_tablet_schema(merged_schema);
429
0
        }
430
103
    }
431
3.73k
    if (rowset_state() == RowsetStatePB::BEGIN_PARTIAL_UPDATE) {
432
3.28k
        set_rowset_state(RowsetStatePB::COMMITTED);
433
3.28k
    }
434
435
3.73k
    update_metadata_size();
436
3.73k
}
437
438
944k
int64_t RowsetMeta::get_metadata_size() const {
439
944k
    return sizeof(RowsetMeta) + _rowset_meta_pb.ByteSizeLong();
440
944k
}
441
442
1.74M
InvertedIndexFileInfo RowsetMeta::inverted_index_file_info_by_pos(size_t pos) const {
443
1.74M
    return _rowset_meta_pb.enable_inverted_index_file_info()
444
1.74M
                   ? (_rowset_meta_pb.inverted_index_file_info_size() > cast_set<int>(pos)
445
56.8k
                              ? _rowset_meta_pb.inverted_index_file_info(cast_set<int>(pos))
446
56.8k
                              : InvertedIndexFileInfo())
447
1.74M
                   : InvertedIndexFileInfo();
448
1.74M
}
449
450
void RowsetMeta::add_inverted_index_files_info(
451
22.4k
        const std::vector<const InvertedIndexFileInfo*>& idx_file_info) {
452
22.4k
    _rowset_meta_pb.set_enable_inverted_index_file_info(true);
453
22.4k
    for (auto finfo : idx_file_info) {
454
6.22k
        auto* new_file_info = _rowset_meta_pb.add_inverted_index_file_info();
455
6.22k
        *new_file_info = *finfo;
456
6.22k
    }
457
22.4k
}
458
459
0
bool operator==(const RowsetMeta& a, const RowsetMeta& b) {
460
0
    if (a._rowset_id != b._rowset_id) return false;
461
0
    if (a._is_removed_from_rowset_meta != b._is_removed_from_rowset_meta) return false;
462
0
    if (!google::protobuf::util::MessageDifferencer::Equals(a._rowset_meta_pb, b._rowset_meta_pb))
463
0
        return false;
464
0
    return true;
465
0
}
466
467
} // namespace doris