@@ -98,7 +98,8 @@ bool MemTableMemoryLimiter::_soft_limit_reached() {
9898}
9999
100100bool MemTableMemoryLimiter::_hard_limit_reached () {
101- return _mem_tracker->consumption () > _load_hard_mem_limit ||
101+ // Include reserved memory in the check to ensure strict limit enforcement
102+ return (_mem_tracker->consumption () + _reserved_mem_usage) > _load_hard_mem_limit ||
102103 _sys_avail_mem_less_than_warning_water_mark () > 0 ||
103104 _process_used_mem_more_than_soft_mem_limit () > 0 ;
104105}
@@ -114,30 +115,39 @@ int64_t MemTableMemoryLimiter::_need_flush() {
114115 return 1 ;
115116 }
116117 });
117- int64_t limit1 = _mem_tracker->consumption () - _load_soft_mem_limit;
118+ // Include reserved memory in the calculation
119+ int64_t total_consumption = _mem_tracker->consumption () + _reserved_mem_usage;
120+ int64_t limit1 = total_consumption - _load_soft_mem_limit;
118121 int64_t limit2 = _sys_avail_mem_less_than_warning_water_mark ();
119122 int64_t limit3 = _process_used_mem_more_than_soft_mem_limit ();
120123 int64_t need_flush = std::max ({limit1, limit2, limit3});
121124 return need_flush - _queue_mem_usage - _flush_mem_usage;
122125}
123126
124- void MemTableMemoryLimiter::handle_memtable_flush (std::function<bool ()> cancel_check) {
127+ void MemTableMemoryLimiter::handle_memtable_flush (std::function<bool ()> cancel_check,
128+ int64_t estimated_mem_size) {
125129 // Check the soft limit.
126130 DCHECK (_load_soft_mem_limit > 0 );
127- do {
128- DBUG_EXECUTE_IF (" MemTableMemoryLimiter.handle_memtable_flush.limit_reached" , {
129- LOG (INFO) << " debug memtable limit reached" ;
130- break ;
131- });
132- if (!_soft_limit_reached () || _load_usage_low ()) {
133- return ;
134- }
135- } while (false );
131+
132+ // Fast path: if no reservation needed and under soft limit, return immediately
133+ if (estimated_mem_size <= 0 ) {
134+ do {
135+ DBUG_EXECUTE_IF (" MemTableMemoryLimiter.handle_memtable_flush.limit_reached" , {
136+ LOG (INFO) << " debug memtable limit reached" ;
137+ break ;
138+ });
139+ if (!_soft_limit_reached () || _load_usage_low ()) {
140+ return ;
141+ }
142+ } while (false );
143+ }
144+
136145 MonotonicStopWatch timer;
137146 timer.start ();
138147 std::unique_lock<std::mutex> l (_lock);
139148 g_memtable_memory_limit_waiting_threads << 1 ;
140149 bool first = true ;
150+
141151 do {
142152 if (!first) {
143153 auto st = _hard_limit_end_cond.wait_for (l, std::chrono::milliseconds (1000 ));
@@ -151,13 +161,38 @@ void MemTableMemoryLimiter::handle_memtable_flush(std::function<bool()> cancel_c
151161 return ;
152162 }
153163 first = false ;
164+
165+ // Check if we can reserve the requested memory
166+ bool can_reserve = false ;
167+ if (estimated_mem_size > 0 ) {
168+ // Try to reserve memory. This ensures we don't exceed hard limit even with
169+ // multiple concurrent writes.
170+ int64_t total_after_reserve = _mem_tracker->consumption () + _reserved_mem_usage +
171+ estimated_mem_size;
172+ // Also check process hard limit, not just soft limit to prevent allocation failures
173+ int64_t process_hard_limit_exceeded = GlobalMemoryArbitrator::process_memory_usage () +
174+ estimated_mem_size -
175+ MemInfo::mem_limit ();
176+ can_reserve = (total_after_reserve <= _load_hard_mem_limit &&
177+ _sys_avail_mem_less_than_warning_water_mark () <= 0 &&
178+ _process_used_mem_more_than_soft_mem_limit () <= 0 &&
179+ process_hard_limit_exceeded <= 0 );
180+
181+ if (can_reserve) {
182+ // Successfully reserved, we can proceed
183+ _reserved_mem_usage += estimated_mem_size;
184+ break ;
185+ }
186+ }
187+
154188 int64_t need_flush = _need_flush ();
155- if (need_flush > 0 ) {
189+ if (need_flush > 0 || (estimated_mem_size > 0 && !can_reserve) ) {
156190 auto limit = _hard_limit_reached () ? Limit::HARD : Limit::SOFT;
157191 LOG (INFO) << " reached memtable memory " << (limit == Limit::HARD ? " hard" : " soft" )
158192 << " , " << GlobalMemoryArbitrator::process_memory_used_details_str () << " , "
159193 << GlobalMemoryArbitrator::sys_mem_available_details_str ()
160194 << " , load mem: " << PrettyPrinter::print_bytes (_mem_tracker->consumption ())
195+ << " , reserved: " << PrettyPrinter::print_bytes (_reserved_mem_usage)
161196 << " , memtable writers num: " << _writers.size ()
162197 << " , active: " << PrettyPrinter::print_bytes (_active_mem_usage)
163198 << " , queue: " << PrettyPrinter::print_bytes (_queue_mem_usage)
@@ -176,9 +211,11 @@ void MemTableMemoryLimiter::handle_memtable_flush(std::function<bool()> cancel_c
176211 } else {
177212 // will not reach here
178213 }
179- _flush_active_memtables (need_flush);
214+ int64_t flush_size = std::max (need_flush, estimated_mem_size);
215+ _flush_active_memtables (flush_size);
180216 }
181- } while (_hard_limit_reached () && !_load_usage_low ());
217+ } while ((_hard_limit_reached () || (estimated_mem_size > 0 )) && !_load_usage_low ());
218+
182219 g_memtable_memory_limit_waiting_threads << -1 ;
183220 timer.stop ();
184221 int64_t time_ms = timer.elapsed_time () / 1000 / 1000 ;
@@ -189,6 +226,7 @@ void MemTableMemoryLimiter::handle_memtable_flush(std::function<bool()> cancel_c
189226 << " , " << GlobalMemoryArbitrator::process_memory_used_details_str () << " , "
190227 << GlobalMemoryArbitrator::sys_mem_available_details_str ()
191228 << " , load mem: " << PrettyPrinter::print_bytes (_mem_tracker->consumption ())
229+ << " , reserved: " << PrettyPrinter::print_bytes (_reserved_mem_usage)
192230 << " , memtable writers num: " << _writers.size ()
193231 << " , active: " << PrettyPrinter::print_bytes (_active_mem_usage)
194232 << " , queue: " << PrettyPrinter::print_bytes (_queue_mem_usage)
@@ -249,6 +287,36 @@ int64_t MemTableMemoryLimiter::_flush_active_memtables(int64_t need_flush) {
249287 return mem_flushed;
250288}
251289
290+ bool MemTableMemoryLimiter::reserve_memory (int64_t size) {
291+ if (size <= 0 ) {
292+ return true ;
293+ }
294+ int64_t total_after_reserve = _mem_tracker->consumption () + _reserved_mem_usage + size;
295+ if (total_after_reserve > _load_hard_mem_limit ||
296+ _sys_avail_mem_less_than_warning_water_mark () > 0 ||
297+ _process_used_mem_more_than_soft_mem_limit () > 0 ) {
298+ return false ;
299+ }
300+ _reserved_mem_usage += size;
301+ return true ;
302+ }
303+
304+ void MemTableMemoryLimiter::release_memory (int64_t size) {
305+ if (size <= 0 ) {
306+ return ;
307+ }
308+ _reserved_mem_usage -= size;
309+ if (_reserved_mem_usage < 0 ) {
310+ LOG (WARNING) << " reserved memory usage is negative: " << _reserved_mem_usage
311+ << " , reset to 0" ;
312+ _reserved_mem_usage = 0 ;
313+ }
314+ // Notify waiting threads that memory has been released
315+ if (!_hard_limit_reached ()) {
316+ _hard_limit_end_cond.notify_all ();
317+ }
318+ }
319+
252320void MemTableMemoryLimiter::refresh_mem_tracker () {
253321 std::lock_guard<std::mutex> l (_lock);
254322 _refresh_mem_tracker ();
@@ -271,6 +339,7 @@ void MemTableMemoryLimiter::refresh_mem_tracker() {
271339 _log_timer.reset ();
272340 LOG (INFO) << ss.str ()
273341 << " , load mem: " << PrettyPrinter::print_bytes (_mem_tracker->consumption ())
342+ << " , reserved: " << PrettyPrinter::print_bytes (_reserved_mem_usage)
274343 << " , memtable writers num: " << _writers.size ()
275344 << " , active: " << PrettyPrinter::print_bytes (_active_mem_usage)
276345 << " , queue: " << PrettyPrinter::print_bytes (_queue_mem_usage)
0 commit comments