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 |