Coverage Report

Created: 2026-10-28 17:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/load/memtable/memtable.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 <stddef.h>
21
#include <stdint.h>
22
23
#include <algorithm>
24
#include <cstdint>
25
#include <cstring>
26
#include <limits>
27
#include <memory>
28
#include <utility>
29
#include <vector>
30
31
#include "common/status.h"
32
#include "core/block/block.h"
33
#include "core/custom_allocator.h"
34
#include "runtime/memory/mem_tracker.h"
35
#include "runtime/thread_context.h"
36
#include "storage/partial_update_info.h"
37
#include "storage/rowset/rowset_fwd.h"
38
#include "storage/tablet/tablet_schema.h"
39
40
namespace doris {
41
42
class SlotDescriptor;
43
class TabletSchema;
44
struct TabletAddRowsPayload;
45
class TupleDescriptor;
46
enum KeysType : int;
47
48
// Active: the memtable is currently used by writer to insert into blocks
49
// Write_finished: the memtable finished write blocks and in the queue waiting for flush
50
// FLUSH: the memtable is under flushing, write segment to disk.
51
enum MemType { ACTIVE = 0, WRITE_FINISHED = 1, FLUSH = 2 };
52
53
// row pos in _input_mutable_block
54
struct RowInBlock {
55
    size_t _row_pos;
56
    int64_t _allocated_lsn = 0;
57
    char* _agg_mem = nullptr;
58
    size_t* _agg_state_offset = nullptr;
59
    bool _has_init_agg;
60
61
34.9k
    RowInBlock(size_t row = 0) : _row_pos(row), _has_init_agg(false) {}
62
    RowInBlock(size_t row, int64_t allocated_lsn)
63
26.7k
            : _row_pos(row), _allocated_lsn(allocated_lsn), _has_init_agg(false) {}
64
65
2.06k
    void init_agg_places(char* agg_mem, size_t* agg_state_offset) {
66
2.06k
        _has_init_agg = true;
67
2.06k
        _agg_mem = agg_mem;
68
2.06k
        _agg_state_offset = agg_state_offset;
69
2.06k
    }
70
71
30.7k
    char* agg_places(size_t offset) const { return _agg_mem + _agg_state_offset[offset]; }
72
73
39.0k
    inline bool has_init_agg() const { return _has_init_agg; }
74
75
2.05k
    inline void remove_init_agg() { _has_init_agg = false; }
76
};
77
78
class Tie {
79
public:
80
    class Iter {
81
    public:
82
168
        Iter(Tie& tie) : _tie(tie), _next(tie._begin + 1) {}
83
10.4k
        size_t left() const { return _left; }
84
26.7k
        size_t right() const { return _right; }
85
86
        // return false means no more ranges
87
4.33k
        bool next() {
88
4.33k
            if (_next >= _tie._end) {
89
118
                return false;
90
118
            }
91
4.21k
            _next = _find(1, _next);
92
4.21k
            if (_next >= _tie._end) {
93
51
                return false;
94
51
            }
95
4.16k
            _left = _next - 1;
96
4.16k
            _next = _find(0, _next);
97
4.16k
            _right = _next;
98
4.16k
            return true;
99
4.21k
        }
100
101
    private:
102
8.37k
        size_t _find(uint8_t value, size_t start) {
103
8.37k
            if (start >= _tie._end) {
104
0
                return start;
105
0
            }
106
8.37k
            size_t offset = start - _tie._begin;
107
8.37k
            size_t size = _tie._end - start;
108
8.37k
            void* p = std::memchr(_tie._bits.data() + offset, value, size);
109
8.37k
            if (p == nullptr) {
110
112
                return _tie._end;
111
112
            }
112
8.26k
            return static_cast<uint8_t*>(p) - _tie._bits.data() + _tie._begin;
113
8.37k
        }
114
115
    private:
116
        Tie& _tie;
117
        size_t _left;
118
        size_t _right;
119
        size_t _next;
120
    };
121
122
public:
123
63
    Tie(size_t begin, size_t end) : _begin(begin), _end(end), _bits(end - begin, 1) {}
124
0
    uint8_t operator[](size_t i) const { return _bits[i - _begin]; }
125
20.6k
    uint8_t& operator[](size_t i) { return _bits[i - _begin]; }
126
168
    Iter iter() { return Iter(*this); }
127
128
private:
129
    const size_t _begin;
130
    const size_t _end;
131
    DorisVector<uint8_t> _bits;
132
};
133
134
// Compares two rows of one mutable block by key. The block is an argument of every call instead of
135
// being stored, so the comparator can never keep a pointer to a block that already went away.
136
class RowInBlockComparator {
137
public:
138
    RowInBlockComparator(std::shared_ptr<TabletSchema> tablet_schema)
139
70
            : _tablet_schema(std::move(tablet_schema)) {}
140
    int operator()(const MutableBlock& block, const RowInBlock* left,
141
                   const RowInBlock* right) const;
142
143
private:
144
    std::shared_ptr<TabletSchema> _tablet_schema;
145
};
146
147
class MemTableStat {
148
public:
149
22
    MemTableStat& operator+=(const MemTableStat& stat) {
150
22
        raw_rows += stat.raw_rows;
151
22
        merged_rows += stat.merged_rows;
152
22
        sort_ns += stat.sort_ns;
153
22
        agg_ns += stat.agg_ns;
154
22
        put_into_output_ns += stat.put_into_output_ns;
155
22
        duration_ns += stat.duration_ns;
156
22
        sort_times += stat.sort_times;
157
22
        agg_times += stat.agg_times;
158
159
22
        return *this;
160
22
    }
161
162
    std::atomic<int64_t> raw_rows = 0;
163
    std::atomic<int64_t> merged_rows = 0;
164
    int64_t sort_ns = 0;
165
    int64_t agg_ns = 0;
166
    int64_t put_into_output_ns = 0;
167
    int64_t duration_ns = 0;
168
    std::atomic<int64_t> sort_times = 0;
169
    std::atomic<int64_t> agg_times = 0;
170
};
171
172
// Common interface and block lifetime of a load memtable.
173
//
174
// The table model decides how inserted rows are tracked and merged, so each model is a separate
175
// implementation: DupKeysMemTable keeps a compact row permutation, while AGG and UNIQUE tables
176
// share the aggregation implementation through AggregatingMemTable. The model is selected once
177
// per memtable by create(); the hooks below are called once per inserted batch or per to_block()
178
// call, never per row, so the sorting and aggregation inner loops stay free of virtual dispatch.
179
class MemTable {
180
public:
181
    static std::shared_ptr<MemTable> create(int64_t tablet_id,
182
                                            std::shared_ptr<TabletSchema> tablet_schema,
183
                                            const std::vector<SlotDescriptor*>* slot_descs,
184
                                            TupleDescriptor* tuple_desc, bool enable_unique_key_mow,
185
                                            PartialUpdateInfo* partial_update_info,
186
                                            const std::shared_ptr<ResourceContext>& resource_ctx,
187
                                            bool need_lsn = false);
188
189
    virtual ~MemTable();
190
191
39
    int64_t tablet_id() const { return _tablet_id; }
192
166
    size_t memory_usage() const { return _mem_tracker->consumption(); }
193
    // Insert the rows named by `rows` from `block`.
194
    //
195
    // The caller must keep the memtable within MAX_MEMTABLE_ROWS rows: this batch has to fit, and
196
    // insert() asserts that instead of reporting an error. MemTableWriter::write is the production
197
    // caller and flushes the memtable before the limit is reached.
198
    //
199
    // Expected failures are reported through the returned Status. Allocation failures raised by a
200
    // model hook are not all converted here, so this may still throw a doris::Exception: after such
201
    // a failure the block can be ahead of its row tracking, and the only safe reaction is to catch
202
    // it and discard the whole memtable. MemTableWriter::write is the production caller and does
203
    // exactly that.
204
    Status insert(const Block* block, const TabletAddRowsPayload& rows);
205
206
0
    virtual void shrink_memtable_by_agg() {}
207
208
    bool need_flush() const;
209
210
26
    virtual bool need_agg() const { return false; }
211
212
    Status to_block(std::unique_ptr<Block>* res);
213
214
8
    ConstAllocatedLsnVectorSharedPtr allocated_lsns() const { return _output_allocated_lsns; }
215
216
53
    bool empty() const { return _input_mutable_block.rows() == 0; }
217
218
23
    const MemTableStat& stat() { return _stat; }
219
220
93
    std::shared_ptr<ResourceContext> resource_ctx() { return _resource_ctx; }
221
222
68
    std::shared_ptr<MemTracker> mem_tracker() { return _mem_tracker; }
223
224
28
    void set_flush_success() { _is_flush_success = true; }
225
226
0
    MemType get_mem_type() { return _mem_type; }
227
228
64
    void update_mem_type(MemType memtype) { _mem_type = memtype; }
229
230
47
    int64_t raw_rows() { return _stat.raw_rows.load(); }
231
232
protected:
233
    MemTable(int64_t tablet_id, std::shared_ptr<TabletSchema> tablet_schema,
234
             const std::vector<SlotDescriptor*>* slot_descs, TupleDescriptor* tuple_desc,
235
             PartialUpdateInfo* partial_update_info,
236
             const std::shared_ptr<ResourceContext>& resource_ctx, bool need_lsn);
237
238
    // Called once when the first batch creates the input/output blocks, before any row is
239
    // appended. Derived models resolve their per-column state from _input_mutable_block here.
240
10
    virtual Status _on_first_insertion() { return Status::OK(); }
241
    // Reserve the row metadata of one batch before its columns are appended.
242
    virtual void _reserve_rows(size_t num_rows, size_t cursor) = 0;
243
    // Record the row metadata of a batch whose columns were appended successfully.
244
    virtual void _append_row_metadata(size_t num_rows, size_t cursor,
245
                                      const DorisVector<int64_t>& allocated_lsns) = 0;
246
    // Sort the tracked rows and return how many adjacent rows share a key.
247
    virtual size_t _sort() = 0;
248
    virtual Status _put_into_output(Block& in_block) = 0;
249
    virtual Status _to_block(std::unique_ptr<Block>* res) = 0;
250
    // Flush threshold of this model, before the memory-pressure check.
251
    virtual int64_t _flush_buffer_size() const;
252
253
    // Rows are addressed by int32 positions inside a block, so a memtable never tracks more. The
254
    // limit is a precondition of insert() rather than a runtime check: MemTableWriter::write
255
    // flushes the memtable before it is reached.
256
    static constexpr size_t MAX_MEMTABLE_ROWS =
257
            static_cast<size_t>(std::numeric_limits<int32_t>::max());
258
259
    // Grow amortized while never exceeding the row limit. Called before the batch columns are
260
    // appended so a failed reservation leaves the block and its row tracking untouched.
261
    template <typename T>
262
286
    static void reserve_for_append(DorisVector<T>& rows, size_t append_size, size_t max_size) {
263
286
        DORIS_CHECK(append_size <= max_size && rows.size() <= max_size - append_size);
264
286
        const auto required_size = rows.size() + append_size;
265
286
        if (required_size > rows.capacity()) {
266
105
            rows.reserve(
267
105
                    std::min(max_size, rows.capacity() + std::max(rows.capacity(), append_size)));
268
105
        }
269
286
    }
_ZN5doris8MemTable18reserve_for_appendISt10shared_ptrINS_10RowInBlockEEEEvRSt6vectorIT_NS_18CustomStdAllocatorIS6_NS_9AllocatorILb0ELb0ELb0ENS_22DefaultMemoryAllocatorELb1EEEEEEmm
Line
Count
Source
262
78
    static void reserve_for_append(DorisVector<T>& rows, size_t append_size, size_t max_size) {
263
78
        DORIS_CHECK(append_size <= max_size && rows.size() <= max_size - append_size);
264
78
        const auto required_size = rows.size() + append_size;
265
78
        if (required_size > rows.capacity()) {
266
69
            rows.reserve(
267
69
                    std::min(max_size, rows.capacity() + std::max(rows.capacity(), append_size)));
268
69
        }
269
78
    }
_ZN5doris8MemTable18reserve_for_appendIjEEvRSt6vectorIT_NS_18CustomStdAllocatorIS3_NS_9AllocatorILb0ELb0ELb0ENS_22DefaultMemoryAllocatorELb1EEEEEEmm
Line
Count
Source
262
141
    static void reserve_for_append(DorisVector<T>& rows, size_t append_size, size_t max_size) {
263
141
        DORIS_CHECK(append_size <= max_size && rows.size() <= max_size - append_size);
264
141
        const auto required_size = rows.size() + append_size;
265
141
        if (required_size > rows.capacity()) {
266
26
            rows.reserve(
267
26
                    std::min(max_size, rows.capacity() + std::max(rows.capacity(), append_size)));
268
26
        }
269
141
    }
_ZN5doris8MemTable18reserve_for_appendIlEEvRSt6vectorIT_NS_18CustomStdAllocatorIS3_NS_9AllocatorILb0ELb0ELb0ENS_22DefaultMemoryAllocatorELb1EEEEEEmm
Line
Count
Source
262
67
    static void reserve_for_append(DorisVector<T>& rows, size_t append_size, size_t max_size) {
263
67
        DORIS_CHECK(append_size <= max_size && rows.size() <= max_size - append_size);
264
67
        const auto required_size = rows.size() + append_size;
265
67
        if (required_size > rows.capacity()) {
266
10
            rows.reserve(
267
10
                    std::min(max_size, rows.capacity() + std::max(rows.capacity(), append_size)));
268
10
        }
269
67
    }
270
271
    int64_t _adaptive_write_buffer_size() const;
272
273
    void _init_columns_offset_by_slot_descs(const std::vector<SlotDescriptor*>* slot_descs,
274
                                            const TupleDescriptor* tuple_desc,
275
                                            PartialUpdateInfo* partial_update_info);
276
277
    std::atomic<MemType> _mem_type;
278
    int64_t _tablet_id;
279
    bool _is_flush_success = false;
280
    std::shared_ptr<TabletSchema> _tablet_schema;
281
282
    std::shared_ptr<ResourceContext> _resource_ctx;
283
284
    std::shared_ptr<MemTracker> _mem_tracker;
285
    int64_t _load_mem_limit = -1;
286
287
    std::vector<int> _column_offset;
288
    int32_t _row_lsn_col_pos = -1;
289
290
    // Number of rows inserted to this memtable.
291
    // This is not the rows in this memtable, because rows may be merged
292
    // in unique or aggregate key model.
293
    MemTableStat _stat;
294
295
    //for vectorized
296
    MutableBlock _input_mutable_block;
297
    MutableBlock _output_mutable_block;
298
    AllocatedLsnVectorSharedPtr _output_allocated_lsns = std::make_shared<std::vector<int64_t>>();
299
    bool _need_lsn = false;
300
    size_t _last_sorted_pos = 0;
301
302
    size_t _num_columns;
303
    bool _is_first_insertion;
304
}; // class MemTable
305
306
} // namespace doris