| Bug #95390 | wake up log_closer background thread in log_wait_for_space_in_log_recent_closed | ||
|---|---|---|---|
| Submitted: | 16 May 2019 9:57 | Modified: | 16 May 2019 13:20 |
| Reporter: | Fungo Wang (OCA) | Email Updates: | |
| Status: | Verified | Impact on me: | |
| Category: | MySQL Server: InnoDB storage engine | Severity: | S5 (Performance) |
| Version: | 8.0.16 | OS: | Any |
| Assigned to: | CPU Architecture: | Any | |
[16 May 2019 12:17]
MySQL Verification Team
HI, Thank you for your bug report, dealing with the performance of the InnoDB SE. I have analysed thoroughly and based on the code analysis alone, I must admit that I agree with you. Verified as reported.
[16 May 2019 13:20]
Fungo Wang
Hi Sinisa,
Thanks for the verification.
After show this perf case to my colleague Weixiang, he suggest to add a check for whether log.closer_event is already set by others, instead of blindly waking up every time, to reduce the system spin lock cause by mutex.
diff --git a/storage/innobase/log/log0buf.cc b/storage/innobase/log/log0buf.cc
index 371596f3055..26b5c4fd85c 100644
--- a/storage/innobase/log/log0buf.cc
+++ b/storage/innobase/log/log0buf.cc
@@ -938,6 +938,9 @@ void log_wait_for_space_in_log_recent_closed(log_t &log, lsn_t lsn) {
uint64_t wait_loops = 0;
while (!log.recent_closed.has_space(lsn)) {
+ if (!os_event_is_set(log.closer_event)) {
+ os_event_set(log.closer_event);
+ }
++wait_loops;
os_thread_sleep(20);
}
The perf result exactly shows spin_lock is reduced, and log_wait_for_space_in_log_recent_closed has gone away from the trace.
With patch 1
=========
+ 41.56% mysqld mysqld [.] ut_delay(unsigned long)
- 3.57% mysqld [kernel.kallsyms] [k] _spin_lock
- _spin_lock
- 43.79% futex_wait_setup
futex_wait
do_futex
sys_futex
- system_call_fastpath
- 88.38% __lll_lock_wait
+ 44.06% Stage_manager::enroll_for(Stage_manager::StageID, THD*, mysql_mutex_t*)
+ 34.57% Gtid_state::update_gtids_impl(THD*, bool)
+ 6.26% log_wait_for_space_in_log_recent_closed(log_t&, unsigned long)
+ 6.03% MYSQL_BIN_LOG::change_stage(THD*, Stage_manager::StageID, THD*, mysql_mutex_t*, mysql_mutex_t*)
+ 3.85% sync_array_reserve_cell(sync_array_t*, void*, unsigned long, char const*, unsigned long)
+ 2.54% MYSQL_BIN_LOG::ordered_commit(THD*, bool, bool)
+ 1.15% MYSQL_BIN_LOG::assign_automatic_gtids_to_flush_group(THD*)
+ 0.62% memo_slot_release(mtr_memo_slot_t*)
+ 0.51% sync_array_wait_event(sync_array_t*, sync_cell_t*&)
+ 11.05% pthread_cond_wait@@GLIBC_2.3.2
+ 0.57% pthread_cond_timedwait@@GLIBC_2.3.2
+ 29.37% futex_wake
+ 10.26% task_rq_lock
+ 4.68% try_to_wake_up
+ 3.15% double_lock_balance
+ 2.38% futex_requeue
+ 1.54% start_this_handle
+ 1.29% thread_return
+ 0.56% mod_timer
With patch 2
=========
+ 42.96% mysqld mysqld [.] ut_delay(unsigned long)
- 3.25% mysqld [kernel.kallsyms] [k] _spin_lock
- _spin_lock
- 38.74% futex_wait_setup
futex_wait
do_futex
sys_futex
- system_call_fastpath
- 64.95% __lll_lock_wait
+ 42.58% Gtid_state::update_gtids_impl(THD*, bool)
+ 36.96% Stage_manager::enroll_for(Stage_manager::StageID, THD*, mysql_mutex_t*)
+ 7.68% MYSQL_BIN_LOG::change_stage(THD*, Stage_manager::StageID, THD*, mysql_mutex_t*, mysql_mutex_t*)
+ 6.66% MYSQL_BIN_LOG::ordered_commit(THD*, bool, bool)
+ 2.37% memo_slot_release(mtr_memo_slot_t*)
+ 1.56% sync_array_wait_event(sync_array_t*, sync_cell_t*&)
+ 1.10% MYSQL_BIN_LOG::assign_automatic_gtids_to_flush_group(THD*)
+ 34.36% pthread_cond_wait@@GLIBC_2.3.2
+ 0.69% pthread_cond_timedwait@@GLIBC_2.3.2
+ 32.44% futex_wake
+ 11.13% task_rq_lock
+ 5.29% try_to_wake_up
+ 2.87% double_lock_balance
+ 2.86% futex_requeue
+ 1.53% start_this_handle
+ 1.36% thread_return
+ 0.74% mod_timer
[16 May 2019 13:25]
MySQL Verification Team
Thank you for the additional code changes.

Description: In the new redo log design in 8.0, there are several background threads for different purpose. log_closer thread is one of them and used to advance dpa_lsn (log_buffer_dirty_pages_added_up_to_lsn()) in log.recent_closed link buffer. log_closer thread wait on log.closer_event event if there is nothing to advance, but no body wake up this event. mtr_commit() works as below: mtr_t::Command::execute() - log_buffer_reserve - m_impl->m_log.for_each_block(write_log); - log_wait_for_space_in_log_recent_closed() - add_dirty_blocks_to_flush_list() - log_buffer_close() Before mtr_commit() add dirty pages to flush list, it need to make sure there is enough space in recent_closed link buffer by calling log_wait_for_space_in_log_recent_closed(), it will wait until satisfied. void log_wait_for_space_in_log_recent_closed(log_t &log, lsn_t lsn) { ut_a(log_lsn_validate(lsn)); ut_ad(lsn >= log_buffer_dirty_pages_added_up_to_lsn(log)); uint64_t wait_loops = 0; while (!log.recent_closed.has_space(lsn)) { ++wait_loops; os_thread_sleep(20); } if (unlikely(wait_loops != 0)) { MONITOR_INC_VALUE(MONITOR_LOG_ON_RECENT_CLOSED_WAIT_LOOPS, wait_loops); } } When mtr_commit is waiting in log_wait_for_space_in_log_recent_closed(), the log_closer thread could also be in waiting state, until timeout is reached. So I think it's better to proactively wake up log_closer in log_wait_for_space_in_log_recent_closed(), and get performance improvement. How to repeat: I did a simple performance test, and observed improvement after waking up. Wake up by using bellow change: diff --git a/storage/innobase/log/log0buf.cc b/storage/innobase/log/log0buf.cc index 371596f3055..e1935e97b82 100644 --- a/storage/innobase/log/log0buf.cc +++ b/storage/innobase/log/log0buf.cc @@ -938,6 +938,7 @@ void log_wait_for_space_in_log_recent_closed(log_t &log, lsn_t lsn) { uint64_t wait_loops = 0; while (!log.recent_closed.has_space(lsn)) { + os_event_set(log.closer_event); ++wait_loops; os_thread_sleep(20); } The performance test is did using a relative small innodb_log_recent_closed_size (16384) configuration. To be able to change this sysvar, ENABLE_EXPERIMENT_SYSVARS should be enabled when buidling mysqld from source. I run with sysbench 1.0 using these parameters: ./src/sysbench oltp_insert --threads=64 --report-interval=1 --time=300 --tables=8 --table_size=200000 run and mysqld with: innodb_log_recent_closed_size = 16384 During performance test, enable recent_closed_wait innodb metric by `set global innodb_monitor_enable = 'log_on_recent_closed_wait_loops';` and use `SELECT * FROM INFORMATION_SCHEMA.INNODB_METRICS where name = 'log_on_recent_closed_wait_loops'\G` to see the counter status. Orginal =========== *************************** 1. row mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_METRICS where name = 'log_on_recent_closed_wait_loops'\G *************************** 1. row *************************** NAME: log_on_recent_closed_wait_loops SUBSYSTEM: log COUNT: 6473910 MAX_COUNT: 6473910 MIN_COUNT: NULL AVG_COUNT: 107898.5 COUNT_RESET: 6473910 MAX_COUNT_RESET: 6473910 MIN_COUNT_RESET: NULL AVG_COUNT_RESET: NULL TIME_ENABLED: 2019-05-16 17:05:06 TIME_DISABLED: NULL TIME_ELAPSED: 60 TIME_RESET: NULL STATUS: enabled TYPE: counter COMMENT: Loops of waits in user threads on space in log.recent_closed 1 row in set (0.00 sec) sysbench output [ 102s ] thds: 64 tps: 25380.71 qps: 25380.71 (r/w/o: 0.00/25380.71/0.00) lat (ms,95%): 4.33 err/s: 0.00 reconn/s: 0.00 [ 103s ] thds: 64 tps: 25441.14 qps: 25441.14 (r/w/o: 0.00/25441.14/0.00) lat (ms,95%): 4.33 err/s: 0.00 reconn/s: 0.00 [ 104s ] thds: 64 tps: 25466.92 qps: 25466.92 (r/w/o: 0.00/25466.92/0.00) lat (ms,95%): 4.33 err/s: 0.00 reconn/s: 0.00 [ 105s ] thds: 64 tps: 25175.02 qps: 25176.02 (r/w/o: 0.00/25176.02/0.00) lat (ms,95%): 4.41 err/s: 0.00 reconn/s: 0.00 [ 106s ] thds: 64 tps: 25465.28 qps: 25464.28 (r/w/o: 0.00/25464.28/0.00) lat (ms,95%): 4.33 err/s: 0.00 reconn/s: 0.00 [ 107s ] thds: 64 tps: 25503.30 qps: 25503.30 (r/w/o: 0.00/25503.30/0.00) lat (ms,95%): 4.33 err/s: 0.00 reconn/s: 0.00 [ 108s ] thds: 64 tps: 25373.74 qps: 25373.74 (r/w/o: 0.00/25373.74/0.00) lat (ms,95%): 4.33 err/s: 0.00 reconn/s: 0.00 [ 109s ] thds: 64 tps: 25211.54 qps: 25211.54 (r/w/o: 0.00/25211.54/0.00) lat (ms,95%): 4.33 err/s: 0.00 reconn/s: 0.00 After wake up ============= mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_METRICS where name = 'log_on_recent_closed_wait_loops'\G *************************** 1. row *************************** NAME: log_on_recent_closed_wait_loops SUBSYSTEM: log COUNT: 1866566 MAX_COUNT: 1866566 MIN_COUNT: NULL AVG_COUNT: 31109.433333333334 COUNT_RESET: 1866566 MAX_COUNT_RESET: 1866566 MIN_COUNT_RESET: NULL AVG_COUNT_RESET: NULL TIME_ENABLED: 2019-05-16 17:16:09 TIME_DISABLED: NULL TIME_ELAPSED: 60 TIME_RESET: NULL STATUS: enabled TYPE: counter COMMENT: Loops of waits in user threads on space in log.recent_closed 1 row in set (0.00 sec) sysbench output [ 117s ] thds: 64 tps: 51308.19 qps: 51308.19 (r/w/o: 0.00/51308.19/0.00) lat (ms,95%): 2.48 err/s: 0.00 reconn/s: 0.00 [ 118s ] thds: 64 tps: 49021.06 qps: 49021.06 (r/w/o: 0.00/49021.06/0.00) lat (ms,95%): 2.52 err/s: 0.00 reconn/s: 0.00 [ 119s ] thds: 64 tps: 47968.44 qps: 47968.44 (r/w/o: 0.00/47968.44/0.00) lat (ms,95%): 2.61 err/s: 0.00 reconn/s: 0.00 [ 120s ] thds: 64 tps: 48050.19 qps: 48050.19 (r/w/o: 0.00/48050.19/0.00) lat (ms,95%): 2.61 err/s: 0.00 reconn/s: 0.00 [ 121s ] thds: 64 tps: 48635.23 qps: 48635.23 (r/w/o: 0.00/48635.23/0.00) lat (ms,95%): 2.52 err/s: 0.00 reconn/s: 0.00 [ 122s ] thds: 64 tps: 49118.43 qps: 49118.43 (r/w/o: 0.00/49118.43/0.00) lat (ms,95%): 2.52 err/s: 0.00 reconn/s: 0.00 [ 123s ] thds: 64 tps: 48318.13 qps: 48318.13 (r/w/o: 0.00/48318.13/0.00) lat (ms,95%): 2.57 err/s: 0.00 reconn/s: 0.00 [ 124s ] thds: 64 tps: 47472.79 qps: 47472.79 (r/w/o: 0.00/47472.79/0.00) lat (ms,95%): 2.81 err/s: 0.00 reconn/s: 0.00 We can see the wait counter is reduced and TPS is improved (almost doubled). The reason to use a small innodb_log_recent_closed_size (16384), is the default value 2M is big enough, and I didn't observe any log_on_recent_closed_wait_loops in simple test. Suggested fix: Wake up log closer in log_wait_for_space_in_log_recent_closed() diff --git a/storage/innobase/log/log0buf.cc b/storage/innobase/log/log0buf.cc index 371596f3055..e1935e97b82 100644 --- a/storage/innobase/log/log0buf.cc +++ b/storage/innobase/log/log0buf.cc @@ -938,6 +938,7 @@ void log_wait_for_space_in_log_recent_closed(log_t &log, lsn_t lsn) { uint64_t wait_loops = 0; while (!log.recent_closed.has_space(lsn)) { + os_event_set(log.closer_event); ++wait_loops; os_thread_sleep(20); }