Coverage Report

Created: 2026-08-10 15:16

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/load/memtable/memtable_flush_executor.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_flush_executor.h"
19
20
#include <gen_cpp/olap_file.pb.h>
21
22
#include <algorithm>
23
#include <cstddef>
24
#include <ostream>
25
26
#include "common/config.h"
27
#include "common/logging.h"
28
#include "common/metrics/doris_metrics.h"
29
#include "common/metrics/metrics.h"
30
#include "common/metrics/system_metrics.h"
31
#include "common/signal_handler.h"
32
#include "load/memtable/memtable.h"
33
#include "runtime/thread_context.h"
34
#include "runtime/workload_group/workload_group.h"
35
#include "runtime/workload_management/resource_context.h"
36
#include "storage/binlog.h"
37
#include "storage/rowset/group_rowset_writer.h"
38
#include "storage/rowset/rowset_writer.h"
39
#include "storage/storage_engine.h"
40
#include "storage/tablet_info.h"
41
#include "util/debug_points.h"
42
#include "util/pretty_printer.h"
43
#include "util/stopwatch.hpp"
44
#include "util/time.h"
45
46
namespace doris {
47
using namespace ErrorCode;
48
49
bvar::Adder<int64_t> g_flush_task_num("memtable_flush_task_num");
50
51
class MemtableFlushTask : public Runnable {
52
    ENABLE_FACTORY_CREATOR(MemtableFlushTask);
53
54
public:
55
    MemtableFlushTask(std::shared_ptr<FlushToken> flush_token, std::shared_ptr<MemTable> memtable,
56
                      int32_t segment_id, int64_t submit_task_time)
57
18
            : _flush_token(flush_token),
58
18
              _memtable(memtable),
59
18
              _segment_id(segment_id),
60
18
              _submit_task_time(submit_task_time) {
61
18
        g_flush_task_num << 1;
62
18
    }
63
64
18
    ~MemtableFlushTask() override { g_flush_task_num << -1; }
65
66
12
    void run() override {
67
12
        auto token = _flush_token.lock();
68
12
        if (token) {
69
12
            token->_flush_memtable(_memtable, _segment_id, _submit_task_time);
70
12
        } else {
71
0
            LOG(WARNING) << "flush token is deconstructed, ignore the flush task";
72
0
        }
73
12
    }
74
75
protected:
76
    std::weak_ptr<FlushToken> _flush_token;
77
    std::shared_ptr<MemTable> _memtable;
78
    int32_t _segment_id;
79
    int64_t _submit_task_time;
80
};
81
82
class PartOfGroupMemtableFlushTask final : public MemtableFlushTask {
83
    ENABLE_FACTORY_CREATOR(PartOfGroupMemtableFlushTask);
84
85
public:
86
    PartOfGroupMemtableFlushTask(std::shared_ptr<FlushToken> flush_token,
87
                                 std::shared_ptr<SharedMemtable> shared_memtable,
88
                                 WriteRequestType write_req_type, int64_t submit_task_time)
89
6
            : MemtableFlushTask(flush_token, nullptr, 0, submit_task_time),
90
6
              _shared_memtable(std::move(shared_memtable)),
91
6
              _write_req_type(write_req_type) {}
92
93
6
    void run() override {
94
6
        auto token = _flush_token.lock();
95
6
        if (token) {
96
6
            token->_flush_group_memtable(_shared_memtable, _write_req_type, _submit_task_time);
97
6
        } else {
98
0
            LOG(WARNING) << "flush token is deconstructed, ignore the flush task";
99
0
        }
100
6
    }
101
102
private:
103
    std::shared_ptr<SharedMemtable> _shared_memtable;
104
    WriteRequestType _write_req_type;
105
};
106
107
0
std::ostream& operator<<(std::ostream& os, const FlushStatistic& stat) {
108
0
    os << "(flush time(ms)=" << stat.flush_time_ns / NANOS_PER_MILLIS
109
0
       << ", flush wait time(ms)=" << stat.flush_wait_time_ns / NANOS_PER_MILLIS
110
0
       << ", flush submit count=" << stat.flush_submit_count
111
0
       << ", running flush count=" << stat.flush_running_count
112
0
       << ", finish flush count=" << stat.flush_finish_count
113
0
       << ", flush bytes: " << stat.flush_size_bytes
114
0
       << ", flush disk bytes: " << stat.flush_disk_size_bytes << ")";
115
0
    return os;
116
0
}
117
118
3
SharedMemtable::~SharedMemtable() {
119
3
    if (block == nullptr) {
120
0
        return;
121
0
    }
122
3
    DCHECK(memtable != nullptr);
123
3
    SCOPED_SWITCH_THREAD_MEM_TRACKER_LIMITER(
124
3
            memtable->resource_ctx()->memory_context()->mem_tracker()->write_tracker());
125
3
    SCOPED_CONSUME_MEM_TRACKER(memtable->mem_tracker());
126
3
    block.reset();
127
3
}
128
129
Status FlushToken::_submit_sub_tasks(ThreadPool* pool,
130
15
                                     std::vector<std::shared_ptr<Runnable>> sub_tasks) {
131
33
    for (int i = 0; i < sub_tasks.size(); ++i) {
132
18
        {
133
18
            std::shared_lock rdlk(_flush_status_lock);
134
18
            DBUG_EXECUTE_IF("FlushToken.submit_sub_task_error", {
135
18
                if (i != 0) {
136
                    // only affect flush binlog task
137
18
                    _flush_status = Status::IOError<false>("dbug_be_memtable_submit_flush_error");
138
18
                }
139
18
            });
140
18
            if (!_flush_status.ok()) {
141
0
                return _flush_status;
142
0
            }
143
18
        }
144
18
        Status submit_st = pool->submit(std::move(sub_tasks[i]));
145
18
        if (UNLIKELY(!submit_st.ok())) {
146
0
            {
147
0
                std::lock_guard wrlk(_flush_status_lock);
148
0
                if (_flush_status.ok()) {
149
0
                    _flush_status = submit_st;
150
0
                }
151
0
            }
152
0
            _shutdown_flush_token();
153
0
            return submit_st;
154
0
        }
155
18
        _stats.flush_submit_count++;
156
18
    }
157
15
    return Status::OK();
158
15
}
159
160
18
Status FlushToken::submit(std::shared_ptr<MemTable> mem_table) {
161
18
    {
162
18
        std::shared_lock rdlk(_flush_status_lock);
163
18
        DBUG_EXECUTE_IF("FlushToken.submit_flush_error", {
164
18
            _flush_status = Status::IOError<false>("dbug_be_memtable_submit_flush_error");
165
18
        });
166
18
        if (!_flush_status.ok()) {
167
0
            return _flush_status;
168
0
        }
169
18
    }
170
171
18
    if (mem_table == nullptr || mem_table->empty()) {
172
3
        return Status::OK();
173
3
    }
174
15
    int64_t submit_task_time = MonotonicNanos();
175
15
    auto* group_rowset_writer = typeid_cast<GroupRowsetWriter*>(_rowset_writer.get());
176
15
    std::shared_ptr<SharedMemtable> shared_memtable;
177
15
    std::vector<std::shared_ptr<Runnable>> tasks;
178
15
    if (group_rowset_writer != nullptr) {
179
3
        auto data_writer = group_rowset_writer->data_writer();
180
3
        auto binlog_writer = group_rowset_writer->row_binlog_writer();
181
3
        DCHECK(data_writer != nullptr);
182
3
        DCHECK(binlog_writer != nullptr);
183
184
3
        shared_memtable = std::make_shared<SharedMemtable>();
185
3
        shared_memtable->memtable = mem_table;
186
        // Keep data/binlog segment_id allocators in sync.
187
3
        auto segment_id = data_writer->allocate_segment_id();
188
3
        auto binlog_segment_id = binlog_writer->allocate_segment_id();
189
3
        DCHECK_EQ(segment_id, binlog_segment_id);
190
3
        shared_memtable->segment_id = segment_id;
191
192
3
        tasks.emplace_back(PartOfGroupMemtableFlushTask::create_shared(
193
3
                shared_from_this(), shared_memtable, WriteRequestType::DATA, submit_task_time));
194
3
        tasks.emplace_back(PartOfGroupMemtableFlushTask::create_shared(
195
3
                shared_from_this(), shared_memtable, WriteRequestType::ROW_BINLOG,
196
3
                submit_task_time));
197
12
    } else {
198
12
        tasks.emplace_back(MemtableFlushTask::create_shared(shared_from_this(), mem_table,
199
12
                                                            _rowset_writer->allocate_segment_id(),
200
12
                                                            submit_task_time));
201
12
    }
202
    // NOTE: we should guarantee WorkloadGroup is not deconstructed when submit memtable flush task.
203
    // because currently WorkloadGroup's can only be destroyed when all queries in the group is finished,
204
    // but not consider whether load channel is finish.
205
15
    std::shared_ptr<WorkloadGroup> wg_sptr = _wg_wptr.lock();
206
15
    ThreadPool* wg_thread_pool = nullptr;
207
15
    if (wg_sptr) {
208
0
        wg_thread_pool = wg_sptr->get_memtable_flush_pool();
209
0
    }
210
15
    ThreadPool* pool = wg_thread_pool ? wg_thread_pool : _thread_pool;
211
212
15
    return _submit_sub_tasks(pool, std::move(tasks));
213
18
}
214
215
void FlushToken::_flush_group_memtable(std::shared_ptr<SharedMemtable> shared_memtable,
216
6
                                       WriteRequestType write_req_type, int64_t submit_task_time) {
217
6
    DCHECK(shared_memtable != nullptr);
218
6
    DCHECK(shared_memtable->memtable != nullptr);
219
6
    DCHECK(write_req_type == WriteRequestType::DATA ||
220
6
           write_req_type == WriteRequestType::ROW_BINLOG);
221
222
6
    auto* group_rowset_writer = typeid_cast<GroupRowsetWriter*>(_rowset_writer.get());
223
6
    DCHECK(group_rowset_writer != nullptr);
224
6
    auto flush_writer = write_req_type == WriteRequestType::DATA
225
6
                                ? group_rowset_writer->data_writer()
226
6
                                : group_rowset_writer->row_binlog_writer();
227
6
    DCHECK(flush_writer != nullptr);
228
6
    _flush_memtable_impl(flush_writer.get(), shared_memtable->memtable.get(),
229
6
                         shared_memtable->segment_id, submit_task_time, shared_memtable.get());
230
6
}
231
232
// NOTE: FlushToken's submit/cancel/wait run in one thread,
233
// so we don't need to make them mutually exclusive, std::atomic is enough.
234
18
void FlushToken::_wait_submit_task_finish() {
235
18
    std::unique_lock<std::mutex> lock(_mutex);
236
33
    _submit_task_finish_cond.wait(lock, [&]() { return _stats.flush_submit_count.load() == 0; });
237
18
}
238
239
32
void FlushToken::_wait_running_task_finish() {
240
32
    std::unique_lock<std::mutex> lock(_mutex);
241
32
    _running_task_finish_cond.wait(lock, [&]() { return _stats.flush_running_count.load() == 0; });
242
32
}
243
244
32
void FlushToken::cancel() {
245
32
    _shutdown_flush_token();
246
32
    _wait_running_task_finish();
247
32
}
248
249
18
Status FlushToken::wait() {
250
18
    _wait_submit_task_finish();
251
18
    {
252
18
        std::shared_lock rdlk(_flush_status_lock);
253
18
        if (!_flush_status.ok()) {
254
2
            return _flush_status;
255
2
        }
256
18
    }
257
16
    return Status::OK();
258
18
}
259
260
Status FlushToken::_try_reserve_memory(const std::shared_ptr<ResourceContext>& resource_context,
261
0
                                       int64_t size) {
262
0
    auto* thread_context = doris::thread_context();
263
0
    auto* memtable_flush_executor =
264
0
            ExecEnv::GetInstance()->storage_engine().memtable_flush_executor();
265
0
    Status st;
266
0
    int32_t max_waiting_time = config::memtable_wait_for_memory_sleep_time_s;
267
0
    do {
268
        // only try to reserve process memory
269
0
        st = thread_context->thread_mem_tracker_mgr->try_reserve(
270
0
                size, ThreadMemTrackerMgr::TryReserveChecker::CHECK_PROCESS);
271
0
        if (st.ok()) {
272
0
            memtable_flush_executor->inc_flushing_task();
273
0
            break;
274
0
        }
275
0
        if (_is_shutdown() || resource_context->task_controller()->is_cancelled()) {
276
0
            st = Status::Cancelled("flush memtable already cancelled");
277
0
            break;
278
0
        }
279
        // Make sure at least one memtable is flushing even reserve memory failed.
280
0
        if (memtable_flush_executor->check_and_inc_has_any_flushing_task()) {
281
            // If there are already any flushing task, Wait for some time and retry.
282
0
            LOG_EVERY_T(INFO, 60) << fmt::format(
283
0
                    "Failed to reserve memory {} for flush memtable, retry after 100ms",
284
0
                    PrettyPrinter::print_bytes(size));
285
0
            std::this_thread::sleep_for(std::chrono::seconds(1));
286
0
            max_waiting_time -= 1;
287
0
        } else {
288
0
            st = Status::OK();
289
0
            break;
290
0
        }
291
0
    } while (max_waiting_time > 0);
292
0
    return st;
293
0
}
294
295
Status FlushToken::_memtable2block(MemTable* memtable, SharedMemtable* shared_memtable,
296
18
                                   std::shared_ptr<Block>& flush_block) {
297
18
    DCHECK(memtable != nullptr);
298
299
18
    if (shared_memtable == nullptr) {
300
12
        std::unique_ptr<Block> block;
301
12
        RETURN_IF_ERROR(memtable->to_block(&block));
302
12
        flush_block.reset(block.release());
303
12
        return Status::OK();
304
12
    }
305
306
6
    std::call_once(shared_memtable->block_once, [&]() {
307
3
        std::unique_ptr<Block> block;
308
3
        shared_memtable->block_status = memtable->to_block(&block);
309
3
        if (shared_memtable->block_status.ok()) {
310
3
            shared_memtable->block.reset(block.release());
311
3
        }
312
3
    });
313
6
    if (!shared_memtable->block_status.ok()) {
314
0
        return shared_memtable->block_status;
315
0
    }
316
6
    flush_block = shared_memtable->block;
317
6
    DCHECK(flush_block != nullptr);
318
6
    return Status::OK();
319
6
}
320
321
void FlushToken::_flush_memtable_impl(RowsetWriter* flush_writer, MemTable* memtable,
322
                                      int32_t segment_id, int64_t submit_task_time,
323
18
                                      SharedMemtable* shared_memtable) {
324
18
    DCHECK(flush_writer != nullptr);
325
18
    DCHECK(memtable != nullptr);
326
327
18
    signal::set_signal_task_id(flush_writer->load_id());
328
18
    signal::tablet_id = memtable->tablet_id();
329
    // Count the task as running before registering the deferred cleanup so
330
    // cancel/shutdown paths keep flush_running_count symmetric on every exit.
331
18
    _stats.flush_running_count++;
332
18
    Defer defer {[&]() {
333
18
        std::lock_guard<std::mutex> lock(_mutex);
334
18
        _stats.flush_submit_count--;
335
18
        if (_stats.flush_submit_count == 0) {
336
15
            _submit_task_finish_cond.notify_one();
337
15
        }
338
18
        _stats.flush_running_count--;
339
18
        if (_stats.flush_running_count == 0) {
340
15
            _running_task_finish_cond.notify_one();
341
15
        }
342
18
    }};
343
18
    DBUG_EXECUTE_IF("FlushToken.flush_memtable.wait_before_first_shutdown",
344
18
                    { std::this_thread::sleep_for(std::chrono::milliseconds(10 * 1000)); });
345
18
    if (_is_shutdown()) {
346
0
        return;
347
0
    }
348
18
    DBUG_EXECUTE_IF("FlushToken.flush_memtable.wait_after_first_shutdown",
349
18
                    { std::this_thread::sleep_for(std::chrono::milliseconds(10 * 1000)); });
350
    // double check if shutdown to avoid wait running task finish count not accurate
351
18
    if (_is_shutdown()) {
352
0
        return;
353
0
    }
354
18
    DBUG_EXECUTE_IF("FlushToken.flush_memtable.wait_after_second_shutdown",
355
18
                    { std::this_thread::sleep_for(std::chrono::milliseconds(10 * 1000)); });
356
18
    uint64_t flush_wait_time_ns = MonotonicNanos() - submit_task_time;
357
18
    _stats.flush_wait_time_ns += flush_wait_time_ns;
358
    // If previous flush has failed, return directly
359
18
    {
360
18
        std::shared_lock rdlk(_flush_status_lock);
361
18
        if (!_flush_status.ok()) {
362
0
            return;
363
0
        }
364
18
    }
365
366
18
    MonotonicStopWatch timer;
367
18
    timer.start();
368
18
    size_t memory_usage = memtable->memory_usage();
369
370
18
    int64_t flush_size = 0;
371
18
    Status s;
372
18
    memtable->update_mem_type(MemType::FLUSH);
373
18
    int64_t duration_ns = 0;
374
18
    {
375
18
        s = [&]() {
376
18
            SCOPED_RAW_TIMER(&duration_ns);
377
18
            SCOPED_ATTACH_TASK(memtable->resource_ctx());
378
18
            SCOPED_SWITCH_THREAD_MEM_TRACKER_LIMITER(
379
18
                    memtable->resource_ctx()->memory_context()->mem_tracker()->write_tracker());
380
18
            SCOPED_CONSUME_MEM_TRACKER(memtable->mem_tracker());
381
382
            // DEFER_RELEASE_RESERVED();
383
384
            // auto reserve_size = memtable->get_flush_reserve_memory_size();
385
            // if (memtable->resource_ctx()->task_controller()->is_enable_reserve_memory() &&
386
            //     reserve_size > 0) {
387
            //     RETURN_IF_ERROR(_try_reserve_memory(memtable->resource_ctx(), reserve_size));
388
            // }
389
390
            // Defer defer {[&]() {
391
            //     ExecEnv::GetInstance()->storage_engine().memtable_flush_executor()->dec_flushing_task();
392
            // }};
393
18
            std::shared_ptr<Block> flush_block;
394
18
            RETURN_IF_ERROR(_memtable2block(memtable, shared_memtable, flush_block));
395
18
            if (flush_writer->context().write_binlog_opt().enable && flush_block->rows() > 0) {
396
3
                const auto& memtable_lsns = memtable->row_binlog_lsns();
397
3
                DCHECK_EQ(memtable_lsns.size(), flush_block->rows());
398
3
                auto lsn_ids = std::make_shared<std::vector<int64_t>>(memtable_lsns.begin(),
399
3
                                                                      memtable_lsns.end());
400
3
                const_cast<RowsetWriterContext&>(flush_writer->context())
401
3
                        .write_binlog_opt()
402
3
                        .write_binlog_config()
403
3
                        .insert_seg_lsn(segment_id, std::move(lsn_ids));
404
3
            }
405
18
            RETURN_IF_ERROR(
406
18
                    flush_writer->flush_memtable(flush_block.get(), segment_id, &flush_size));
407
16
            memtable->set_flush_success();
408
409
16
            return Status::OK();
410
18
        }();
411
412
18
        if (s.ok()) {
413
16
            bool record_memtable_stat = shared_memtable == nullptr;
414
16
            if (shared_memtable != nullptr) {
415
4
                auto finished_sub_task_count = shared_memtable->add_finished_sub_task() + 1;
416
4
                record_memtable_stat =
417
4
                        finished_sub_task_count == shared_memtable->total_sub_task_count.load();
418
4
            }
419
16
            if (record_memtable_stat) {
420
13
                _memtable_stat += memtable->stat();
421
13
            }
422
16
            DorisMetrics::instance()->memtable_flush_total->increment(1);
423
16
            DorisMetrics::instance()->memtable_flush_duration_us->increment(duration_ns / 1000);
424
16
        }
425
18
    }
426
427
18
    {
428
18
        std::shared_lock rdlk(_flush_status_lock);
429
18
        if (!_flush_status.ok()) {
430
0
            return;
431
0
        }
432
18
    }
433
18
    if (!s.ok()) {
434
2
        std::lock_guard wrlk(_flush_status_lock);
435
2
        if (_flush_status.ok()) {
436
2
            LOG(WARNING) << "Flush memtable failed with res = " << s
437
2
                         << ", load_id: " << print_id(flush_writer->load_id());
438
2
            _flush_status = s;
439
2
        }
440
2
        _shutdown_flush_token();
441
2
        return;
442
2
    }
443
444
16
    VLOG_CRITICAL << "flush memtable wait time: "
445
3
                  << PrettyPrinter::print(flush_wait_time_ns, TUnit::TIME_NS)
446
3
                  << ", flush memtable cost: "
447
3
                  << PrettyPrinter::print(timer.elapsed_time(), TUnit::TIME_NS)
448
3
                  << ", submit count: " << _stats.flush_submit_count
449
3
                  << ", running count: " << _stats.flush_running_count
450
3
                  << ", finish count: " << _stats.flush_finish_count
451
3
                  << ", mem size: " << PrettyPrinter::print_bytes(memory_usage)
452
3
                  << ", disk size: " << PrettyPrinter::print_bytes(flush_size);
453
16
    _stats.flush_time_ns += timer.elapsed_time();
454
16
    _stats.flush_finish_count++;
455
16
    _stats.flush_size_bytes += memtable->memory_usage();
456
16
    _stats.flush_disk_size_bytes += flush_size;
457
16
}
458
459
void FlushToken::_flush_memtable(std::shared_ptr<MemTable> memtable_ptr, int32_t segment_id,
460
12
                                 int64_t submit_task_time) {
461
12
    _flush_memtable_impl(_rowset_writer.get(), memtable_ptr.get(), segment_id, submit_task_time);
462
12
}
463
464
std::pair<int, int> MemTableFlushExecutor::calc_flush_thread_count(int num_cpus, int num_disk,
465
221
                                                                   int thread_num_per_store) {
466
221
    if (config::enable_adaptive_flush_threads && num_cpus > 0) {
467
205
        int min = std::max(1, (int)(num_cpus * config::min_flush_thread_num_per_cpu));
468
205
        int max = std::max(min, num_cpus * config::max_flush_thread_num_per_cpu);
469
205
        return {min, max};
470
205
    }
471
16
    int min = std::max(1, thread_num_per_store);
472
16
    int max = num_cpus == 0
473
16
                      ? num_disk * min
474
16
                      : std::min(num_disk * min, num_cpus * config::max_flush_thread_num_per_cpu);
475
16
    return {min, max};
476
221
}
477
478
97
void MemTableFlushExecutor::init(int num_disk) {
479
97
    _num_disk = std::max(1, num_disk);
480
97
    int num_cpus = std::thread::hardware_concurrency();
481
482
97
    auto [min_threads, max_threads] =
483
97
            calc_flush_thread_count(num_cpus, _num_disk, config::flush_thread_num_per_store);
484
97
    static_cast<void>(ThreadPoolBuilder("MemTableFlushThreadPool")
485
97
                              .set_min_threads(min_threads)
486
97
                              .set_max_threads(max_threads)
487
97
                              .build(&_flush_pool));
488
489
97
    auto [hi_min, hi_max] = calc_flush_thread_count(
490
97
            num_cpus, _num_disk, config::high_priority_flush_thread_num_per_store);
491
97
    static_cast<void>(ThreadPoolBuilder("MemTableHighPriorityFlushThreadPool")
492
97
                              .set_min_threads(hi_min)
493
97
                              .set_max_threads(hi_max)
494
97
                              .build(&_high_prio_flush_pool));
495
97
}
496
497
11
void MemTableFlushExecutor::update_memtable_flush_threads() {
498
11
    int num_cpus = std::thread::hardware_concurrency();
499
500
11
    auto [min_threads, max_threads] =
501
11
            calc_flush_thread_count(num_cpus, _num_disk, config::flush_thread_num_per_store);
502
    // Update max_threads first to avoid constraint violation when increasing min_threads
503
11
    static_cast<void>(_flush_pool->set_max_threads(max_threads));
504
11
    static_cast<void>(_flush_pool->set_min_threads(min_threads));
505
506
11
    auto [hi_min, hi_max] = calc_flush_thread_count(
507
11
            num_cpus, _num_disk, config::high_priority_flush_thread_num_per_store);
508
    // Update max_threads first to avoid constraint violation when increasing min_threads
509
11
    static_cast<void>(_high_prio_flush_pool->set_max_threads(hi_max));
510
11
    static_cast<void>(_high_prio_flush_pool->set_min_threads(hi_min));
511
11
}
512
513
// NOTE: we use SERIAL mode here to ensure all mem-tables from one tablet are flushed in order.
514
Status MemTableFlushExecutor::create_flush_token(
515
        std::shared_ptr<FlushToken>& flush_token, std::shared_ptr<RowsetWriter> rowset_writer,
516
        bool is_high_priority, std::shared_ptr<WorkloadGroup> wg_sptr,
517
18
        std::shared_ptr<OlapTableSchemaParam> table_schema_param) {
518
18
    switch (rowset_writer->type()) {
519
0
    case ALPHA_ROWSET:
520
        // alpha rowset do not support flush in CONCURRENT.  and not support alpha rowset now.
521
0
        return Status::InternalError<false>("not support alpha rowset load now.");
522
18
    case BETA_ROWSET: {
523
        // beta rowset can be flush in CONCURRENT, because each memtable using a new segment writer.
524
18
        ThreadPool* pool = is_high_priority ? _high_prio_flush_pool.get() : _flush_pool.get();
525
18
        flush_token = FlushToken::create_shared(pool, wg_sptr);
526
18
        flush_token->set_rowset_writer(rowset_writer);
527
18
        flush_token->set_table_schema_param(std::move(table_schema_param));
528
18
        return Status::OK();
529
0
    }
530
0
    default:
531
0
        return Status::InternalError<false>("unknown rowset type.");
532
18
    }
533
18
}
534
535
} // namespace doris