Coverage Report

Created: 2026-07-09 02:45

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/load/memtable/memtable_memory_limiter.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_memory_limiter.h"
19
20
#include <bvar/bvar.h>
21
22
#include "common/config.h"
23
#include "common/metrics/doris_metrics.h"
24
#include "common/metrics/metrics.h"
25
#include "load/memtable/memtable.h"
26
#include "load/memtable/memtable_writer.h"
27
#include "util/mem_info.h"
28
29
namespace doris {
30
DEFINE_GAUGE_METRIC_PROTOTYPE_5ARG(memtable_memory_limiter_mem_consumption, MetricUnit::BYTES, "",
31
                                   memtable_memory_limiter_mem_consumption,
32
                                   Labels({{"type", "load"}}));
33
34
bvar::LatencyRecorder g_memtable_memory_limit_latency_ms("mm_limiter_limit_time_ms");
35
bvar::Adder<int> g_memtable_memory_limit_waiting_threads("mm_limiter_waiting_threads");
36
bvar::LatencyRecorder g_memtable_table_backpressure_latency_ms(
37
        "mm_limiter_table_backpressure_time_ms");
38
bvar::Adder<int> g_memtable_table_backpressure_waiting_threads(
39
        "mm_limiter_table_backpressure_waiting_threads");
40
bvar::Status<int64_t> g_memtable_table_backpressure_pending_count(
41
        "mm_limiter_table_backpressure_pending_count", 0);
42
bvar::Status<int64_t> g_memtable_active_memory("mm_limiter_mem_active", 0);
43
bvar::Status<int64_t> g_memtable_write_memory("mm_limiter_mem_write", 0);
44
bvar::Status<int64_t> g_memtable_flush_memory("mm_limiter_mem_flush", 0);
45
bvar::Status<int64_t> g_memtable_load_memory("mm_limiter_mem_load", 0);
46
bvar::Status<int64_t> g_load_hard_mem_limit("mm_limiter_limit_hard", 0);
47
bvar::Status<int64_t> g_load_soft_mem_limit("mm_limiter_limit_soft", 0);
48
bvar::Adder<uint64_t> g_flush_cuz_load_mem_exceed_hard_limit("flush_cuz_hard_limit");
49
bvar::Adder<uint64_t> g_flush_cuz_sys_mem_exceed_soft_limit("flush_cuz_soft_limit");
50
bvar::Adder<int> g_memtable_memory_limit_flush_memtable_count("mm_limiter_flush_memtable_count");
51
bvar::LatencyRecorder g_memtable_memory_limit_flush_size_bytes("mm_limiter_flush_size_bytes");
52
53
// Calculate the total memory limit of all load tasks on this BE
54
2
static int64_t calc_process_max_load_memory(int64_t process_mem_limit) {
55
2
    if (process_mem_limit == -1) {
56
        // no limit
57
0
        return -1;
58
0
    }
59
2
    int32_t max_load_memory_percent = config::load_process_max_memory_limit_percent;
60
2
    return process_mem_limit * max_load_memory_percent / 100;
61
2
}
62
63
35
MemTableMemoryLimiter::MemTableMemoryLimiter() {}
64
65
35
MemTableMemoryLimiter::~MemTableMemoryLimiter() {
66
35
    DEREGISTER_HOOK_METRIC(memtable_memory_limiter_mem_consumption);
67
35
}
68
69
2
Status MemTableMemoryLimiter::init(int64_t process_mem_limit) {
70
2
    _load_hard_mem_limit = calc_process_max_load_memory(process_mem_limit);
71
2
    _load_soft_mem_limit = _load_hard_mem_limit * config::load_process_soft_mem_limit_percent / 100;
72
2
    _load_safe_mem_permit =
73
2
            _load_hard_mem_limit * config::load_process_safe_mem_permit_percent / 100;
74
2
    g_load_hard_mem_limit.set_value(_load_hard_mem_limit);
75
2
    g_load_soft_mem_limit.set_value(_load_soft_mem_limit);
76
2
    _mem_tracker = std::make_unique<MemTracker>("AllMemTableMemory");
77
2
    REGISTER_HOOK_METRIC(memtable_memory_limiter_mem_consumption,
78
2
                         [this]() { return _mem_tracker->consumption(); });
79
2
    _log_timer.start();
80
2
    return Status::OK();
81
2
}
82
83
16
void MemTableMemoryLimiter::register_writer(std::weak_ptr<MemTableWriter> writer) {
84
16
    std::lock_guard<std::mutex> l(_lock);
85
16
    _writers.push_back(writer);
86
16
}
87
88
2
int64_t MemTableMemoryLimiter::_sys_avail_mem_less_than_warning_water_mark() {
89
    // reserve a small amount of memory so we do not trigger MinorGC
90
2
    return doris::MemInfo::sys_mem_available_warning_water_mark() -
91
2
           doris::GlobalMemoryArbitrator::sys_mem_available() +
92
2
           config::memtable_limiter_reserved_memory_bytes;
93
2
}
94
95
2
int64_t MemTableMemoryLimiter::_process_used_mem_more_than_soft_mem_limit() {
96
    // reserve a small amount of memory so we do not trigger MinorGC
97
2
    return GlobalMemoryArbitrator::process_memory_usage() - MemInfo::soft_mem_limit() +
98
2
           config::memtable_limiter_reserved_memory_bytes;
99
2
}
100
101
2
bool MemTableMemoryLimiter::_soft_limit_reached() {
102
2
    return _mem_tracker->consumption() > _load_soft_mem_limit || _hard_limit_reached();
103
2
}
104
105
2
bool MemTableMemoryLimiter::_hard_limit_reached() {
106
2
    return _mem_tracker->consumption() > _load_hard_mem_limit ||
107
2
           _sys_avail_mem_less_than_warning_water_mark() > 0 ||
108
2
           _process_used_mem_more_than_soft_mem_limit() > 0;
109
2
}
110
111
0
bool MemTableMemoryLimiter::_load_usage_low() {
112
0
    return _mem_tracker->consumption() <= _load_safe_mem_permit;
113
0
}
114
115
0
int64_t MemTableMemoryLimiter::_need_flush() {
116
0
    DBUG_EXECUTE_IF("MemTableMemoryLimiter._need_flush.random_flush", {
117
0
        if (rand() % 100 < (100 * dp->param("percent", 0.5))) {
118
0
            LOG(INFO) << "debug memtable need flush return 1";
119
0
            return 1;
120
0
        }
121
0
    });
122
0
    int64_t limit1 = _mem_tracker->consumption() - _load_soft_mem_limit;
123
0
    int64_t limit2 = _sys_avail_mem_less_than_warning_water_mark();
124
0
    int64_t limit3 = _process_used_mem_more_than_soft_mem_limit();
125
0
    int64_t need_flush = std::max({limit1, limit2, limit3});
126
0
    return need_flush - _queue_mem_usage - _flush_mem_usage;
127
0
}
128
129
0
int64_t MemTableMemoryLimiter::_table_flush_pending_memtable_count(int64_t table_id) {
130
0
    int64_t pending_memtables = 0;
131
0
    for (const auto& writer : _writers) {
132
0
        auto writer_sptr = writer.lock();
133
0
        if (writer_sptr == nullptr) {
134
0
            continue;
135
0
        }
136
0
        if (writer_sptr->table_id() == table_id) {
137
0
            pending_memtables += writer_sptr->flush_pending_memtable_count();
138
0
        }
139
0
    }
140
0
    return pending_memtables;
141
0
}
142
143
void MemTableMemoryLimiter::handle_table_memtable_backpressure(std::function<bool()> cancel_check,
144
0
                                                               int64_t table_id) {
145
0
    if (!config::enable_table_memtable_flush_backpressure) {
146
0
        return;
147
0
    }
148
0
    const int64_t pending_count_limit = config::table_memtable_flush_pending_count_limit;
149
0
    if (pending_count_limit <= 0) {
150
0
        return;
151
0
    }
152
0
    DORIS_CHECK(table_id > 0);
153
154
0
    std::unique_lock<std::mutex> l(_lock);
155
0
    int64_t pending_count = _table_flush_pending_memtable_count(table_id);
156
0
    if (pending_count < pending_count_limit) {
157
0
        return;
158
0
    }
159
160
0
    MonotonicStopWatch timer;
161
0
    timer.start();
162
0
    g_memtable_table_backpressure_waiting_threads << 1;
163
0
    while (pending_count >= pending_count_limit) {
164
0
        g_memtable_table_backpressure_pending_count.set_value(pending_count);
165
0
        LOG_EVERY_T(INFO, 1) << "table memtable flush backpressure: table_id=" << table_id
166
0
                             << ", pending_memtables=" << pending_count
167
0
                             << ", limit=" << pending_count_limit
168
0
                             << ", memtable writers num: " << _writers.size();
169
0
        if (cancel_check && cancel_check()) {
170
0
            LOG(INFO) << "cancelled when waiting for table memtable flush backpressure"
171
0
                      << ", table_id=" << table_id << ", pending_memtables=" << pending_count
172
0
                      << ", limit=" << pending_count_limit;
173
0
            g_memtable_table_backpressure_waiting_threads << -1;
174
0
            return;
175
0
        }
176
0
        static_cast<void>(_hard_limit_end_cond.wait_for(l, std::chrono::milliseconds(100)));
177
0
        pending_count = _table_flush_pending_memtable_count(table_id);
178
0
    }
179
0
    g_memtable_table_backpressure_pending_count.set_value(pending_count);
180
0
    g_memtable_table_backpressure_waiting_threads << -1;
181
0
    timer.stop();
182
0
    int64_t time_ms = timer.elapsed_time() / 1000 / 1000;
183
0
    g_memtable_table_backpressure_latency_ms << time_ms;
184
0
    LOG(INFO) << "waited " << PrettyPrinter::print(timer.elapsed_time(), TUnit::TIME_NS)
185
0
              << " for table memtable flush backpressure"
186
0
              << ", table_id=" << table_id << ", pending_memtables=" << pending_count
187
0
              << ", limit=" << pending_count_limit << ", memtable writers num: " << _writers.size();
188
0
}
189
190
void MemTableMemoryLimiter::handle_memtable_flush(std::function<bool()> cancel_check,
191
2
                                                  WorkloadGroup*) {
192
    // Check the soft limit.
193
2
    DCHECK(_load_soft_mem_limit > 0);
194
2
    do {
195
2
        DBUG_EXECUTE_IF("MemTableMemoryLimiter.handle_memtable_flush.limit_reached", {
196
2
            LOG(INFO) << "debug memtable limit reached";
197
2
            break;
198
2
        });
199
2
        if (!_soft_limit_reached() || _load_usage_low()) {
200
2
            return;
201
2
        }
202
2
    } while (false);
203
0
    MonotonicStopWatch timer;
204
0
    timer.start();
205
0
    std::unique_lock<std::mutex> l(_lock);
206
0
    g_memtable_memory_limit_waiting_threads << 1;
207
0
    bool first = true;
208
0
    do {
209
0
        if (!first) {
210
0
            auto st = _hard_limit_end_cond.wait_for(l, std::chrono::milliseconds(1000));
211
0
            if (st == std::cv_status::timeout) {
212
0
                LOG(INFO) << "timeout when waiting for memory hard limit end, try again";
213
0
            }
214
0
        }
215
0
        if (cancel_check && cancel_check()) {
216
0
            LOG(INFO) << "cancelled when waiting for memtable flush";
217
0
            g_memtable_memory_limit_waiting_threads << -1;
218
0
            return;
219
0
        }
220
0
        first = false;
221
0
        int64_t need_flush = _need_flush();
222
0
        if (need_flush > 0) {
223
0
            auto limit = _hard_limit_reached() ? Limit::HARD : Limit::SOFT;
224
0
            LOG(INFO) << "reached memtable memory " << (limit == Limit::HARD ? "hard" : "soft")
225
0
                      << ", " << GlobalMemoryArbitrator::process_memory_used_details_str() << ", "
226
0
                      << GlobalMemoryArbitrator::sys_mem_available_details_str()
227
0
                      << ", load mem: " << PrettyPrinter::print_bytes(_mem_tracker->consumption())
228
0
                      << ", memtable writers num: " << _writers.size()
229
0
                      << ", active: " << PrettyPrinter::print_bytes(_active_mem_usage)
230
0
                      << ", queue: " << PrettyPrinter::print_bytes(_queue_mem_usage)
231
0
                      << ", flush: " << PrettyPrinter::print_bytes(_flush_mem_usage)
232
0
                      << ", need flush: " << PrettyPrinter::print_bytes(need_flush);
233
0
            if (VLOG_DEBUG_IS_ON) {
234
0
                auto log_str = doris::ProcessProfile::instance()
235
0
                                       ->memory_profile()
236
0
                                       ->process_memory_detail_str();
237
0
                LOG_LONG_STRING(INFO, log_str);
238
0
            }
239
0
            if (limit == Limit::HARD) {
240
0
                g_flush_cuz_load_mem_exceed_hard_limit << 1;
241
0
            } else if (limit == Limit::SOFT) {
242
0
                g_flush_cuz_sys_mem_exceed_soft_limit << 1;
243
0
            } else {
244
                // will not reach here
245
0
            }
246
0
            _flush_active_memtables(need_flush);
247
0
        }
248
0
    } while (_hard_limit_reached() && !_load_usage_low());
249
0
    g_memtable_memory_limit_waiting_threads << -1;
250
0
    timer.stop();
251
0
    int64_t time_ms = timer.elapsed_time() / 1000 / 1000;
252
0
    g_memtable_memory_limit_latency_ms << time_ms;
253
0
    if (time_ms > 0) {
254
0
        LOG(INFO) << "waited " << PrettyPrinter::print(timer.elapsed_time(), TUnit::TIME_NS)
255
0
                  << " for memtable memory limit"
256
0
                  << ", " << GlobalMemoryArbitrator::process_memory_used_details_str() << ", "
257
0
                  << GlobalMemoryArbitrator::sys_mem_available_details_str()
258
0
                  << ", load mem: " << PrettyPrinter::print_bytes(_mem_tracker->consumption())
259
0
                  << ", memtable writers num: " << _writers.size()
260
0
                  << ", active: " << PrettyPrinter::print_bytes(_active_mem_usage)
261
0
                  << ", queue: " << PrettyPrinter::print_bytes(_queue_mem_usage)
262
0
                  << ", flush: " << PrettyPrinter::print_bytes(_flush_mem_usage);
263
0
    }
264
0
}
265
266
0
int64_t MemTableMemoryLimiter::_flush_active_memtables(int64_t need_flush) {
267
0
    _refresh_mem_tracker();
268
0
    if (_active_writers.size() == 0) {
269
0
        return 0;
270
0
    }
271
272
0
    using WriterMem = std::pair<std::weak_ptr<MemTableWriter>, int64_t>;
273
0
    auto cmp = [](WriterMem left, WriterMem right) { return left.second < right.second; };
274
0
    std::priority_queue<WriterMem, std::vector<WriterMem>, decltype(cmp)> heap(cmp);
275
276
0
    for (auto writer : _active_writers) {
277
0
        auto w = writer.lock();
278
0
        if (w == nullptr) {
279
0
            continue;
280
0
        }
281
0
        heap.emplace(w, w->active_memtable_mem_consumption());
282
0
    }
283
284
0
    int64_t mem_flushed = 0;
285
0
    int64_t num_flushed = 0;
286
287
0
    while (mem_flushed < need_flush && !heap.empty()) {
288
0
        auto [writer, sort_mem] = heap.top();
289
0
        heap.pop();
290
0
        auto w = writer.lock();
291
0
        if (w == nullptr) {
292
0
            continue;
293
0
        }
294
295
0
        int64_t mem = w->active_memtable_mem_consumption();
296
0
        if (mem < sort_mem * 0.9) {
297
            // if the memtable writer just got flushed, don't flush it again
298
0
            continue;
299
0
        }
300
0
        Status st = w->flush_async();
301
0
        if (!st.ok()) {
302
0
            auto err_msg = fmt::format(
303
0
                    "tablet writer failed to reduce mem consumption by flushing memtable, "
304
0
                    "tablet_id={}, err={}",
305
0
                    w->tablet_id(), st.to_string());
306
0
            LOG(WARNING) << err_msg;
307
0
            static_cast<void>(w->cancel_with_status(st));
308
0
        }
309
0
        mem_flushed += mem;
310
0
        num_flushed += (mem > 0);
311
0
        g_memtable_memory_limit_flush_memtable_count << 1;
312
0
        g_memtable_memory_limit_flush_size_bytes << mem;
313
0
    }
314
0
    LOG(INFO) << "flushed " << num_flushed << " out of " << _active_writers.size()
315
0
              << " active writers, flushed size: " << PrettyPrinter::print_bytes(mem_flushed);
316
0
    return mem_flushed;
317
0
}
318
319
0
void MemTableMemoryLimiter::refresh_mem_tracker() {
320
0
    std::lock_guard<std::mutex> l(_lock);
321
0
    _refresh_mem_tracker();
322
0
    std::stringstream ss;
323
0
    Limit limit = Limit::NONE;
324
0
    if (_soft_limit_reached()) {
325
0
        limit = _hard_limit_reached() ? Limit::HARD : Limit::SOFT;
326
0
        ss << "reached " << (limit == Limit::HARD ? "hard" : "soft") << " limit";
327
0
    } else if (_last_limit == Limit::NONE) {
328
0
        return;
329
0
    } else {
330
0
        ss << "ended " << (_last_limit == Limit::HARD ? "hard" : "soft") << " limit";
331
0
    }
332
333
0
    if (_last_limit == limit && _log_timer.elapsed_time() < LOG_INTERVAL) {
334
0
        return;
335
0
    }
336
337
0
    _last_limit = limit;
338
0
    _log_timer.reset();
339
0
    LOG(INFO) << ss.str()
340
0
              << ", load mem: " << PrettyPrinter::print_bytes(_mem_tracker->consumption())
341
0
              << ", memtable writers num: " << _writers.size()
342
0
              << ", active: " << PrettyPrinter::print_bytes(_active_mem_usage)
343
0
              << ", queue: " << PrettyPrinter::print_bytes(_queue_mem_usage)
344
0
              << ", flush: " << PrettyPrinter::print_bytes(_flush_mem_usage);
345
0
    if (VLOG_DEBUG_IS_ON) {
346
0
        auto log_str =
347
0
                doris::ProcessProfile::instance()->memory_profile()->process_memory_detail_str();
348
0
        LOG_LONG_STRING(INFO, log_str);
349
0
    }
350
0
}
351
352
0
void MemTableMemoryLimiter::_refresh_mem_tracker() {
353
0
    _flush_mem_usage = 0;
354
0
    _queue_mem_usage = 0;
355
0
    _active_mem_usage = 0;
356
0
    _active_writers.clear();
357
0
    for (auto it = _writers.begin(); it != _writers.end();) {
358
0
        if (auto writer = it->lock()) {
359
            // The memtable is currently used by writer to insert blocks.
360
0
            auto active_usage = writer->active_memtable_mem_consumption();
361
0
            _active_mem_usage += active_usage;
362
0
            if (active_usage > 0) {
363
0
                _active_writers.push_back(writer);
364
0
            }
365
366
0
            auto flush_usage = writer->mem_consumption(MemType::FLUSH);
367
0
            _flush_mem_usage += flush_usage;
368
369
0
            auto write_usage = writer->mem_consumption(MemType::WRITE_FINISHED);
370
0
            _queue_mem_usage += write_usage;
371
0
            ++it;
372
0
        } else {
373
0
            *it = std::move(_writers.back());
374
0
            _writers.pop_back();
375
0
        }
376
0
    }
377
0
    _mem_usage = _active_mem_usage + _queue_mem_usage + _flush_mem_usage;
378
0
    g_memtable_active_memory.set_value(_active_mem_usage);
379
0
    g_memtable_write_memory.set_value(_queue_mem_usage);
380
0
    g_memtable_flush_memory.set_value(_flush_mem_usage);
381
0
    g_memtable_load_memory.set_value(_mem_usage);
382
0
    VLOG_DEBUG << "refreshed mem_tracker, num writers: " << _writers.size()
383
0
               << ", mem usage: " << _mem_usage;
384
0
    _mem_tracker->set_consumption(_mem_usage);
385
0
    if (!_hard_limit_reached()) {
386
0
        _hard_limit_end_cond.notify_all();
387
0
    }
388
0
}
389
390
} // namespace doris