Coverage Report

Created: 2026-08-04 11:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/load/memtable/memtable_writer.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 "load/memtable/memtable_writer.h"
19
20
#include <fmt/format.h>
21
22
#include <filesystem>
23
#include <ostream>
24
#include <string>
25
#include <utility>
26
27
#include "common/compiler_util.h" // IWYU pragma: keep
28
#include "common/config.h"
29
#include "common/logging.h"
30
#include "common/status.h"
31
#include "core/block/block.h"
32
#include "io/fs/file_writer.h" // IWYU pragma: keep
33
#include "load/memtable/memtable.h"
34
#include "load/memtable/memtable_flush_executor.h"
35
#include "load/memtable/memtable_memory_limiter.h"
36
#include "runtime/exec_env.h"
37
#include "runtime/memory/mem_tracker.h"
38
#include "runtime/workload_group/workload_group.h"
39
#include "runtime/workload_management/resource_context.h"
40
#include "service/backend_options.h"
41
#include "storage/rowset/beta_rowset_writer.h"
42
#include "storage/rowset/group_rowset_writer.h"
43
#include "storage/rowset/rowset_writer.h"
44
#include "storage/schema_change/schema_change.h"
45
#include "storage/storage_engine.h"
46
#include "storage/tablet/tablet_schema.h"
47
#include "storage/tablet_info.h"
48
#include "util/mem_info.h"
49
#include "util/stopwatch.hpp"
50
51
namespace doris {
52
bvar::Adder<uint64_t> g_flush_cuz_rowscnt_oveflow("flush_cuz_rowscnt_oveflow");
53
54
using namespace ErrorCode;
55
56
305k
MemTableWriter::MemTableWriter(const WriteRequest& req) : _req(req) {}
57
58
305k
MemTableWriter::~MemTableWriter() {
59
305k
    if (!_is_init) {
60
215k
        return;
61
215k
    }
62
89.3k
    if (_flush_token != nullptr) {
63
        // cancel and wait all memtables in flush queue to be finished
64
89.3k
        _flush_token->cancel();
65
89.3k
    }
66
89.3k
    _mem_table.reset();
67
89.3k
}
68
69
0
uint64_t MemTableWriter::workload_group_id() const {
70
0
    auto wg = _resource_ctx->workload_group();
71
0
    if (wg != nullptr) {
72
0
        return wg->id();
73
0
    }
74
0
    return 0;
75
0
}
76
77
Status MemTableWriter::init(std::shared_ptr<RowsetWriter> rowset_writer,
78
                            TabletSchemaSPtr tablet_schema,
79
                            std::shared_ptr<PartialUpdateInfo> partial_update_info,
80
89.0k
                            std::shared_ptr<WorkloadGroup> wg_sptr, bool unique_key_mow) {
81
89.0k
    _rowset_writer = rowset_writer;
82
89.0k
    _tablet_schema = tablet_schema;
83
89.0k
    _unique_key_mow = unique_key_mow;
84
89.0k
    _partial_update_info = partial_update_info;
85
89.0k
    _resource_ctx = thread_context()->resource_ctx();
86
89.0k
    _need_row_binlog_lsn = false;
87
89.2k
    if (_req.table_schema_param != nullptr) {
88
101k
        for (const auto* index_schema : _req.table_schema_param->indexes()) {
89
101k
            if (index_schema->index_id == _req.index_id) {
90
89.0k
                _need_row_binlog_lsn = index_schema->row_binlog_id > 0;
91
89.0k
                break;
92
89.0k
            }
93
101k
        }
94
89.2k
    }
95
89.0k
    if (_need_row_binlog_lsn) {
96
0
        const auto keys_type = _tablet_schema->keys_type();
97
0
        if (keys_type == KeysType::AGG_KEYS ||
98
0
            (keys_type == KeysType::UNIQUE_KEYS && !_unique_key_mow)) {
99
            // Row-binlog LSN sidecar does not support MemTable aggregation now. For AGG tables
100
            // and unique key merge-on-read tables, multiple input rows can be merged into one
101
            // output row in MemTable, so their output LSN semantics should be implemented
102
            // explicitly before enabling row-binlog LSNs on these table types.
103
0
            return Status::NotSupported(
104
0
                    "row binlog lsn does not support AGG table or unique key merge-on-read table");
105
0
        }
106
0
    }
107
108
89.0k
    _reset_mem_table();
109
110
    // create flush handler
111
    // by assigning segment_id to memtable before submiting to flush executor,
112
    // we can make sure same keys sort in the same order in all replicas.
113
89.0k
    RETURN_IF_ERROR(
114
89.0k
            ExecEnv::GetInstance()->storage_engine().memtable_flush_executor()->create_flush_token(
115
89.0k
                    _flush_token, _rowset_writer, _req.is_high_priority, wg_sptr,
116
89.0k
                    _req.table_schema_param));
117
118
89.0k
    _is_init = true;
119
89.0k
    return Status::OK();
120
89.0k
}
121
122
Status MemTableWriter::write(const Block* block, const TabletAddRowsPayload& rows,
123
138k
                             bool* memtable_flushed) {
124
138k
    if (memtable_flushed != nullptr) {
125
138k
        *memtable_flushed = false;
126
138k
    }
127
138k
    if (UNLIKELY(rows.row_idxs.empty())) {
128
0
        DCHECK(rows.row_binlog_lsns.empty());
129
0
        return Status::OK();
130
0
    }
131
138k
    _lock_watch.start();
132
138k
    std::lock_guard<std::mutex> l(_lock);
133
138k
    _lock_watch.stop();
134
138k
    if (_is_cancelled) {
135
0
        return _cancel_status;
136
0
    }
137
138k
    if (!_is_init) {
138
0
        return Status::Error<NOT_INITIALIZED>("delta segment writer has not been initialized");
139
0
    }
140
138k
    if (_is_closed) {
141
0
        return Status::Error<ALREADY_CLOSED>("write block after closed tablet_id={}, load_id={}-{}",
142
0
                                             _req.tablet_id, _req.load_id.hi(), _req.load_id.lo());
143
0
    }
144
145
138k
    if (_need_row_binlog_lsn) {
146
0
        if (rows.row_binlog_lsns.empty()) {
147
0
            return Status::InternalError(
148
0
                    "row binlog lsn is missing for tablet_id={}, index_id={}, load_id={}-{}",
149
0
                    _req.tablet_id, _req.index_id, _req.load_id.hi(), _req.load_id.lo());
150
0
        }
151
0
        DCHECK(rows.row_binlog_lsns.size() == rows.row_idxs.size());
152
0
    }
153
154
    // Flush and reset memtable if it is raw rows great than int32_t.
155
138k
    int64_t raw_rows = _mem_table->raw_rows();
156
138k
    DBUG_EXECUTE_IF("MemTableWriter.too_many_raws",
157
138k
                    { raw_rows = std::numeric_limits<int32_t>::max(); });
158
138k
    if (raw_rows + rows.row_idxs.size() > std::numeric_limits<int32_t>::max()) {
159
0
        g_flush_cuz_rowscnt_oveflow << 1;
160
0
        RETURN_IF_ERROR(_flush_memtable());
161
0
        if (memtable_flushed != nullptr) {
162
0
            *memtable_flushed = true;
163
0
        }
164
0
    }
165
166
138k
    _total_received_rows += rows.row_idxs.size();
167
138k
    auto st = _mem_table->insert(block, rows);
168
169
    // Reset memtable immediately after insert failure to prevent potential flush operations.
170
    // This is a defensive measure because:
171
    // 1. When insert fails (e.g., memory allocation failure during add_rows),
172
    //    the memtable is in an inconsistent state and should not be flushed
173
    // 2. However, memory pressure might trigger a flush operation on this failed memtable
174
    // 3. By resetting here, we ensure the failed memtable won't be included in any subsequent flush,
175
    //    thus preventing potential crashes
176
138k
    DBUG_EXECUTE_IF("MemTableWriter.write.random_insert_error", {
177
138k
        if (rand() % 100 < (100 * dp->param("percent", 0.3))) {
178
138k
            st = Status::InternalError<false>("write memtable random failed for debug");
179
138k
        }
180
138k
    });
181
138k
    if (!st.ok()) [[unlikely]] {
182
0
        _reset_mem_table();
183
0
        return st;
184
0
    }
185
186
138k
    if (UNLIKELY(_mem_table->need_agg() && config::enable_shrink_memory)) {
187
0
        _mem_table->shrink_memtable_by_agg();
188
0
    }
189
138k
    if (UNLIKELY(_mem_table->need_flush())) {
190
0
        RETURN_IF_ERROR(_flush_memtable());
191
0
        if (memtable_flushed != nullptr) {
192
0
            *memtable_flushed = true;
193
0
        }
194
0
    }
195
196
138k
    return Status::OK();
197
138k
}
198
199
0
Status MemTableWriter::_flush_memtable() {
200
0
    auto s = _flush_memtable_async();
201
0
    _reset_mem_table();
202
0
    if (UNLIKELY(!s.ok())) {
203
0
        return s;
204
0
    }
205
0
    return Status::OK();
206
0
}
207
208
89.3k
Status MemTableWriter::_flush_memtable_async() {
209
89.3k
    DCHECK(_flush_token != nullptr);
210
89.3k
    std::shared_ptr<MemTable> memtable;
211
89.3k
    {
212
89.3k
        std::lock_guard<std::mutex> l(_mem_table_ptr_lock);
213
89.3k
        memtable = _mem_table;
214
89.3k
        _mem_table = nullptr;
215
89.3k
        memtable->update_mem_type(MemType::WRITE_FINISHED);
216
89.3k
        _freezed_mem_tables.push_back(memtable);
217
89.3k
    }
218
89.3k
    return _flush_token->submit(memtable);
219
89.3k
}
220
221
0
Status MemTableWriter::flush_async() {
222
0
    std::lock_guard<std::mutex> l(_lock);
223
    // Three calling paths:
224
    // 1. call by local, from `VTabletWriterV2::_write_memtable`.
225
    // 2. call by remote, from `LoadChannelMgr::_get_load_channel`.
226
    // 3. call by daemon thread, from `handle_paused_queries` -> `flush_workload_group_memtables`.
227
0
    if (!_is_init || _is_closed) {
228
        // This writer is uninitialized or closed before flushing, do nothing.
229
        // We return OK instead of NOT_INITIALIZED or ALREADY_CLOSED.
230
        // Because this method maybe called when trying to reduce mem consumption,
231
        // and at that time, the writer may not be initialized yet and that is a normal case.
232
0
        return Status::OK();
233
0
    }
234
235
0
    if (_is_cancelled) {
236
0
        return _cancel_status;
237
0
    }
238
239
0
    DCHECK(_resource_ctx != nullptr);
240
0
    SCOPED_SWITCH_THREAD_MEM_TRACKER_LIMITER(_resource_ctx->memory_context()->mem_tracker());
241
242
0
    VLOG_NOTICE << "flush memtable to reduce mem consumption. memtable size: "
243
0
                << PrettyPrinter::print_bytes(_mem_table->memory_usage())
244
0
                << ", tablet: " << _req.tablet_id << ", load id: " << print_id(_req.load_id);
245
0
    auto s = _flush_memtable_async();
246
0
    _reset_mem_table();
247
0
    return s;
248
0
}
249
250
27.9k
Status MemTableWriter::wait_flush() {
251
27.9k
    {
252
27.9k
        std::lock_guard<std::mutex> l(_lock);
253
27.9k
        if (!_is_init || _is_closed) {
254
            // return OK instead of NOT_INITIALIZED or ALREADY_CLOSED for same reason
255
            // as described in flush_async()
256
27.9k
            return Status::OK();
257
27.9k
        }
258
0
        if (_is_cancelled) {
259
0
            return _cancel_status;
260
0
        }
261
0
    }
262
0
    SCOPED_RAW_TIMER(&_wait_flush_time_ns);
263
0
    RETURN_IF_ERROR(_flush_token->wait());
264
0
    return Status::OK();
265
0
}
266
267
88.9k
void MemTableWriter::_reset_mem_table() {
268
88.9k
    {
269
88.9k
        std::lock_guard<std::mutex> l(_mem_table_ptr_lock);
270
88.9k
        _mem_table.reset(new MemTable(_req.tablet_id, _tablet_schema, _req.slots, _req.tuple_desc,
271
88.9k
                                      _unique_key_mow, _partial_update_info.get(), _resource_ctx,
272
88.9k
                                      _need_row_binlog_lsn));
273
88.9k
    }
274
275
88.9k
    _segment_num++;
276
88.9k
}
277
278
89.3k
Status MemTableWriter::close() {
279
89.3k
    _lock_watch.start();
280
89.3k
    std::lock_guard<std::mutex> l(_lock);
281
89.3k
    _lock_watch.stop();
282
89.3k
    if (_is_cancelled) {
283
0
        return _cancel_status;
284
0
    }
285
89.3k
    if (!_is_init) {
286
0
        return Status::Error<NOT_INITIALIZED>("delta segment writer has not been initialized");
287
0
    }
288
89.3k
    if (_is_closed) {
289
0
        LOG(WARNING) << "close after closed tablet_id=" << _req.tablet_id
290
0
                     << " load_id=" << _req.load_id;
291
0
        return Status::OK();
292
0
    }
293
294
89.3k
    auto s = _flush_memtable_async();
295
89.3k
    {
296
89.3k
        std::lock_guard<std::mutex> lm(_mem_table_ptr_lock);
297
89.3k
        _mem_table.reset();
298
89.3k
    }
299
89.3k
    _is_closed = true;
300
89.3k
    if (UNLIKELY(!s.ok())) {
301
0
        return s;
302
89.3k
    } else {
303
89.3k
        return Status::OK();
304
89.3k
    }
305
89.3k
}
306
307
89.2k
Status MemTableWriter::_do_close_wait() {
308
89.2k
    SCOPED_RAW_TIMER(&_close_wait_time_ns);
309
89.2k
    std::lock_guard<std::mutex> l(_lock);
310
89.2k
    DCHECK(_is_init)
311
0
            << "delta writer is supposed be to initialized before close_wait() being called";
312
313
89.2k
    if (_is_cancelled) {
314
0
        return _cancel_status;
315
0
    }
316
317
89.2k
    Status st;
318
    // return error if previous flush failed
319
89.2k
    {
320
89.2k
        SCOPED_RAW_TIMER(&_wait_flush_time_ns);
321
89.2k
        st = _flush_token->wait();
322
89.2k
    }
323
89.2k
    if (UNLIKELY(!st.ok())) {
324
43
        LOG(WARNING) << "previous flush failed tablet " << _req.tablet_id;
325
43
        return st;
326
43
    }
327
328
89.2k
    if (_rowset_writer->num_rows() + _flush_token->memtable_stat().merged_rows !=
329
89.2k
        _total_received_rows) {
330
0
        LOG(WARNING) << "the rows number written doesn't match, rowset num rows written to file: "
331
0
                     << _rowset_writer->num_rows()
332
0
                     << ", merged_rows: " << _flush_token->memtable_stat().merged_rows
333
0
                     << ", total received rows: " << _total_received_rows;
334
0
        return Status::InternalError("rows number written by delta writer dosen't match");
335
0
    }
336
337
    // print slow log if wait more than 1s
338
89.2k
    if (_wait_flush_time_ns > 1000UL * 1000 * 1000) {
339
506
        LOG(INFO) << "close delta writer for tablet: " << _req.tablet_id
340
506
                  << ", load id: " << print_id(_req.load_id) << ", wait close for "
341
506
                  << _wait_flush_time_ns << "(ns), stats: " << _flush_token->get_stats();
342
506
    }
343
344
89.2k
    return Status::OK();
345
89.2k
}
346
347
31
void MemTableWriter::_update_profile(RuntimeProfile* profile) {
348
31
    if (!profile) {
349
0
        return;
350
0
    }
351
    // NOTE: MemTableWriter may be accessed when profile is out of scope, in MemTableMemoryLimiter.
352
    // To avoid accessing dangling pointers, we cannot make profile as a member of MemTableWriter.
353
31
    auto child =
354
31
            profile->create_child(fmt::format("MemTableWriter {}", _req.tablet_id), true, true);
355
31
    auto lock_timer = ADD_TIMER(child, "LockTime");
356
31
    auto sort_timer = ADD_TIMER(child, "MemTableSortTime");
357
31
    auto agg_timer = ADD_TIMER(child, "MemTableAggTime");
358
31
    auto memtable_duration_timer = ADD_TIMER(child, "MemTableDurationTime");
359
31
    auto segment_writer_timer = ADD_TIMER(child, "SegmentWriterTime");
360
31
    auto wait_flush_timer = ADD_TIMER(child, "MemTableWaitFlushTime");
361
31
    auto put_into_output_timer = ADD_TIMER(child, "MemTablePutIntoOutputTime");
362
31
    auto delete_bitmap_timer = ADD_TIMER(child, "DeleteBitmapTime");
363
31
    auto close_wait_timer = ADD_TIMER(child, "CloseWaitTime");
364
31
    auto sort_times = ADD_COUNTER(child, "MemTableSortTimes", TUnit::UNIT);
365
31
    auto agg_times = ADD_COUNTER(child, "MemTableAggTimes", TUnit::UNIT);
366
31
    auto segment_num = ADD_COUNTER(child, "SegmentNum", TUnit::UNIT);
367
31
    auto raw_rows_num = ADD_COUNTER(child, "RawRowNum", TUnit::UNIT);
368
31
    auto merged_rows_num = ADD_COUNTER(child, "MergedRowNum", TUnit::UNIT);
369
370
31
    COUNTER_UPDATE(lock_timer, _lock_watch.elapsed_time());
371
31
    COUNTER_SET(delete_bitmap_timer, _rowset_writer->delete_bitmap_ns());
372
31
    COUNTER_SET(segment_writer_timer, _rowset_writer->segment_writer_ns());
373
31
    COUNTER_SET(wait_flush_timer, _wait_flush_time_ns);
374
31
    COUNTER_SET(close_wait_timer, _close_wait_time_ns);
375
31
    COUNTER_SET(segment_num, _segment_num);
376
31
    const auto& memtable_stat = _flush_token->memtable_stat();
377
31
    COUNTER_SET(sort_timer, memtable_stat.sort_ns);
378
31
    COUNTER_SET(agg_timer, memtable_stat.agg_ns);
379
31
    COUNTER_SET(memtable_duration_timer, memtable_stat.duration_ns);
380
31
    COUNTER_SET(put_into_output_timer, memtable_stat.put_into_output_ns);
381
31
    COUNTER_SET(sort_times, memtable_stat.sort_times);
382
31
    COUNTER_SET(agg_times, memtable_stat.agg_times);
383
31
    COUNTER_SET(raw_rows_num, memtable_stat.raw_rows);
384
31
    COUNTER_SET(merged_rows_num, memtable_stat.merged_rows);
385
31
}
386
387
89.3k
Status MemTableWriter::cancel() {
388
89.3k
    return cancel_with_status(Status::Cancelled("already cancelled"));
389
89.3k
}
390
391
185k
Status MemTableWriter::cancel_with_status(const Status& st) {
392
185k
    std::lock_guard<std::mutex> l(_lock);
393
185k
    if (_is_cancelled) {
394
11
        return Status::OK();
395
11
    }
396
185k
    {
397
185k
        std::lock_guard<std::mutex> lm(_mem_table_ptr_lock);
398
185k
        _mem_table.reset();
399
185k
    }
400
185k
    if (_flush_token != nullptr) {
401
        // cancel and wait all memtables in flush queue to be finished
402
89.3k
        _flush_token->cancel();
403
89.3k
    }
404
185k
    _is_cancelled = true;
405
185k
    _cancel_status = st;
406
185k
    return Status::OK();
407
185k
}
408
409
82.5k
const FlushStatistic& MemTableWriter::get_flush_token_stats() {
410
82.5k
    return _flush_token->get_stats();
411
82.5k
}
412
413
138k
uint64_t MemTableWriter::flush_running_count() const {
414
138k
    return _flush_token == nullptr ? 0 : _flush_token->get_stats().flush_running_count.load();
415
138k
}
416
417
130k
int64_t MemTableWriter::table_id() const {
418
130k
    DORIS_CHECK(_req.table_schema_param != nullptr);
419
130k
    return _req.table_schema_param->table_id();
420
130k
}
421
422
4.68k
int64_t MemTableWriter::flush_pending_memtable_count() {
423
4.68k
    std::lock_guard<std::mutex> l(_mem_table_ptr_lock);
424
4.68k
    int64_t memtable_count = 0;
425
4.68k
    for (const auto& mem_table : _freezed_mem_tables) {
426
0
        auto mem_table_sptr = mem_table.lock();
427
0
        if (mem_table_sptr == nullptr) {
428
0
            continue;
429
0
        }
430
0
        auto mem_type = mem_table_sptr->get_mem_type();
431
0
        if (mem_type == MemType::WRITE_FINISHED || mem_type == MemType::FLUSH) {
432
0
            memtable_count++;
433
0
        }
434
0
    }
435
4.68k
    return memtable_count;
436
4.68k
}
437
438
6.05M
int64_t MemTableWriter::mem_consumption(MemType mem) {
439
6.05M
    if (!_is_init) {
440
        // This method may be called before this writer is initialized.
441
        // So _flush_token may be null.
442
308
        return 0;
443
308
    }
444
6.05M
    int64_t mem_usage = 0;
445
6.05M
    {
446
6.05M
        std::lock_guard<std::mutex> l(_mem_table_ptr_lock);
447
6.05M
        for (const auto& mem_table : _freezed_mem_tables) {
448
1.97M
            auto mem_table_sptr = mem_table.lock();
449
1.97M
            if (mem_table_sptr != nullptr && mem_table_sptr->get_mem_type() == mem) {
450
608k
                mem_usage += mem_table_sptr->memory_usage();
451
608k
            }
452
1.97M
        }
453
6.05M
    }
454
6.05M
    return mem_usage;
455
6.05M
}
456
457
3.02M
int64_t MemTableWriter::active_memtable_mem_consumption() {
458
3.02M
    std::lock_guard<std::mutex> l(_mem_table_ptr_lock);
459
3.02M
    return _mem_table != nullptr ? _mem_table->memory_usage() : 0;
460
3.02M
}
461
462
} // namespace doris