Coverage Report

Created: 2026-06-09 13:56

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