[branch-2.1](memory) Fix reserve memory compatible with memory GC and logging (#37682)
pick #36307 #36412
This commit is contained in:
@ -50,6 +50,7 @@
|
||||
#include "runtime/memory/global_memory_arbitrator.h"
|
||||
#include "runtime/memory/mem_tracker.h"
|
||||
#include "runtime/memory/mem_tracker_limiter.h"
|
||||
#include "runtime/memory/memory_arbitrator.h"
|
||||
#include "runtime/runtime_query_statistics_mgr.h"
|
||||
#include "runtime/workload_group/workload_group_manager.h"
|
||||
#include "util/cpu_info.h"
|
||||
@ -192,7 +193,7 @@ void Daemon::memory_maintenance_thread() {
|
||||
// Refresh process memory metrics.
|
||||
doris::PerfCounters::refresh_proc_status();
|
||||
doris::MemInfo::refresh_proc_meminfo();
|
||||
doris::GlobalMemoryArbitrator::refresh_vm_rss_sub_allocator_cache();
|
||||
doris::GlobalMemoryArbitrator::reset_refresh_interval_memory_growth();
|
||||
|
||||
// Update and print memory stat when the memory changes by 256M.
|
||||
if (abs(last_print_proc_mem - PerfCounters::get_vm_rss()) > 268435456) {
|
||||
@ -229,11 +230,11 @@ void Daemon::memory_gc_thread() {
|
||||
if (config::disable_memory_gc) {
|
||||
continue;
|
||||
}
|
||||
auto sys_mem_available = doris::MemInfo::sys_mem_available();
|
||||
auto sys_mem_available = doris::GlobalMemoryArbitrator::sys_mem_available();
|
||||
auto process_memory_usage = doris::GlobalMemoryArbitrator::process_memory_usage();
|
||||
|
||||
// GC excess memory for resource groups that not enable overcommit
|
||||
auto tg_free_mem = doris::MemInfo::tg_disable_overcommit_group_gc();
|
||||
auto tg_free_mem = doris::MemoryArbitrator::tg_disable_overcommit_group_gc();
|
||||
sys_mem_available += tg_free_mem;
|
||||
process_memory_usage -= tg_free_mem;
|
||||
|
||||
@ -241,13 +242,13 @@ void Daemon::memory_gc_thread() {
|
||||
(sys_mem_available < doris::MemInfo::sys_mem_available_low_water_mark() ||
|
||||
process_memory_usage >= doris::MemInfo::mem_limit())) {
|
||||
// No longer full gc and minor gc during sleep.
|
||||
std::string mem_info =
|
||||
doris::GlobalMemoryArbitrator::process_limit_exceeded_errmsg_str();
|
||||
memory_full_gc_sleep_time_ms = memory_gc_sleep_time_ms;
|
||||
memory_minor_gc_sleep_time_ms = memory_gc_sleep_time_ms;
|
||||
LOG(INFO) << fmt::format(
|
||||
"[MemoryGC] start full GC, {}.",
|
||||
doris::GlobalMemoryArbitrator::process_limit_exceeded_errmsg_str());
|
||||
LOG(INFO) << fmt::format("[MemoryGC] start full GC, {}.", mem_info);
|
||||
doris::MemTrackerLimiter::print_log_process_usage();
|
||||
if (doris::MemInfo::process_full_gc()) {
|
||||
if (doris::MemoryArbitrator::process_full_gc(std::move(mem_info))) {
|
||||
// If there is not enough memory to be gc, the process memory usage will not be printed in the next continuous gc.
|
||||
doris::MemTrackerLimiter::enable_print_log_process_usage();
|
||||
}
|
||||
@ -255,12 +256,12 @@ void Daemon::memory_gc_thread() {
|
||||
(sys_mem_available < doris::MemInfo::sys_mem_available_warning_water_mark() ||
|
||||
process_memory_usage >= doris::MemInfo::soft_mem_limit())) {
|
||||
// No minor gc during sleep, but full gc is possible.
|
||||
std::string mem_info =
|
||||
doris::GlobalMemoryArbitrator::process_soft_limit_exceeded_errmsg_str();
|
||||
memory_minor_gc_sleep_time_ms = memory_gc_sleep_time_ms;
|
||||
LOG(INFO) << fmt::format(
|
||||
"[MemoryGC] start minor GC, {}.",
|
||||
doris::GlobalMemoryArbitrator::process_soft_limit_exceeded_errmsg_str());
|
||||
LOG(INFO) << fmt::format("[MemoryGC] start minor GC, {}.", mem_info);
|
||||
doris::MemTrackerLimiter::print_log_process_usage();
|
||||
if (doris::MemInfo::process_minor_gc()) {
|
||||
if (doris::MemoryArbitrator::process_minor_gc(std::move(mem_info))) {
|
||||
doris::MemTrackerLimiter::enable_print_log_process_usage();
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -40,6 +40,7 @@
|
||||
#include "gutil/strings/substitute.h"
|
||||
#include "http/action/tablets_info_action.h"
|
||||
#include "http/web_page_handler.h"
|
||||
#include "runtime/memory/global_memory_arbitrator.h"
|
||||
#include "runtime/memory/mem_tracker.h"
|
||||
#include "runtime/memory/mem_tracker_limiter.h"
|
||||
#include "util/easy_json.h"
|
||||
@ -155,6 +156,8 @@ void mem_tracker_handler(const WebPageHandler::ArgumentMap& args, std::stringstr
|
||||
MemTrackerLimiter::Type::SCHEMA_CHANGE);
|
||||
} else if (iter->second == "other") {
|
||||
MemTrackerLimiter::make_type_snapshots(&snapshots, MemTrackerLimiter::Type::OTHER);
|
||||
} else if (iter->second == "reserved_memory") {
|
||||
GlobalMemoryArbitrator::make_reserved_memory_snapshots(&snapshots);
|
||||
}
|
||||
} else {
|
||||
(*output) << "<h4>*Notice:</h4>\n";
|
||||
|
||||
@ -80,8 +80,8 @@ void MemTableMemoryLimiter::register_writer(std::weak_ptr<MemTableWriter> writer
|
||||
int64_t MemTableMemoryLimiter::_avail_mem_lack() {
|
||||
// reserve a small amount of memory so we do not trigger MinorGC
|
||||
auto reserved_mem = doris::MemInfo::sys_mem_available_low_water_mark();
|
||||
auto avail_mem_lack =
|
||||
doris::MemInfo::sys_mem_available_warning_water_mark() - MemInfo::sys_mem_available();
|
||||
auto avail_mem_lack = doris::MemInfo::sys_mem_available_warning_water_mark() -
|
||||
doris::GlobalMemoryArbitrator::sys_mem_available();
|
||||
return avail_mem_lack + reserved_mem;
|
||||
}
|
||||
|
||||
@ -225,14 +225,13 @@ void MemTableMemoryLimiter::refresh_mem_tracker() {
|
||||
_log_timer.reset();
|
||||
// if not exist load task, this log should not be printed.
|
||||
if (_mem_usage != 0) {
|
||||
LOG(INFO) << ss.str() << ", process mem: " << PerfCounters::get_vm_rss_str()
|
||||
<< " (without allocator cache: "
|
||||
<< PrettyPrinter::print_bytes(GlobalMemoryArbitrator::process_memory_usage())
|
||||
<< "), load mem: " << PrettyPrinter::print_bytes(_mem_tracker->consumption())
|
||||
<< ", memtable writers num: " << _writers.size()
|
||||
<< " (active: " << PrettyPrinter::print_bytes(_active_mem_usage)
|
||||
<< ", write: " << PrettyPrinter::print_bytes(_write_mem_usage)
|
||||
<< ", flush: " << PrettyPrinter::print_bytes(_flush_mem_usage) << ")";
|
||||
LOG(INFO) << fmt::format(
|
||||
"{}, {}, load mem: {}, memtable writers num: {} (active: {}, write: {}, flush: {})",
|
||||
ss.str(), GlobalMemoryArbitrator::process_memory_used_details_str(),
|
||||
PrettyPrinter::print_bytes(_mem_tracker->consumption()), _writers.size(),
|
||||
PrettyPrinter::print_bytes(_active_mem_usage),
|
||||
PrettyPrinter::print_bytes(_write_mem_usage),
|
||||
PrettyPrinter::print_bytes(_flush_mem_usage));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -19,16 +19,64 @@
|
||||
|
||||
#include <bvar/bvar.h>
|
||||
|
||||
#include "runtime/thread_context.h"
|
||||
|
||||
namespace doris {
|
||||
|
||||
std::mutex GlobalMemoryArbitrator::_reserved_trackers_lock;
|
||||
std::unordered_map<std::string, MemTracker::MemCounter> GlobalMemoryArbitrator::_reserved_trackers;
|
||||
|
||||
bvar::PassiveStatus<int64_t> g_vm_rss_sub_allocator_cache(
|
||||
"meminfo_vm_rss_sub_allocator_cache",
|
||||
[](void*) { return GlobalMemoryArbitrator::vm_rss_sub_allocator_cache(); }, nullptr);
|
||||
bvar::PassiveStatus<int64_t> g_process_memory_usage(
|
||||
"meminfo_process_memory_usage",
|
||||
[](void*) { return GlobalMemoryArbitrator::process_memory_usage(); }, nullptr);
|
||||
bvar::PassiveStatus<int64_t> g_sys_mem_avail(
|
||||
"meminfo_sys_mem_avail", [](void*) { return GlobalMemoryArbitrator::sys_mem_available(); },
|
||||
nullptr);
|
||||
|
||||
std::atomic<int64_t> GlobalMemoryArbitrator::_s_vm_rss_sub_allocator_cache = -1;
|
||||
std::atomic<int64_t> GlobalMemoryArbitrator::_s_process_reserved_memory = 0;
|
||||
std::atomic<int64_t> GlobalMemoryArbitrator::refresh_interval_memory_growth = 0;
|
||||
|
||||
bool GlobalMemoryArbitrator::try_reserve_process_memory(int64_t bytes) {
|
||||
if (sys_mem_available() - bytes < MemInfo::sys_mem_available_low_water_mark()) {
|
||||
return false;
|
||||
}
|
||||
int64_t old_reserved_mem = _s_process_reserved_memory.load(std::memory_order_relaxed);
|
||||
int64_t new_reserved_mem = 0;
|
||||
do {
|
||||
new_reserved_mem = old_reserved_mem + bytes;
|
||||
if (UNLIKELY(vm_rss_sub_allocator_cache() +
|
||||
refresh_interval_memory_growth.load(std::memory_order_relaxed) +
|
||||
new_reserved_mem >=
|
||||
MemInfo::mem_limit())) {
|
||||
return false;
|
||||
}
|
||||
} while (!_s_process_reserved_memory.compare_exchange_weak(old_reserved_mem, new_reserved_mem,
|
||||
std::memory_order_relaxed));
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_reserved_trackers_lock);
|
||||
_reserved_trackers[doris::thread_context()->thread_mem_tracker()->label()].add(bytes);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void GlobalMemoryArbitrator::release_process_reserved_memory(int64_t bytes) {
|
||||
_s_process_reserved_memory.fetch_sub(bytes, std::memory_order_relaxed);
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_reserved_trackers_lock);
|
||||
auto label = doris::thread_context()->thread_mem_tracker()->label();
|
||||
auto it = _reserved_trackers.find(label);
|
||||
if (it == _reserved_trackers.end()) {
|
||||
DCHECK(false) << "release unknown reserved memory " << label << ", bytes: " << bytes;
|
||||
return;
|
||||
}
|
||||
_reserved_trackers[label].sub(bytes);
|
||||
if (_reserved_trackers[label].current_value() == 0) {
|
||||
_reserved_trackers.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace doris
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "runtime/memory/mem_tracker.h"
|
||||
#include "util/mem_info.h"
|
||||
|
||||
namespace doris {
|
||||
@ -30,14 +31,12 @@ public:
|
||||
* accurate, since those pages are not really RSS but a memory
|
||||
* that can be used at anytime via jemalloc.
|
||||
*/
|
||||
static inline void refresh_vm_rss_sub_allocator_cache() {
|
||||
_s_vm_rss_sub_allocator_cache.store(
|
||||
PerfCounters::get_vm_rss() - static_cast<int64_t>(MemInfo::allocator_cache_mem()),
|
||||
std::memory_order_relaxed);
|
||||
MemInfo::refresh_interval_memory_growth = 0;
|
||||
}
|
||||
static inline int64_t vm_rss_sub_allocator_cache() {
|
||||
return _s_vm_rss_sub_allocator_cache.load(std::memory_order_relaxed);
|
||||
return PerfCounters::get_vm_rss() - static_cast<int64_t>(MemInfo::allocator_cache_mem());
|
||||
}
|
||||
|
||||
static inline void reset_refresh_interval_memory_growth() {
|
||||
refresh_interval_memory_growth = 0;
|
||||
}
|
||||
|
||||
// If need to use process memory in your execution logic, pls use it.
|
||||
@ -45,32 +44,80 @@ public:
|
||||
// add reserved memory and growth memory since the last vm_rss update.
|
||||
static inline int64_t process_memory_usage() {
|
||||
return vm_rss_sub_allocator_cache() +
|
||||
MemInfo::refresh_interval_memory_growth.load(std::memory_order_relaxed) +
|
||||
refresh_interval_memory_growth.load(std::memory_order_relaxed) +
|
||||
process_reserved_memory();
|
||||
}
|
||||
|
||||
static inline bool try_reserve_process_memory(int64_t bytes) {
|
||||
if (MemInfo::sys_mem_available() - bytes < MemInfo::sys_mem_available_low_water_mark()) {
|
||||
return false;
|
||||
}
|
||||
int64_t old_reserved_mem = _s_process_reserved_memory.load(std::memory_order_relaxed);
|
||||
int64_t new_reserved_mem = 0;
|
||||
do {
|
||||
new_reserved_mem = old_reserved_mem + bytes;
|
||||
if (UNLIKELY(vm_rss_sub_allocator_cache() +
|
||||
MemInfo::refresh_interval_memory_growth.load(
|
||||
std::memory_order_relaxed) +
|
||||
new_reserved_mem >=
|
||||
MemInfo::mem_limit())) {
|
||||
return false;
|
||||
}
|
||||
} while (!_s_process_reserved_memory.compare_exchange_weak(
|
||||
old_reserved_mem, new_reserved_mem, std::memory_order_relaxed));
|
||||
return true;
|
||||
static std::string process_memory_used_str() {
|
||||
auto msg = fmt::format("process memory used {}",
|
||||
PrettyPrinter::print(process_memory_usage(), TUnit::BYTES));
|
||||
#ifdef ADDRESS_SANITIZER
|
||||
msg = "[ASAN]" + msg;
|
||||
#endif
|
||||
return msg;
|
||||
}
|
||||
|
||||
static inline void release_process_reserved_memory(int64_t bytes) {
|
||||
_s_process_reserved_memory.fetch_sub(bytes, std::memory_order_relaxed);
|
||||
static std::string process_memory_used_details_str() {
|
||||
auto msg = fmt::format(
|
||||
"process memory used {}(= {}[vm/rss] - {}[tc/jemalloc_cache] + {}[reserved] + "
|
||||
"{}B[waiting_refresh])",
|
||||
PrettyPrinter::print(process_memory_usage(), TUnit::BYTES),
|
||||
PerfCounters::get_vm_rss_str(),
|
||||
PrettyPrinter::print(static_cast<uint64_t>(MemInfo::allocator_cache_mem()),
|
||||
TUnit::BYTES),
|
||||
PrettyPrinter::print(process_reserved_memory(), TUnit::BYTES),
|
||||
refresh_interval_memory_growth);
|
||||
#ifdef ADDRESS_SANITIZER
|
||||
msg = "[ASAN]" + msg;
|
||||
#endif
|
||||
return msg;
|
||||
}
|
||||
|
||||
static inline int64_t sys_mem_available() {
|
||||
return MemInfo::_s_sys_mem_available.load(std::memory_order_relaxed) -
|
||||
refresh_interval_memory_growth.load(std::memory_order_relaxed) -
|
||||
process_reserved_memory();
|
||||
}
|
||||
|
||||
static inline std::string sys_mem_available_str() {
|
||||
auto msg = fmt::format("sys available memory {}",
|
||||
PrettyPrinter::print(sys_mem_available(), TUnit::BYTES));
|
||||
#ifdef ADDRESS_SANITIZER
|
||||
msg = "[ASAN]" + msg;
|
||||
#endif
|
||||
return msg;
|
||||
}
|
||||
|
||||
static inline std::string sys_mem_available_details_str() {
|
||||
auto msg = fmt::format(
|
||||
"sys available memory {}(= {}[proc/available] - {}[reserved] - "
|
||||
"{}B[waiting_refresh])",
|
||||
PrettyPrinter::print(sys_mem_available(), TUnit::BYTES),
|
||||
PrettyPrinter::print(MemInfo::_s_sys_mem_available.load(std::memory_order_relaxed),
|
||||
TUnit::BYTES),
|
||||
PrettyPrinter::print(process_reserved_memory(), TUnit::BYTES),
|
||||
refresh_interval_memory_growth);
|
||||
#ifdef ADDRESS_SANITIZER
|
||||
msg = "[ASAN]" + msg;
|
||||
#endif
|
||||
return msg;
|
||||
}
|
||||
|
||||
static bool try_reserve_process_memory(int64_t bytes);
|
||||
static void release_process_reserved_memory(int64_t bytes);
|
||||
|
||||
static inline void make_reserved_memory_snapshots(
|
||||
std::vector<MemTracker::Snapshot>* snapshots) {
|
||||
std::lock_guard<std::mutex> l(_reserved_trackers_lock);
|
||||
for (const auto& pair : _reserved_trackers) {
|
||||
MemTracker::Snapshot snapshot;
|
||||
snapshot.type = "reserved_memory";
|
||||
snapshot.label = pair.first;
|
||||
snapshot.limit = -1;
|
||||
snapshot.cur_consumption = pair.second.current_value();
|
||||
snapshot.peak_consumption = pair.second.peak_value();
|
||||
(*snapshots).emplace_back(snapshot);
|
||||
}
|
||||
}
|
||||
|
||||
static inline int64_t process_reserved_memory() {
|
||||
@ -79,8 +126,7 @@ public:
|
||||
|
||||
static bool is_exceed_soft_mem_limit(int64_t bytes = 0) {
|
||||
return process_memory_usage() + bytes >= MemInfo::soft_mem_limit() ||
|
||||
MemInfo::sys_mem_available() - bytes <
|
||||
MemInfo::sys_mem_available_warning_water_mark();
|
||||
sys_mem_available() - bytes < MemInfo::sys_mem_available_warning_water_mark();
|
||||
}
|
||||
|
||||
static bool is_exceed_hard_mem_limit(int64_t bytes = 0) {
|
||||
@ -93,44 +139,45 @@ public:
|
||||
// because `new/malloc` will trigger mem hook when using tcmalloc/jemalloc allocator cache,
|
||||
// but it may not actually alloc physical memory, which is not expected in mem hook fail.
|
||||
return process_memory_usage() + bytes >= MemInfo::mem_limit() ||
|
||||
MemInfo::sys_mem_available() - bytes < MemInfo::sys_mem_available_low_water_mark();
|
||||
sys_mem_available() - bytes < MemInfo::sys_mem_available_low_water_mark();
|
||||
}
|
||||
|
||||
static std::string process_mem_log_str() {
|
||||
return fmt::format(
|
||||
"os physical memory {}. process memory used {}, limit {}, soft limit {}. sys "
|
||||
"available memory {}, low water mark {}, warning water mark {}. Refresh interval "
|
||||
"memory growth {} B",
|
||||
PrettyPrinter::print(MemInfo::physical_mem(), TUnit::BYTES),
|
||||
PerfCounters::get_vm_rss_str(), MemInfo::mem_limit_str(),
|
||||
MemInfo::soft_mem_limit_str(), MemInfo::sys_mem_available_str(),
|
||||
PrettyPrinter::print(MemInfo::sys_mem_available_low_water_mark(), TUnit::BYTES),
|
||||
PrettyPrinter::print(MemInfo::sys_mem_available_warning_water_mark(), TUnit::BYTES),
|
||||
MemInfo::refresh_interval_memory_growth);
|
||||
}
|
||||
|
||||
static std::string process_limit_exceeded_errmsg_str() {
|
||||
return fmt::format(
|
||||
"process memory used {} exceed limit {} or sys available memory {} less than low "
|
||||
"water mark {}",
|
||||
PerfCounters::get_vm_rss_str(), MemInfo::mem_limit_str(),
|
||||
MemInfo::sys_mem_available_str(),
|
||||
PrettyPrinter::print(MemInfo::sys_mem_available_low_water_mark(), TUnit::BYTES));
|
||||
}
|
||||
|
||||
static std::string process_soft_limit_exceeded_errmsg_str() {
|
||||
return fmt::format(
|
||||
"process memory used {} exceed soft limit {} or sys available memory {} less than "
|
||||
"os physical memory {}. {}, limit {}, soft limit {}. {}, low water mark {}, "
|
||||
"warning water mark {}.",
|
||||
PerfCounters::get_vm_rss_str(), MemInfo::soft_mem_limit_str(),
|
||||
MemInfo::sys_mem_available_str(),
|
||||
PrettyPrinter::print(MemInfo::physical_mem(), TUnit::BYTES),
|
||||
process_memory_used_details_str(), MemInfo::mem_limit_str(),
|
||||
MemInfo::soft_mem_limit_str(), sys_mem_available_details_str(),
|
||||
PrettyPrinter::print(MemInfo::sys_mem_available_low_water_mark(), TUnit::BYTES),
|
||||
PrettyPrinter::print(MemInfo::sys_mem_available_warning_water_mark(),
|
||||
TUnit::BYTES));
|
||||
}
|
||||
|
||||
static std::string process_limit_exceeded_errmsg_str() {
|
||||
return fmt::format(
|
||||
"{} exceed limit {} or {} less than low water mark {}", process_memory_used_str(),
|
||||
MemInfo::mem_limit_str(), sys_mem_available_str(),
|
||||
PrettyPrinter::print(MemInfo::sys_mem_available_low_water_mark(), TUnit::BYTES));
|
||||
}
|
||||
|
||||
static std::string process_soft_limit_exceeded_errmsg_str() {
|
||||
return fmt::format("{} exceed soft limit {} or {} less than warning water mark {}.",
|
||||
process_memory_used_str(), MemInfo::soft_mem_limit_str(),
|
||||
sys_mem_available_str(),
|
||||
PrettyPrinter::print(MemInfo::sys_mem_available_warning_water_mark(),
|
||||
TUnit::BYTES));
|
||||
}
|
||||
|
||||
// It is only used after the memory limit is exceeded. When multiple threads are waiting for the available memory of the process,
|
||||
// avoid multiple threads starting at the same time and causing OOM.
|
||||
static std::atomic<int64_t> refresh_interval_memory_growth;
|
||||
|
||||
private:
|
||||
static std::atomic<int64_t> _s_vm_rss_sub_allocator_cache;
|
||||
static std::atomic<int64_t> _s_process_reserved_memory;
|
||||
|
||||
static std::mutex _reserved_trackers_lock;
|
||||
static std::unordered_map<std::string, MemTracker::MemCounter> _reserved_trackers;
|
||||
};
|
||||
|
||||
} // namespace doris
|
||||
|
||||
@ -216,6 +216,13 @@ void MemTrackerLimiter::make_process_snapshots(std::vector<MemTracker::Snapshot>
|
||||
snapshot.peak_consumption = PerfCounters::get_vm_hwm();
|
||||
(*snapshots).emplace_back(snapshot);
|
||||
|
||||
snapshot.type = "reserved memory";
|
||||
snapshot.label = "";
|
||||
snapshot.limit = -1;
|
||||
snapshot.cur_consumption = GlobalMemoryArbitrator::process_reserved_memory();
|
||||
snapshot.peak_consumption = -1;
|
||||
(*snapshots).emplace_back(snapshot);
|
||||
|
||||
snapshot.type = "process virtual memory"; // from /proc VmSize VmPeak
|
||||
snapshot.label = "";
|
||||
snapshot.limit = -1;
|
||||
@ -359,10 +366,10 @@ void MemTrackerLimiter::print_log_process_usage() {
|
||||
std::string MemTrackerLimiter::tracker_limit_exceeded_str() {
|
||||
std::string err_msg = fmt::format(
|
||||
"memory tracker limit exceeded, tracker label:{}, type:{}, limit "
|
||||
"{}, peak used {}, current used {}. backend {} process memory used {}.",
|
||||
"{}, peak used {}, current used {}. backend {}, {}.",
|
||||
label(), type_string(_type), print_bytes(limit()),
|
||||
print_bytes(_consumption->peak_value()), print_bytes(_consumption->current_value()),
|
||||
BackendOptions::get_localhost(), PerfCounters::get_vm_rss_str());
|
||||
BackendOptions::get_localhost(), GlobalMemoryArbitrator::process_memory_used_str());
|
||||
if (_type == Type::QUERY || _type == Type::LOAD) {
|
||||
err_msg += fmt::format(
|
||||
" exec node:<{}>, can `set exec_mem_limit=8G` to change limit, details see "
|
||||
@ -377,23 +384,17 @@ std::string MemTrackerLimiter::tracker_limit_exceeded_str() {
|
||||
}
|
||||
|
||||
int64_t MemTrackerLimiter::free_top_memory_query(int64_t min_free_mem,
|
||||
const std::string& vm_rss_str,
|
||||
const std::string& mem_available_str,
|
||||
const std::string& cancel_reason,
|
||||
RuntimeProfile* profile, Type type) {
|
||||
return free_top_memory_query(
|
||||
min_free_mem, type, ExecEnv::GetInstance()->mem_tracker_limiter_pool,
|
||||
[&vm_rss_str, &mem_available_str, &type](int64_t mem_consumption,
|
||||
const std::string& label) {
|
||||
[&cancel_reason, &type](int64_t mem_consumption, const std::string& label) {
|
||||
return fmt::format(
|
||||
"Process has no memory available, cancel top memory used {}: "
|
||||
"{} memory tracker <{}> consumption {}, backend {} "
|
||||
"process memory used {} exceed limit {} or sys available memory {} "
|
||||
"less than low water mark {}. Execute again after enough memory, "
|
||||
"details see be.INFO.",
|
||||
type_string(type), type_string(type), label, print_bytes(mem_consumption),
|
||||
BackendOptions::get_localhost(), vm_rss_str, MemInfo::mem_limit_str(),
|
||||
mem_available_str,
|
||||
print_bytes(MemInfo::sys_mem_available_low_water_mark()));
|
||||
"Process memory not enough, cancel top memory used {}: "
|
||||
"<{}> consumption {}, backend {}, {}. Execute again "
|
||||
"after enough memory, details see be.INFO.",
|
||||
type_string(type), label, print_bytes(mem_consumption),
|
||||
BackendOptions::get_localhost(), cancel_reason);
|
||||
},
|
||||
profile, GCType::PROCESS);
|
||||
}
|
||||
@ -504,23 +505,17 @@ int64_t MemTrackerLimiter::free_top_memory_query(
|
||||
}
|
||||
|
||||
int64_t MemTrackerLimiter::free_top_overcommit_query(int64_t min_free_mem,
|
||||
const std::string& vm_rss_str,
|
||||
const std::string& mem_available_str,
|
||||
const std::string& cancel_reason,
|
||||
RuntimeProfile* profile, Type type) {
|
||||
return free_top_overcommit_query(
|
||||
min_free_mem, type, ExecEnv::GetInstance()->mem_tracker_limiter_pool,
|
||||
[&vm_rss_str, &mem_available_str, &type](int64_t mem_consumption,
|
||||
const std::string& label) {
|
||||
[&cancel_reason, &type](int64_t mem_consumption, const std::string& label) {
|
||||
return fmt::format(
|
||||
"Process has less memory, cancel top memory overcommit {}: "
|
||||
"{} memory tracker <{}> consumption {}, backend {} "
|
||||
"process memory used {} exceed soft limit {} or sys available memory {} "
|
||||
"less than warning water mark {}. Execute again after enough memory, "
|
||||
"details see be.INFO.",
|
||||
type_string(type), type_string(type), label, print_bytes(mem_consumption),
|
||||
BackendOptions::get_localhost(), vm_rss_str, MemInfo::soft_mem_limit_str(),
|
||||
mem_available_str,
|
||||
print_bytes(MemInfo::sys_mem_available_warning_water_mark()));
|
||||
"Process memory not enough, cancel top memory overcommit {}: "
|
||||
"<{}> consumption {}, backend {}, {}. Execute again "
|
||||
"after enough memory, details see be.INFO.",
|
||||
type_string(type), label, print_bytes(mem_consumption),
|
||||
BackendOptions::get_localhost(), cancel_reason);
|
||||
},
|
||||
profile, GCType::PROCESS);
|
||||
}
|
||||
|
||||
@ -141,7 +141,7 @@ public:
|
||||
return true;
|
||||
}
|
||||
bool st = true;
|
||||
if (is_overcommit_tracker() && config::enable_query_memory_overcommit) {
|
||||
if (is_overcommit_tracker() && !config::enable_query_memory_overcommit) {
|
||||
st = _consumption->try_add(bytes, _limit);
|
||||
} else {
|
||||
_consumption->add(bytes);
|
||||
@ -192,9 +192,8 @@ public:
|
||||
static void print_log_process_usage();
|
||||
|
||||
// Start canceling from the query with the largest memory usage until the memory of min_free_mem size is freed.
|
||||
// vm_rss_str and mem_available_str recorded when gc is triggered, for log printing.
|
||||
static int64_t free_top_memory_query(int64_t min_free_mem, const std::string& vm_rss_str,
|
||||
const std::string& mem_available_str,
|
||||
// cancel_reason recorded when gc is triggered, for log printing.
|
||||
static int64_t free_top_memory_query(int64_t min_free_mem, const std::string& cancel_reason,
|
||||
RuntimeProfile* profile, Type type = Type::QUERY);
|
||||
|
||||
static int64_t free_top_memory_query(
|
||||
@ -202,16 +201,13 @@ public:
|
||||
const std::function<std::string(int64_t, const std::string&)>& cancel_msg,
|
||||
RuntimeProfile* profile, GCType gctype);
|
||||
|
||||
static int64_t free_top_memory_load(int64_t min_free_mem, const std::string& vm_rss_str,
|
||||
const std::string& mem_available_str,
|
||||
static int64_t free_top_memory_load(int64_t min_free_mem, const std::string& cancel_reason,
|
||||
RuntimeProfile* profile) {
|
||||
return free_top_memory_query(min_free_mem, vm_rss_str, mem_available_str, profile,
|
||||
Type::LOAD);
|
||||
return free_top_memory_query(min_free_mem, cancel_reason, profile, Type::LOAD);
|
||||
}
|
||||
// Start canceling from the query with the largest memory overcommit ratio until the memory
|
||||
// of min_free_mem size is freed.
|
||||
static int64_t free_top_overcommit_query(int64_t min_free_mem, const std::string& vm_rss_str,
|
||||
const std::string& mem_available_str,
|
||||
static int64_t free_top_overcommit_query(int64_t min_free_mem, const std::string& cancel_reason,
|
||||
RuntimeProfile* profile, Type type = Type::QUERY);
|
||||
|
||||
static int64_t free_top_overcommit_query(
|
||||
@ -219,11 +215,9 @@ public:
|
||||
const std::function<std::string(int64_t, const std::string&)>& cancel_msg,
|
||||
RuntimeProfile* profile, GCType gctype);
|
||||
|
||||
static int64_t free_top_overcommit_load(int64_t min_free_mem, const std::string& vm_rss_str,
|
||||
const std::string& mem_available_str,
|
||||
static int64_t free_top_overcommit_load(int64_t min_free_mem, const std::string& cancel_reason,
|
||||
RuntimeProfile* profile) {
|
||||
return free_top_overcommit_query(min_free_mem, vm_rss_str, mem_available_str, profile,
|
||||
Type::LOAD);
|
||||
return free_top_overcommit_query(min_free_mem, cancel_reason, profile, Type::LOAD);
|
||||
}
|
||||
|
||||
// only for Type::QUERY or Type::LOAD.
|
||||
|
||||
271
be/src/runtime/memory/memory_arbitrator.cpp
Normal file
271
be/src/runtime/memory/memory_arbitrator.cpp
Normal file
@ -0,0 +1,271 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
#include "runtime/memory/memory_arbitrator.h"
|
||||
|
||||
#include "runtime/memory/cache_manager.h"
|
||||
#include "runtime/workload_group/workload_group.h"
|
||||
#include "runtime/workload_group/workload_group_manager.h"
|
||||
#include "util/mem_info.h"
|
||||
#include "util/runtime_profile.h"
|
||||
#include "util/stopwatch.hpp"
|
||||
|
||||
namespace doris {
|
||||
|
||||
// step1: free all cache
|
||||
// step2: free resource groups memory that enable overcommit
|
||||
// step3: free global top overcommit query, if enable query memory overcommit
|
||||
// TODO Now, the meaning is different from java minor gc + full gc, more like small gc + large gc.
|
||||
bool MemoryArbitrator::process_minor_gc(std::string mem_info) {
|
||||
MonotonicStopWatch watch;
|
||||
watch.start();
|
||||
int64_t freed_mem = 0;
|
||||
std::unique_ptr<RuntimeProfile> profile = std::make_unique<RuntimeProfile>("");
|
||||
|
||||
Defer defer {[&]() {
|
||||
MemInfo::notify_je_purge_dirty_pages();
|
||||
std::stringstream ss;
|
||||
profile->pretty_print(&ss);
|
||||
LOG(INFO) << fmt::format(
|
||||
"[MemoryGC] end minor GC, free memory {}. cost(us): {}, details: {}",
|
||||
PrettyPrinter::print(freed_mem, TUnit::BYTES), watch.elapsed_time() / 1000,
|
||||
ss.str());
|
||||
}};
|
||||
|
||||
freed_mem += CacheManager::instance()->for_each_cache_prune_stale(profile.get());
|
||||
MemInfo::notify_je_purge_dirty_pages();
|
||||
if (freed_mem > MemInfo::process_minor_gc_size()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (config::enable_workload_group_memory_gc) {
|
||||
RuntimeProfile* tg_profile = profile->create_child("WorkloadGroup", true, true);
|
||||
freed_mem += tg_enable_overcommit_group_gc(MemInfo::process_minor_gc_size() - freed_mem,
|
||||
tg_profile, true);
|
||||
if (freed_mem > MemInfo::process_minor_gc_size()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (config::enable_query_memory_overcommit) {
|
||||
VLOG_NOTICE << MemTrackerLimiter::type_detail_usage(
|
||||
"[MemoryGC] before free top memory overcommit query in minor GC",
|
||||
MemTrackerLimiter::Type::QUERY);
|
||||
RuntimeProfile* toq_profile =
|
||||
profile->create_child("FreeTopOvercommitMemoryQuery", true, true);
|
||||
freed_mem += MemTrackerLimiter::free_top_overcommit_query(
|
||||
MemInfo::process_minor_gc_size() - freed_mem, mem_info, toq_profile);
|
||||
if (freed_mem > MemInfo::process_minor_gc_size()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// step1: free all cache
|
||||
// step2: free resource groups memory that enable overcommit
|
||||
// step3: free global top memory query
|
||||
// step4: free top overcommit load, load retries are more expensive, So cancel at the end.
|
||||
// step5: free top memory load
|
||||
bool MemoryArbitrator::process_full_gc(std::string mem_info) {
|
||||
MonotonicStopWatch watch;
|
||||
watch.start();
|
||||
int64_t freed_mem = 0;
|
||||
std::unique_ptr<RuntimeProfile> profile = std::make_unique<RuntimeProfile>("");
|
||||
|
||||
Defer defer {[&]() {
|
||||
MemInfo::notify_je_purge_dirty_pages();
|
||||
std::stringstream ss;
|
||||
profile->pretty_print(&ss);
|
||||
LOG(INFO) << fmt::format(
|
||||
"[MemoryGC] end full GC, free Memory {}. cost(us): {}, details: {}",
|
||||
PrettyPrinter::print(freed_mem, TUnit::BYTES), watch.elapsed_time() / 1000,
|
||||
ss.str());
|
||||
}};
|
||||
|
||||
freed_mem += CacheManager::instance()->for_each_cache_prune_all(profile.get());
|
||||
MemInfo::notify_je_purge_dirty_pages();
|
||||
if (freed_mem > MemInfo::process_full_gc_size()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (config::enable_workload_group_memory_gc) {
|
||||
RuntimeProfile* tg_profile = profile->create_child("WorkloadGroup", true, true);
|
||||
freed_mem += tg_enable_overcommit_group_gc(MemInfo::process_full_gc_size() - freed_mem,
|
||||
tg_profile, false);
|
||||
if (freed_mem > MemInfo::process_full_gc_size()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
VLOG_NOTICE << MemTrackerLimiter::type_detail_usage(
|
||||
"[MemoryGC] before free top memory query in full GC", MemTrackerLimiter::Type::QUERY);
|
||||
RuntimeProfile* tmq_profile = profile->create_child("FreeTopMemoryQuery", true, true);
|
||||
freed_mem += MemTrackerLimiter::free_top_memory_query(
|
||||
MemInfo::process_full_gc_size() - freed_mem, mem_info, tmq_profile);
|
||||
if (freed_mem > MemInfo::process_full_gc_size()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (config::enable_query_memory_overcommit) {
|
||||
VLOG_NOTICE << MemTrackerLimiter::type_detail_usage(
|
||||
"[MemoryGC] before free top memory overcommit load in full GC",
|
||||
MemTrackerLimiter::Type::LOAD);
|
||||
RuntimeProfile* tol_profile =
|
||||
profile->create_child("FreeTopMemoryOvercommitLoad", true, true);
|
||||
freed_mem += MemTrackerLimiter::free_top_overcommit_load(
|
||||
MemInfo::process_full_gc_size() - freed_mem, mem_info, tol_profile);
|
||||
if (freed_mem > MemInfo::process_full_gc_size()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
VLOG_NOTICE << MemTrackerLimiter::type_detail_usage(
|
||||
"[MemoryGC] before free top memory load in full GC", MemTrackerLimiter::Type::LOAD);
|
||||
RuntimeProfile* tml_profile = profile->create_child("FreeTopMemoryLoad", true, true);
|
||||
freed_mem += MemTrackerLimiter::free_top_memory_load(
|
||||
MemInfo::process_full_gc_size() - freed_mem, mem_info, tml_profile);
|
||||
return freed_mem > MemInfo::process_full_gc_size();
|
||||
}
|
||||
|
||||
int64_t MemoryArbitrator::tg_disable_overcommit_group_gc() {
|
||||
MonotonicStopWatch watch;
|
||||
watch.start();
|
||||
std::vector<WorkloadGroupPtr> task_groups;
|
||||
std::unique_ptr<RuntimeProfile> tg_profile = std::make_unique<RuntimeProfile>("WorkloadGroup");
|
||||
int64_t total_free_memory = 0;
|
||||
|
||||
ExecEnv::GetInstance()->workload_group_mgr()->get_related_workload_groups(
|
||||
[](const WorkloadGroupPtr& workload_group) {
|
||||
return workload_group->is_mem_limit_valid() &&
|
||||
!workload_group->enable_memory_overcommit();
|
||||
},
|
||||
&task_groups);
|
||||
if (task_groups.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::vector<WorkloadGroupPtr> task_groups_overcommit;
|
||||
for (const auto& workload_group : task_groups) {
|
||||
if (workload_group->memory_used() > workload_group->memory_limit()) {
|
||||
task_groups_overcommit.push_back(workload_group);
|
||||
}
|
||||
}
|
||||
if (task_groups_overcommit.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
LOG(INFO) << fmt::format(
|
||||
"[MemoryGC] start GC work load group that not enable overcommit, number of overcommit "
|
||||
"group: {}, "
|
||||
"if it exceeds the limit, try free size = (group used - group limit).",
|
||||
task_groups_overcommit.size());
|
||||
|
||||
Defer defer {[&]() {
|
||||
if (total_free_memory > 0) {
|
||||
std::stringstream ss;
|
||||
tg_profile->pretty_print(&ss);
|
||||
LOG(INFO) << fmt::format(
|
||||
"[MemoryGC] end GC work load group that not enable overcommit, number of "
|
||||
"overcommit group: {}, free memory {}. cost(us): {}, details: {}",
|
||||
task_groups_overcommit.size(),
|
||||
PrettyPrinter::print(total_free_memory, TUnit::BYTES),
|
||||
watch.elapsed_time() / 1000, ss.str());
|
||||
}
|
||||
}};
|
||||
|
||||
for (const auto& workload_group : task_groups_overcommit) {
|
||||
auto used = workload_group->memory_used();
|
||||
total_free_memory += workload_group->gc_memory(used - workload_group->memory_limit(),
|
||||
tg_profile.get(), false);
|
||||
}
|
||||
return total_free_memory;
|
||||
}
|
||||
|
||||
int64_t MemoryArbitrator::tg_enable_overcommit_group_gc(int64_t request_free_memory,
|
||||
RuntimeProfile* profile, bool is_minor_gc) {
|
||||
MonotonicStopWatch watch;
|
||||
watch.start();
|
||||
std::vector<WorkloadGroupPtr> task_groups;
|
||||
ExecEnv::GetInstance()->workload_group_mgr()->get_related_workload_groups(
|
||||
[](const WorkloadGroupPtr& workload_group) {
|
||||
return workload_group->is_mem_limit_valid() &&
|
||||
workload_group->enable_memory_overcommit();
|
||||
},
|
||||
&task_groups);
|
||||
if (task_groups.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int64_t total_exceeded_memory = 0;
|
||||
std::vector<int64_t> used_memorys;
|
||||
std::vector<int64_t> exceeded_memorys;
|
||||
for (const auto& workload_group : task_groups) {
|
||||
int64_t used_memory = workload_group->memory_used();
|
||||
int64_t exceeded = used_memory - workload_group->memory_limit();
|
||||
int64_t exceeded_memory = exceeded > 0 ? exceeded : 0;
|
||||
total_exceeded_memory += exceeded_memory;
|
||||
used_memorys.emplace_back(used_memory);
|
||||
exceeded_memorys.emplace_back(exceeded_memory);
|
||||
}
|
||||
|
||||
int64_t total_free_memory = 0;
|
||||
bool gc_all_exceeded = request_free_memory >= total_exceeded_memory;
|
||||
std::string log_prefix = fmt::format(
|
||||
"work load group that enable overcommit, number of group: {}, request_free_memory:{}, "
|
||||
"total_exceeded_memory:{}",
|
||||
task_groups.size(), request_free_memory, total_exceeded_memory);
|
||||
if (gc_all_exceeded) {
|
||||
LOG(INFO) << fmt::format(
|
||||
"[MemoryGC] start GC {}, request more than exceeded, try free size = (group used - "
|
||||
"group limit).",
|
||||
log_prefix);
|
||||
} else {
|
||||
LOG(INFO) << fmt::format(
|
||||
"[MemoryGC] start GC {}, request less than exceeded, try free size = ((group used "
|
||||
"- group limit) / all group total_exceeded_memory) * request_free_memory.",
|
||||
log_prefix);
|
||||
}
|
||||
|
||||
Defer defer {[&]() {
|
||||
if (total_free_memory > 0) {
|
||||
std::stringstream ss;
|
||||
profile->pretty_print(&ss);
|
||||
LOG(INFO) << fmt::format(
|
||||
"[MemoryGC] end GC {}, free memory {}. cost(us): {}, details: {}", log_prefix,
|
||||
PrettyPrinter::print(total_free_memory, TUnit::BYTES),
|
||||
watch.elapsed_time() / 1000, ss.str());
|
||||
}
|
||||
}};
|
||||
|
||||
for (int i = 0; i < task_groups.size(); ++i) {
|
||||
if (exceeded_memorys[i] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// todo: GC according to resource group priority
|
||||
auto tg_need_free_memory = int64_t(
|
||||
gc_all_exceeded ? exceeded_memorys[i]
|
||||
: static_cast<double>(exceeded_memorys[i]) / total_exceeded_memory *
|
||||
request_free_memory); // exceeded memory as a weight
|
||||
auto workload_group = task_groups[i];
|
||||
total_free_memory += workload_group->gc_memory(tg_need_free_memory, profile, is_minor_gc);
|
||||
}
|
||||
return total_free_memory;
|
||||
}
|
||||
|
||||
} // namespace doris
|
||||
40
be/src/runtime/memory/memory_arbitrator.h
Normal file
40
be/src/runtime/memory/memory_arbitrator.h
Normal file
@ -0,0 +1,40 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "runtime/memory/global_memory_arbitrator.h"
|
||||
|
||||
namespace doris {
|
||||
|
||||
class MemoryArbitrator {
|
||||
public:
|
||||
static bool process_minor_gc(
|
||||
std::string mem_info =
|
||||
doris::GlobalMemoryArbitrator::process_soft_limit_exceeded_errmsg_str());
|
||||
static bool process_full_gc(
|
||||
std::string mem_info =
|
||||
doris::GlobalMemoryArbitrator::process_limit_exceeded_errmsg_str());
|
||||
|
||||
static int64_t tg_disable_overcommit_group_gc();
|
||||
static int64_t tg_enable_overcommit_group_gc(int64_t request_free_memory,
|
||||
RuntimeProfile* profile, bool is_minor_gc);
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
} // namespace doris
|
||||
@ -173,18 +173,20 @@ int64_t WorkloadGroup::gc_memory(int64_t need_free_mem, RuntimeProfile* profile,
|
||||
MemTracker::print_bytes(_memory_limit), BackendOptions::get_localhost());
|
||||
}
|
||||
}
|
||||
std::string process_mem_usage_str = GlobalMemoryArbitrator::process_mem_log_str();
|
||||
auto cancel_top_overcommit_str = [cancel_str, process_mem_usage_str](int64_t mem_consumption,
|
||||
const std::string& label) {
|
||||
auto cancel_top_overcommit_str = [cancel_str](int64_t mem_consumption,
|
||||
const std::string& label) {
|
||||
return fmt::format(
|
||||
"{} cancel top memory overcommit tracker <{}> consumption {}. details:{}",
|
||||
cancel_str, label, MemTracker::print_bytes(mem_consumption), process_mem_usage_str);
|
||||
"{} cancel top memory overcommit tracker <{}> consumption {}. details:{}, Execute "
|
||||
"again after enough memory, details see be.INFO.",
|
||||
cancel_str, label, MemTracker::print_bytes(mem_consumption),
|
||||
GlobalMemoryArbitrator::process_limit_exceeded_errmsg_str());
|
||||
};
|
||||
auto cancel_top_usage_str = [cancel_str, process_mem_usage_str](int64_t mem_consumption,
|
||||
const std::string& label) {
|
||||
return fmt::format("{} cancel top memory used tracker <{}> consumption {}. details:{}",
|
||||
cancel_str, label, MemTracker::print_bytes(mem_consumption),
|
||||
process_mem_usage_str);
|
||||
auto cancel_top_usage_str = [cancel_str](int64_t mem_consumption, const std::string& label) {
|
||||
return fmt::format(
|
||||
"{} cancel top memory used tracker <{}> consumption {}. details:{}, Execute again "
|
||||
"after enough memory, details see be.INFO.",
|
||||
cancel_str, label, MemTracker::print_bytes(mem_consumption),
|
||||
GlobalMemoryArbitrator::process_soft_limit_exceeded_errmsg_str());
|
||||
};
|
||||
|
||||
LOG(INFO) << fmt::format(
|
||||
|
||||
@ -193,14 +193,11 @@ void WorkloadGroupMgr::refresh_wg_memory_info() {
|
||||
// we count these cache memories equally on workload groups.
|
||||
double ratio = (double)proc_vm_rss / (double)all_queries_mem_used;
|
||||
if (ratio <= 1.25) {
|
||||
std::string debug_msg = fmt::format(
|
||||
"\nProcess Memory Summary: process_vm_rss: {}, process mem: {}, sys mem available: "
|
||||
"{}, all quries mem: {}",
|
||||
PrettyPrinter::print(proc_vm_rss, TUnit::BYTES),
|
||||
PrettyPrinter::print(doris::GlobalMemoryArbitrator::process_memory_usage(),
|
||||
TUnit::BYTES),
|
||||
doris::MemInfo::sys_mem_available_str(),
|
||||
PrettyPrinter::print(all_queries_mem_used, TUnit::BYTES));
|
||||
std::string debug_msg =
|
||||
fmt::format("\nProcess Memory Summary: {}, {}, all quries mem: {}",
|
||||
doris::GlobalMemoryArbitrator::process_memory_used_details_str(),
|
||||
doris::GlobalMemoryArbitrator::sys_mem_available_details_str(),
|
||||
PrettyPrinter::print(all_queries_mem_used, TUnit::BYTES));
|
||||
LOG_EVERY_T(INFO, 10) << debug_msg;
|
||||
}
|
||||
|
||||
|
||||
@ -39,33 +39,20 @@
|
||||
#include "common/config.h"
|
||||
#include "common/status.h"
|
||||
#include "gutil/strings/split.h"
|
||||
#include "runtime/exec_env.h"
|
||||
#include "runtime/memory/cache_manager.h"
|
||||
#include "runtime/memory/mem_tracker_limiter.h"
|
||||
#include "runtime/workload_group/workload_group.h"
|
||||
#include "runtime/workload_group/workload_group_manager.h"
|
||||
#include "util/cgroup_util.h"
|
||||
#include "util/defer_op.h"
|
||||
#include "util/parse_util.h"
|
||||
#include "util/pretty_printer.h"
|
||||
#include "util/runtime_profile.h"
|
||||
#include "util/stopwatch.hpp"
|
||||
#include "util/string_parser.hpp"
|
||||
|
||||
namespace doris {
|
||||
|
||||
bvar::PassiveStatus<int64_t> g_sys_mem_avail(
|
||||
"meminfo_sys_mem_avail", [](void*) { return MemInfo::sys_mem_available(); }, nullptr);
|
||||
|
||||
bool MemInfo::_s_initialized = false;
|
||||
std::atomic<int64_t> MemInfo::_s_physical_mem = std::numeric_limits<int64_t>::max();
|
||||
std::atomic<int64_t> MemInfo::_s_mem_limit = std::numeric_limits<int64_t>::max();
|
||||
std::atomic<int64_t> MemInfo::_s_soft_mem_limit = std::numeric_limits<int64_t>::max();
|
||||
|
||||
std::atomic<int64_t> MemInfo::_s_allocator_cache_mem = 0;
|
||||
std::string MemInfo::_s_allocator_cache_mem_str = "";
|
||||
std::atomic<int64_t> MemInfo::_s_virtual_memory_used = 0;
|
||||
std::atomic<int64_t> MemInfo::refresh_interval_memory_growth = 0;
|
||||
|
||||
int64_t MemInfo::_s_cgroup_mem_limit = std::numeric_limits<int64_t>::max();
|
||||
int64_t MemInfo::_s_cgroup_mem_usage = std::numeric_limits<int64_t>::min();
|
||||
@ -99,9 +86,6 @@ void MemInfo::refresh_allocator_mem() {
|
||||
get_je_metrics("stats.metadata") +
|
||||
get_je_all_arena_metrics("pdirty") * get_page_size(),
|
||||
std::memory_order_relaxed);
|
||||
_s_allocator_cache_mem_str = PrettyPrinter::print(
|
||||
static_cast<uint64_t>(_s_allocator_cache_mem.load(std::memory_order_relaxed)),
|
||||
TUnit::BYTES);
|
||||
_s_virtual_memory_used.store(get_je_metrics("stats.mapped"), std::memory_order_relaxed);
|
||||
#else
|
||||
_s_allocator_cache_mem.store(get_tc_metrics("tcmalloc.pageheap_free_bytes") +
|
||||
@ -109,265 +93,12 @@ void MemInfo::refresh_allocator_mem() {
|
||||
get_tc_metrics("tcmalloc.transfer_cache_free_bytes") +
|
||||
get_tc_metrics("tcmalloc.thread_cache_free_bytes"),
|
||||
std::memory_order_relaxed);
|
||||
_s_allocator_cache_mem_str = PrettyPrinter::print(
|
||||
static_cast<uint64_t>(_s_allocator_cache_mem.load(std::memory_order_relaxed)),
|
||||
TUnit::BYTES);
|
||||
_s_virtual_memory_used.store(get_tc_metrics("generic.total_physical_bytes") +
|
||||
get_tc_metrics("tcmalloc.pageheap_unmapped_bytes"),
|
||||
std::memory_order_relaxed);
|
||||
#endif
|
||||
}
|
||||
|
||||
// step1: free all cache
|
||||
// step2: free resource groups memory that enable overcommit
|
||||
// step3: free global top overcommit query, if enable query memory overcommit
|
||||
// TODO Now, the meaning is different from java minor gc + full gc, more like small gc + large gc.
|
||||
bool MemInfo::process_minor_gc() {
|
||||
MonotonicStopWatch watch;
|
||||
watch.start();
|
||||
int64_t freed_mem = 0;
|
||||
std::unique_ptr<RuntimeProfile> profile = std::make_unique<RuntimeProfile>("");
|
||||
std::string pre_vm_rss = PerfCounters::get_vm_rss_str();
|
||||
std::string pre_sys_mem_available = MemInfo::sys_mem_available_str();
|
||||
|
||||
Defer defer {[&]() {
|
||||
MemInfo::notify_je_purge_dirty_pages();
|
||||
std::stringstream ss;
|
||||
profile->pretty_print(&ss);
|
||||
LOG(INFO) << fmt::format(
|
||||
"[MemoryGC] end minor GC, free memory {}. cost(us): {}, details: {}",
|
||||
PrettyPrinter::print(freed_mem, TUnit::BYTES), watch.elapsed_time() / 1000,
|
||||
ss.str());
|
||||
}};
|
||||
|
||||
freed_mem += CacheManager::instance()->for_each_cache_prune_stale(profile.get());
|
||||
MemInfo::notify_je_purge_dirty_pages();
|
||||
if (freed_mem > MemInfo::process_minor_gc_size()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (config::enable_workload_group_memory_gc) {
|
||||
RuntimeProfile* tg_profile = profile->create_child("WorkloadGroup", true, true);
|
||||
freed_mem += tg_enable_overcommit_group_gc(MemInfo::process_minor_gc_size() - freed_mem,
|
||||
tg_profile, true);
|
||||
if (freed_mem > MemInfo::process_minor_gc_size()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (config::enable_query_memory_overcommit) {
|
||||
VLOG_NOTICE << MemTrackerLimiter::type_detail_usage(
|
||||
"[MemoryGC] before free top memory overcommit query in minor GC",
|
||||
MemTrackerLimiter::Type::QUERY);
|
||||
RuntimeProfile* toq_profile =
|
||||
profile->create_child("FreeTopOvercommitMemoryQuery", true, true);
|
||||
freed_mem += MemTrackerLimiter::free_top_overcommit_query(
|
||||
MemInfo::process_minor_gc_size() - freed_mem, pre_vm_rss, pre_sys_mem_available,
|
||||
toq_profile);
|
||||
if (freed_mem > MemInfo::process_minor_gc_size()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// step1: free all cache
|
||||
// step2: free resource groups memory that enable overcommit
|
||||
// step3: free global top memory query
|
||||
// step4: free top overcommit load, load retries are more expensive, So cancel at the end.
|
||||
// step5: free top memory load
|
||||
bool MemInfo::process_full_gc() {
|
||||
MonotonicStopWatch watch;
|
||||
watch.start();
|
||||
int64_t freed_mem = 0;
|
||||
std::unique_ptr<RuntimeProfile> profile = std::make_unique<RuntimeProfile>("");
|
||||
std::string pre_vm_rss = PerfCounters::get_vm_rss_str();
|
||||
std::string pre_sys_mem_available = MemInfo::sys_mem_available_str();
|
||||
|
||||
Defer defer {[&]() {
|
||||
MemInfo::notify_je_purge_dirty_pages();
|
||||
std::stringstream ss;
|
||||
profile->pretty_print(&ss);
|
||||
LOG(INFO) << fmt::format(
|
||||
"[MemoryGC] end full GC, free Memory {}. cost(us): {}, details: {}",
|
||||
PrettyPrinter::print(freed_mem, TUnit::BYTES), watch.elapsed_time() / 1000,
|
||||
ss.str());
|
||||
}};
|
||||
|
||||
freed_mem += CacheManager::instance()->for_each_cache_prune_all(profile.get());
|
||||
MemInfo::notify_je_purge_dirty_pages();
|
||||
if (freed_mem > MemInfo::process_full_gc_size()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (config::enable_workload_group_memory_gc) {
|
||||
RuntimeProfile* tg_profile = profile->create_child("WorkloadGroup", true, true);
|
||||
freed_mem += tg_enable_overcommit_group_gc(MemInfo::process_full_gc_size() - freed_mem,
|
||||
tg_profile, false);
|
||||
if (freed_mem > MemInfo::process_full_gc_size()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
VLOG_NOTICE << MemTrackerLimiter::type_detail_usage(
|
||||
"[MemoryGC] before free top memory query in full GC", MemTrackerLimiter::Type::QUERY);
|
||||
RuntimeProfile* tmq_profile = profile->create_child("FreeTopMemoryQuery", true, true);
|
||||
freed_mem += MemTrackerLimiter::free_top_memory_query(
|
||||
MemInfo::process_full_gc_size() - freed_mem, pre_vm_rss, pre_sys_mem_available,
|
||||
tmq_profile);
|
||||
if (freed_mem > MemInfo::process_full_gc_size()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (config::enable_query_memory_overcommit) {
|
||||
VLOG_NOTICE << MemTrackerLimiter::type_detail_usage(
|
||||
"[MemoryGC] before free top memory overcommit load in full GC",
|
||||
MemTrackerLimiter::Type::LOAD);
|
||||
RuntimeProfile* tol_profile =
|
||||
profile->create_child("FreeTopMemoryOvercommitLoad", true, true);
|
||||
freed_mem += MemTrackerLimiter::free_top_overcommit_load(
|
||||
MemInfo::process_full_gc_size() - freed_mem, pre_vm_rss, pre_sys_mem_available,
|
||||
tol_profile);
|
||||
if (freed_mem > MemInfo::process_full_gc_size()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
VLOG_NOTICE << MemTrackerLimiter::type_detail_usage(
|
||||
"[MemoryGC] before free top memory load in full GC", MemTrackerLimiter::Type::LOAD);
|
||||
RuntimeProfile* tml_profile = profile->create_child("FreeTopMemoryLoad", true, true);
|
||||
freed_mem +=
|
||||
MemTrackerLimiter::free_top_memory_load(MemInfo::process_full_gc_size() - freed_mem,
|
||||
pre_vm_rss, pre_sys_mem_available, tml_profile);
|
||||
return freed_mem > MemInfo::process_full_gc_size();
|
||||
}
|
||||
|
||||
int64_t MemInfo::tg_disable_overcommit_group_gc() {
|
||||
MonotonicStopWatch watch;
|
||||
watch.start();
|
||||
std::vector<WorkloadGroupPtr> task_groups;
|
||||
std::unique_ptr<RuntimeProfile> tg_profile = std::make_unique<RuntimeProfile>("WorkloadGroup");
|
||||
int64_t total_free_memory = 0;
|
||||
|
||||
ExecEnv::GetInstance()->workload_group_mgr()->get_related_workload_groups(
|
||||
[](const WorkloadGroupPtr& workload_group) {
|
||||
return workload_group->is_mem_limit_valid() &&
|
||||
!workload_group->enable_memory_overcommit();
|
||||
},
|
||||
&task_groups);
|
||||
if (task_groups.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::vector<WorkloadGroupPtr> task_groups_overcommit;
|
||||
for (const auto& workload_group : task_groups) {
|
||||
if (workload_group->memory_used() > workload_group->memory_limit()) {
|
||||
task_groups_overcommit.push_back(workload_group);
|
||||
}
|
||||
}
|
||||
if (task_groups_overcommit.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
LOG(INFO) << fmt::format(
|
||||
"[MemoryGC] start GC work load group that not enable overcommit, number of overcommit "
|
||||
"group: {}, "
|
||||
"if it exceeds the limit, try free size = (group used - group limit).",
|
||||
task_groups_overcommit.size());
|
||||
|
||||
Defer defer {[&]() {
|
||||
if (total_free_memory > 0) {
|
||||
std::stringstream ss;
|
||||
tg_profile->pretty_print(&ss);
|
||||
LOG(INFO) << fmt::format(
|
||||
"[MemoryGC] end GC work load group that not enable overcommit, number of "
|
||||
"overcommit group: {}, free memory {}. cost(us): {}, details: {}",
|
||||
task_groups_overcommit.size(),
|
||||
PrettyPrinter::print(total_free_memory, TUnit::BYTES),
|
||||
watch.elapsed_time() / 1000, ss.str());
|
||||
}
|
||||
}};
|
||||
|
||||
for (const auto& workload_group : task_groups_overcommit) {
|
||||
auto used = workload_group->memory_used();
|
||||
total_free_memory += workload_group->gc_memory(used - workload_group->memory_limit(),
|
||||
tg_profile.get(), false);
|
||||
}
|
||||
return total_free_memory;
|
||||
}
|
||||
|
||||
int64_t MemInfo::tg_enable_overcommit_group_gc(int64_t request_free_memory, RuntimeProfile* profile,
|
||||
bool is_minor_gc) {
|
||||
MonotonicStopWatch watch;
|
||||
watch.start();
|
||||
std::vector<WorkloadGroupPtr> task_groups;
|
||||
ExecEnv::GetInstance()->workload_group_mgr()->get_related_workload_groups(
|
||||
[](const WorkloadGroupPtr& workload_group) {
|
||||
return workload_group->is_mem_limit_valid() &&
|
||||
workload_group->enable_memory_overcommit();
|
||||
},
|
||||
&task_groups);
|
||||
if (task_groups.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int64_t total_exceeded_memory = 0;
|
||||
std::vector<int64_t> used_memorys;
|
||||
std::vector<int64_t> exceeded_memorys;
|
||||
for (const auto& workload_group : task_groups) {
|
||||
int64_t used_memory = workload_group->memory_used();
|
||||
int64_t exceeded = used_memory - workload_group->memory_limit();
|
||||
int64_t exceeded_memory = exceeded > 0 ? exceeded : 0;
|
||||
total_exceeded_memory += exceeded_memory;
|
||||
used_memorys.emplace_back(used_memory);
|
||||
exceeded_memorys.emplace_back(exceeded_memory);
|
||||
}
|
||||
|
||||
int64_t total_free_memory = 0;
|
||||
bool gc_all_exceeded = request_free_memory >= total_exceeded_memory;
|
||||
std::string log_prefix = fmt::format(
|
||||
"work load group that enable overcommit, number of group: {}, request_free_memory:{}, "
|
||||
"total_exceeded_memory:{}",
|
||||
task_groups.size(), request_free_memory, total_exceeded_memory);
|
||||
if (gc_all_exceeded) {
|
||||
LOG(INFO) << fmt::format(
|
||||
"[MemoryGC] start GC {}, request more than exceeded, try free size = (group used - "
|
||||
"group limit).",
|
||||
log_prefix);
|
||||
} else {
|
||||
LOG(INFO) << fmt::format(
|
||||
"[MemoryGC] start GC {}, request less than exceeded, try free size = ((group used "
|
||||
"- group limit) / all group total_exceeded_memory) * request_free_memory.",
|
||||
log_prefix);
|
||||
}
|
||||
|
||||
Defer defer {[&]() {
|
||||
if (total_free_memory > 0) {
|
||||
std::stringstream ss;
|
||||
profile->pretty_print(&ss);
|
||||
LOG(INFO) << fmt::format(
|
||||
"[MemoryGC] end GC {}, free memory {}. cost(us): {}, details: {}", log_prefix,
|
||||
PrettyPrinter::print(total_free_memory, TUnit::BYTES),
|
||||
watch.elapsed_time() / 1000, ss.str());
|
||||
}
|
||||
}};
|
||||
|
||||
for (int i = 0; i < task_groups.size(); ++i) {
|
||||
if (exceeded_memorys[i] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// todo: GC according to resource group priority
|
||||
auto tg_need_free_memory = int64_t(
|
||||
gc_all_exceeded ? exceeded_memorys[i]
|
||||
: static_cast<double>(exceeded_memorys[i]) / total_exceeded_memory *
|
||||
request_free_memory); // exceeded memory as a weight
|
||||
auto workload_group = task_groups[i];
|
||||
total_free_memory += workload_group->gc_memory(tg_need_free_memory, profile, is_minor_gc);
|
||||
}
|
||||
return total_free_memory;
|
||||
}
|
||||
|
||||
#ifndef __APPLE__
|
||||
void MemInfo::refresh_proc_meminfo() {
|
||||
std::ifstream meminfo("/proc/meminfo", std::ios::in);
|
||||
@ -546,13 +277,15 @@ void MemInfo::init() {
|
||||
getline(vminfo, line);
|
||||
boost::algorithm::trim(line);
|
||||
StringParser::ParseResult result;
|
||||
int64_t mem_value = StringParser::string_to_int<int64_t>(line.data(), line.size(), &result);
|
||||
auto mem_value = StringParser::string_to_int<int64_t>(line.data(), line.size(), &result);
|
||||
|
||||
if (result == StringParser::PARSE_SUCCESS) {
|
||||
_s_vm_min_free_kbytes = mem_value * 1024L;
|
||||
}
|
||||
}
|
||||
if (vminfo.is_open()) vminfo.close();
|
||||
if (vminfo.is_open()) {
|
||||
vminfo.close();
|
||||
}
|
||||
|
||||
// Redhat 4.x OS, `/proc/meminfo` has no `MemAvailable`.
|
||||
if (_mem_info_bytes.find("MemAvailable") != _mem_info_bytes.end()) {
|
||||
@ -576,7 +309,9 @@ void MemInfo::init() {
|
||||
std::string hugepage_enable;
|
||||
// If file not exist, getline returns an empty string.
|
||||
getline(sys_transparent_hugepage, hugepage_enable);
|
||||
if (sys_transparent_hugepage.is_open()) sys_transparent_hugepage.close();
|
||||
if (sys_transparent_hugepage.is_open()) {
|
||||
sys_transparent_hugepage.close();
|
||||
}
|
||||
if (hugepage_enable == "[always] madvise never") {
|
||||
std::cout << "[WARNING!] /sys/kernel/mm/transparent_hugepage/enabled: " << hugepage_enable
|
||||
<< ", Doris not recommend turning on THP, which may cause the BE process to use "
|
||||
@ -591,7 +326,9 @@ void MemInfo::init() {
|
||||
std::ifstream sys_vm("/proc/sys/vm/overcommit_memory", std::ios::in);
|
||||
std::string vm_overcommit;
|
||||
getline(sys_vm, vm_overcommit);
|
||||
if (sys_vm.is_open()) sys_vm.close();
|
||||
if (sys_vm.is_open()) {
|
||||
sys_vm.close();
|
||||
}
|
||||
if (!vm_overcommit.empty() && std::stoi(vm_overcommit) == 2) {
|
||||
std::cout << "[WARNING!] /proc/sys/vm/overcommit_memory: " << vm_overcommit
|
||||
<< ", expect is 1, memory limit check is handed over to Doris Allocator, "
|
||||
@ -632,12 +369,11 @@ void MemInfo::init() {
|
||||
|
||||
std::string MemInfo::debug_string() {
|
||||
DCHECK(_s_initialized);
|
||||
CGroupUtil util;
|
||||
std::stringstream stream;
|
||||
stream << "Physical Memory: " << PrettyPrinter::print(_s_physical_mem, TUnit::BYTES)
|
||||
<< std::endl;
|
||||
stream << "Memory Limt: " << PrettyPrinter::print(_s_mem_limit, TUnit::BYTES) << std::endl;
|
||||
stream << "CGroup Info: " << util.debug_string() << std::endl;
|
||||
stream << "CGroup Info: " << doris::CGroupUtil::debug_string() << std::endl;
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
|
||||
@ -73,19 +73,6 @@ public:
|
||||
|
||||
static void refresh_proc_meminfo();
|
||||
|
||||
static inline int64_t sys_mem_available() {
|
||||
return _s_sys_mem_available.load(std::memory_order_relaxed) -
|
||||
refresh_interval_memory_growth;
|
||||
}
|
||||
static inline std::string sys_mem_available_str() {
|
||||
#ifdef ADDRESS_SANITIZER
|
||||
return "[ASAN]" + PrettyPrinter::print(_s_sys_mem_available.load(std::memory_order_relaxed),
|
||||
TUnit::BYTES);
|
||||
#else
|
||||
return PrettyPrinter::print(_s_sys_mem_available.load(std::memory_order_relaxed),
|
||||
TUnit::BYTES);
|
||||
#endif
|
||||
}
|
||||
static inline int64_t sys_mem_available_low_water_mark() {
|
||||
return _s_sys_mem_available_low_water_mark;
|
||||
}
|
||||
@ -157,7 +144,6 @@ public:
|
||||
static inline size_t allocator_cache_mem() {
|
||||
return _s_allocator_cache_mem.load(std::memory_order_relaxed);
|
||||
}
|
||||
static inline std::string allocator_cache_mem_str() { return _s_allocator_cache_mem_str; }
|
||||
|
||||
// Tcmalloc property `generic.total_physical_bytes` records the total length of the virtual memory
|
||||
// obtained by the process malloc, not the physical memory actually used by the process in the OS.
|
||||
@ -183,25 +169,15 @@ public:
|
||||
|
||||
static std::string debug_string();
|
||||
|
||||
static bool process_minor_gc();
|
||||
static bool process_full_gc();
|
||||
|
||||
static int64_t tg_disable_overcommit_group_gc();
|
||||
static int64_t tg_enable_overcommit_group_gc(int64_t request_free_memory,
|
||||
RuntimeProfile* profile, bool is_minor_gc);
|
||||
|
||||
// It is only used after the memory limit is exceeded. When multiple threads are waiting for the available memory of the process,
|
||||
// avoid multiple threads starting at the same time and causing OOM.
|
||||
static std::atomic<int64_t> refresh_interval_memory_growth;
|
||||
|
||||
private:
|
||||
friend class GlobalMemoryArbitrator;
|
||||
|
||||
static bool _s_initialized;
|
||||
static std::atomic<int64_t> _s_physical_mem;
|
||||
static std::atomic<int64_t> _s_mem_limit;
|
||||
static std::atomic<int64_t> _s_soft_mem_limit;
|
||||
|
||||
static std::atomic<int64_t> _s_allocator_cache_mem;
|
||||
static std::string _s_allocator_cache_mem_str;
|
||||
static std::atomic<int64_t> _s_virtual_memory_used;
|
||||
|
||||
static int64_t _s_cgroup_mem_limit;
|
||||
|
||||
@ -86,7 +86,7 @@ void Allocator<clear_memory_, mmap_populate, use_mmap>::sys_memory_check(size_t
|
||||
while (wait_milliseconds < doris::config::thread_wait_gc_max_milliseconds) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
if (!doris::GlobalMemoryArbitrator::is_exceed_hard_mem_limit(size)) {
|
||||
doris::MemInfo::refresh_interval_memory_growth += size;
|
||||
doris::GlobalMemoryArbitrator::refresh_interval_memory_growth += size;
|
||||
break;
|
||||
}
|
||||
if (doris::thread_context()->thread_mem_tracker_mgr->is_query_cancelled()) {
|
||||
@ -189,13 +189,9 @@ template <bool clear_memory_, bool mmap_populate, bool use_mmap>
|
||||
void Allocator<clear_memory_, mmap_populate, use_mmap>::throw_bad_alloc(
|
||||
const std::string& err) const {
|
||||
LOG(WARNING) << err
|
||||
<< fmt::format(
|
||||
" os physical memory {}. process memory used {}, sys available memory "
|
||||
"{}, Stacktrace: {}",
|
||||
doris::PrettyPrinter::print(doris::MemInfo::physical_mem(),
|
||||
doris::TUnit::BYTES),
|
||||
doris::PerfCounters::get_vm_rss_str(),
|
||||
doris::MemInfo::sys_mem_available_str(), doris::get_stack_trace());
|
||||
<< fmt::format("{}, Stacktrace: {}",
|
||||
doris::GlobalMemoryArbitrator::process_mem_log_str(),
|
||||
doris::get_stack_trace());
|
||||
doris::MemTrackerLimiter::print_log_process_usage();
|
||||
throw doris::Exception(doris::ErrorCode::MEM_ALLOC_FAILED, err);
|
||||
}
|
||||
|
||||
@ -62,6 +62,7 @@
|
||||
#include "exec/tablet_info.h"
|
||||
#include "runtime/descriptors.h"
|
||||
#include "runtime/exec_env.h"
|
||||
#include "runtime/memory/memory_arbitrator.h"
|
||||
#include "runtime/runtime_state.h"
|
||||
#include "runtime/thread_context.h"
|
||||
#include "service/backend_options.h"
|
||||
@ -554,7 +555,7 @@ Status VNodeChannel::add_block(vectorized::Block* block, const Payload* payload)
|
||||
int VNodeChannel::try_send_and_fetch_status(RuntimeState* state,
|
||||
std::unique_ptr<ThreadPoolToken>& thread_pool_token) {
|
||||
DBUG_EXECUTE_IF("VNodeChannel.try_send_and_fetch_status_full_gc",
|
||||
{ MemInfo::process_full_gc(); });
|
||||
{ MemoryArbitrator::process_full_gc(); });
|
||||
|
||||
if (_cancelled || _send_finished) { // not run
|
||||
return 0;
|
||||
@ -875,7 +876,7 @@ void VNodeChannel::cancel(const std::string& cancel_msg) {
|
||||
}
|
||||
|
||||
Status VNodeChannel::close_wait(RuntimeState* state) {
|
||||
DBUG_EXECUTE_IF("VNodeChannel.close_wait_full_gc", { MemInfo::process_full_gc(); });
|
||||
DBUG_EXECUTE_IF("VNodeChannel.close_wait_full_gc", { MemoryArbitrator::process_full_gc(); });
|
||||
SCOPED_CONSUME_MEM_TRACKER(_node_channel_tracker.get());
|
||||
// set _is_closed to true finally
|
||||
Defer set_closed {[&]() {
|
||||
|
||||
Reference in New Issue
Block a user