Coverage Report

Created: 2026-09-13 20:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/storage/rowid_conversion.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 <algorithm>
21
#include <map>
22
#include <memory>
23
#include <utility>
24
#include <vector>
25
26
#include "common/cast_set.h"
27
#include "common/check.h"
28
#include "runtime/thread_context.h"
29
#include "storage/olap_common.h"
30
#include "storage/utils.h"
31
32
namespace doris {
33
34
// For unique key merge on write table, we should update delete bitmap
35
// of destination rowset when compaction finished.
36
// Through the row id correspondence between the source rowset and the
37
// destination rowset, we can quickly update the delete bitmap of the
38
// destination rowset.
39
class RowIdConversion {
40
public:
41
    enum class Mode { DENSE, LAZY_CHUNKED };
42
43
    struct DestinationRowId {
44
        uint32_t segment_pos;
45
        uint32_t row_id;
46
    };
47
48
273
    explicit RowIdConversion(Mode mode = Mode::DENSE) : _mode(mode) {}
49
273
    ~RowIdConversion() { RELEASE_THREAD_MEM_TRACKER(_seg_rowid_map_mem_used); }
50
51
    Status init_segment_map(const RowsetId& src_rowset_id, const std::vector<uint32_t>& segment_ids,
52
424
                            const std::vector<uint32_t>& num_rows) {
53
424
        DCHECK_EQ(segment_ids.size(), num_rows.size());
54
1.16k
        for (size_t i = 0; i < num_rows.size(); i++) {
55
739
            auto src_segment = std::pair<RowsetId, uint32_t> {src_rowset_id, segment_ids[i]};
56
739
            auto iter = _segment_to_id_map.find(src_segment);
57
            // A segment-group reader can be reopened, so reuse existing source-segment maps.
58
739
            if (iter != _segment_to_id_map.end()) {
59
22
                DORIS_CHECK_LT(iter->second, _segment_num_rows.size());
60
22
                DORIS_CHECK_EQ(_segment_num_rows[iter->second], num_rows[i]);
61
22
                continue;
62
22
            }
63
64
717
            constexpr size_t RESERVED_MEMORY = 10 * 1024 * 1024; // 10M
65
717
            RETURN_IF_ERROR(check_memory_limit(RESERVED_MEMORY));
66
67
717
            uint32_t id = cast_set<uint32_t>(_segment_num_rows.size());
68
717
            auto insert_result = _segment_to_id_map.emplace(src_segment, id);
69
717
            DORIS_CHECK(insert_result.second);
70
717
            _id_to_segment_map.push_back(src_segment);
71
717
            _segment_num_rows.push_back(num_rows[i]);
72
717
            if (_mode == Mode::LAZY_CHUNKED) {
73
4
                _lazy_segments_rowid_map.emplace_back();
74
4
                auto& chunks = _lazy_segments_rowid_map.back();
75
4
                chunks.resize((cast_set<size_t>(num_rows[i]) + ROWS_PER_CHUNK - 1) /
76
4
                              ROWS_PER_CHUNK);
77
4
                track_lazy_mem_usage(chunks.capacity() * sizeof(std::unique_ptr<RowIdPair[]>));
78
4
                continue;
79
4
            }
80
81
713
            std::vector<std::pair<uint32_t, uint32_t>> vec(
82
713
                    num_rows[i], std::pair<uint32_t, uint32_t>(UINT32_MAX, UINT32_MAX));
83
84
            //NOTE: manually count _segments_rowid_map's memory here, because _segments_rowid_map could be used by indexCompaction.
85
            // indexCompaction is a thridparty code, it's too complex to modify it.
86
            // refer compact_column.
87
713
            track_mem_usage(vec.capacity());
88
713
            _segments_rowid_map.emplace_back(std::move(vec));
89
713
        }
90
424
        return Status::OK();
91
424
    }
92
93
    // set dst rowset id
94
150
    void set_dst_rowset_id(const RowsetId& dst_rowset_id) { _dst_rowst_id = dst_rowset_id; }
95
27
    const RowsetId& get_dst_rowset_id() const { return _dst_rowst_id; }
96
97
    // add row id to the map
98
    Status add(const std::vector<RowLocation>& rss_row_ids,
99
1.78k
               const std::vector<uint32_t>& dst_segments_num_row) {
100
4.75M
        for (auto& item : rss_row_ids) {
101
4.75M
            if (item.row_id == -1) {
102
0
                continue;
103
0
            }
104
4.75M
            uint32_t id = _segment_to_id_map.at(
105
4.75M
                    std::pair<RowsetId, uint32_t> {item.rowset_id, item.segment_id});
106
4.75M
            if (_cur_dst_segment_pos < dst_segments_num_row.size() &&
107
4.75M
                _cur_dst_segment_rowid >= dst_segments_num_row[_cur_dst_segment_pos]) {
108
1.15k
                _cur_dst_segment_pos++;
109
1.15k
                _cur_dst_segment_rowid = 0;
110
1.15k
            }
111
4.75M
            if (_mode == Mode::DENSE) {
112
4.75M
                _segments_rowid_map[id][item.row_id] = std::pair<uint32_t, uint32_t> {
113
4.75M
                        _cur_dst_segment_pos, _cur_dst_segment_rowid++};
114
4.75M
                continue;
115
4.75M
            }
116
6
            RowIdPair* destination = nullptr;
117
6
            RETURN_IF_ERROR(get_or_create_lazy_destination(id, item.row_id, &destination));
118
6
            *destination = {_cur_dst_segment_pos, _cur_dst_segment_rowid++};
119
6
        }
120
1.78k
        return Status::OK();
121
1.78k
    }
122
123
    // Get the destination segment position and row id. The physical destination segment id is
124
    // resolved only after the output rowset is built.
125
    // return non-zero if the src RowLocation does not exist
126
1.77M
    int get(const RowLocation& src, DestinationRowId* dst) const {
127
1.77M
        auto iter = _segment_to_id_map.find({src.rowset_id, src.segment_id});
128
1.77M
        if (iter == _segment_to_id_map.end()) {
129
2
            return -1;
130
2
        }
131
1.77M
        const RowIdPair* destination = nullptr;
132
1.77M
        if (_mode == Mode::DENSE) {
133
1.77M
            const auto& rowid_map = _segments_rowid_map[iter->second];
134
1.77M
            if (src.row_id >= rowid_map.size()) {
135
1
                return -1;
136
1
            }
137
1.77M
            destination = &rowid_map[src.row_id];
138
1.77M
        } else {
139
10
            const auto id = iter->second;
140
10
            if (src.row_id >= _segment_num_rows[id]) {
141
1
                return -1;
142
1
            }
143
9
            destination = get_lazy_destination(id, src.row_id);
144
9
        }
145
1.77M
        if (destination == nullptr) {
146
2
            return -1;
147
2
        }
148
1.77M
        const auto& [dst_segment_pos, dst_rowid] = *destination;
149
1.77M
        if (dst_segment_pos == UINT32_MAX && dst_rowid == UINT32_MAX) {
150
764k
            return -1;
151
764k
        }
152
153
1.00M
        dst->segment_pos = dst_segment_pos;
154
1.00M
        dst->row_id = dst_rowid;
155
1.00M
        return 0;
156
1.77M
    }
157
158
    const std::vector<std::vector<std::pair<uint32_t, uint32_t>>>& get_rowid_conversion_map()
159
29
            const {
160
29
        DORIS_CHECK(_mode == Mode::DENSE);
161
29
        return _segments_rowid_map;
162
29
    }
163
164
3
    size_t memory_usage() const { return _seg_rowid_map_mem_used; }
165
166
30
    const std::map<std::pair<RowsetId, uint32_t>, uint32_t>& get_src_segment_to_id_map() const {
167
30
        return _segment_to_id_map;
168
30
    }
169
170
0
    std::pair<RowsetId, uint32_t> get_segment_by_id(uint32_t id) const {
171
0
        DCHECK_GT(_id_to_segment_map.size(), id);
172
0
        return _id_to_segment_map.at(id);
173
0
    }
174
175
0
    uint32_t get_id_by_segment(const std::pair<RowsetId, uint32_t>& segment) const {
176
0
        return _segment_to_id_map.at(segment);
177
0
    }
178
179
private:
180
    using RowIdPair = std::pair<uint32_t, uint32_t>;
181
    using LazySegmentRowIdMap = std::vector<std::unique_ptr<RowIdPair[]>>;
182
    // A 4096-row chunk uses 32 KiB, balancing sparse-range waste and allocation overhead.
183
    static constexpr uint32_t ROWS_PER_CHUNK = 4096;
184
185
723
    Status check_memory_limit(size_t reserved_memory) const {
186
723
        if (!doris::GlobalMemoryArbitrator::is_exceed_hard_mem_limit(reserved_memory)) {
187
723
            return Status::OK();
188
723
        }
189
0
        return Status::MemoryLimitExceeded(fmt::format(
190
0
                "RowIdConversion allocation failed, process memory exceed limit or sys available "
191
0
                "memory less than low water mark, {}, consuming tracker:<{}>, peak used {}, "
192
0
                "current used {}.",
193
0
                doris::GlobalMemoryArbitrator::process_mem_log_str(),
194
0
                doris::thread_context()->thread_mem_tracker_mgr->limiter_mem_tracker()->label(),
195
0
                doris::thread_context()
196
0
                        ->thread_mem_tracker_mgr->limiter_mem_tracker()
197
0
                        ->peak_consumption(),
198
0
                doris::thread_context()
199
0
                        ->thread_mem_tracker_mgr->limiter_mem_tracker()
200
0
                        ->consumption()));
201
723
    }
202
203
    Status get_or_create_lazy_destination(uint32_t segment_id, uint32_t row_id,
204
6
                                          RowIdPair** destination) {
205
6
        DORIS_CHECK_LT(segment_id, _segment_num_rows.size());
206
6
        DORIS_CHECK_LT(row_id, _segment_num_rows[segment_id]);
207
6
        auto& chunks = _lazy_segments_rowid_map[segment_id];
208
6
        auto& chunk = chunks[row_id / ROWS_PER_CHUNK];
209
6
        if (chunk == nullptr) {
210
6
            constexpr size_t CHUNK_BYTES = ROWS_PER_CHUNK * sizeof(RowIdPair);
211
6
            RETURN_IF_ERROR(check_memory_limit(CHUNK_BYTES));
212
6
            chunk = std::make_unique<RowIdPair[]>(ROWS_PER_CHUNK);
213
6
            std::fill_n(chunk.get(), ROWS_PER_CHUNK, RowIdPair {UINT32_MAX, UINT32_MAX});
214
6
            track_lazy_mem_usage(CHUNK_BYTES);
215
6
        }
216
6
        *destination = &chunk[row_id % ROWS_PER_CHUNK];
217
6
        return Status::OK();
218
6
    }
219
220
9
    const RowIdPair* get_lazy_destination(uint32_t segment_id, uint32_t row_id) const {
221
9
        const auto& chunks = _lazy_segments_rowid_map[segment_id];
222
9
        const auto& chunk = chunks[row_id / ROWS_PER_CHUNK];
223
9
        return chunk == nullptr ? nullptr : &chunk[row_id % ROWS_PER_CHUNK];
224
9
    }
225
226
713
    void track_mem_usage(size_t delta_std_pair_cap) {
227
713
        _std_pair_cap += delta_std_pair_cap;
228
229
713
        size_t new_size =
230
713
                _std_pair_cap * sizeof(std::pair<uint32_t, uint32_t>) +
231
713
                _segments_rowid_map.capacity() * sizeof(std::vector<std::pair<uint32_t, uint32_t>>);
232
713
        CONSUME_THREAD_MEM_TRACKER(new_size - _seg_rowid_map_mem_used);
233
0
        _seg_rowid_map_mem_used = new_size;
234
713
    }
235
236
10
    void track_lazy_mem_usage(size_t bytes) {
237
10
        CONSUME_THREAD_MEM_TRACKER(bytes);
238
0
        _seg_rowid_map_mem_used += bytes;
239
10
    }
240
241
private:
242
    // the first level vector: index indicates src segment.
243
    // the second level vector: index indicates row id of source segment,
244
    // value indicates destination segment position and row id.
245
    // <UINT32_MAX, UINT32_MAX> indicates current row not exist.
246
    std::vector<std::vector<std::pair<uint32_t, uint32_t>>> _segments_rowid_map;
247
    // The first-level index indicates the internal source segment id.
248
    // The second-level index is source row_id / ROWS_PER_CHUNK and selects a lazy chunk.
249
    // The chunk offset is source row_id % ROWS_PER_CHUNK.
250
    // The value indicates destination segment position and row id.
251
    std::vector<LazySegmentRowIdMap> _lazy_segments_rowid_map;
252
    std::vector<uint32_t> _segment_num_rows;
253
    size_t _seg_rowid_map_mem_used {0};
254
    size_t _std_pair_cap {0};
255
    Mode _mode;
256
257
    // Map source segment to 0 to n
258
    std::map<std::pair<RowsetId, uint32_t>, uint32_t> _segment_to_id_map;
259
260
    // Map 0 to n to source segment
261
    std::vector<std::pair<RowsetId, uint32_t>> _id_to_segment_map;
262
263
    // dst rowset id
264
    RowsetId _dst_rowst_id;
265
266
    // current dst segment position
267
    std::uint32_t _cur_dst_segment_pos = 0;
268
269
    // current rowid of dst segment
270
    std::uint32_t _cur_dst_segment_rowid = 0;
271
};
272
273
} // namespace doris