Bug #97822 buf_page_get_gen buf_pool->stat.n_page_gets++ is a cpu waste

Submitted: 27 Nov 2019 23:58 Modified: 28 Jan 2020 2:23
Reporter: Daniel Black Email Updates:
Status: Closed Impact on me:
None 
Category:MySQL Server: InnoDB storage engine Severity:S5 (Performance)
Version:5.7.28 OS:Linux (Centos7)
Assigned to: CPU Architecture:Any

[27 Nov 2019 23:58] Daniel Black
Description:
Running TPCCRunner test on a x86_64, 40 core E5-2680, Centos 7 server

pid=$(pidof mysqld) ; perf record -F 10  -o mysql-${name}.perf  -p $pid -- sleep 20 ;   perf record -F 10 -g -o mysql-${name}.g.perf

A perf recording shows that buf_page_get_gen has a high CPU usage. The call stack to it it quick 

(p.s ignoring native_write_msr_safe - seems to be as a consequence of the performance recording)

Samples: 51K of event 'cycles:ppp', Event count (approx.): 4851376331525430918
  Overhead  Command  Shared Object        Symbol                                                                                                                                                                                                   ◆
+  189.04%  mysqld   [kernel.kallsyms]    [k] native_write_msr_safe                                                                                                                                                                                ▒
+   12.60%  mysqld   mysqld               [.] rec_get_offsets_func                                                                                                                                                                                 ▒
+   12.39%  mysqld   mysqld               [.] MYSQLparse                                                                                                                                                                                           ▒
+    5.93%  mysqld   mysqld               [.] page_cur_search_with_match                                                                                                                                                                           ▒
-    4.62%  mysqld   mysqld               [.] buf_page_get_gen                                                                                                                                                                                     +    4.00%  mysqld   mysqld               [.] cmp_dtuple_rec_with_match_low                                                                                                                                                                        ▒
+    3.68%  mysqld   mysqld               [.] btr_cur_search_to_nth_level                       

A disassembly of this profile shows:
       │      Disassembly of section .text:
       │
       │      0000000001096de0 <buf_page_get_gen(page_id_t const&, page_size_t const&, unsigned long, buf_block_t*, unsigned long, char const*, unsigned long, mtr_t*, bool)>:
       │      _Z16buf_page_get_genRK9page_id_tRK11page_size_tmP11buf_block_tmPKcmP5mtr_tb():
       │              ulint                   mode,
       │              const char*             file,
       │              ulint                   line,
       │              mtr_t*                  mtr,
       │              bool                    dirty_with_no_latch)
       │      {
  0.96 │        push   %rbp
       │        mov    %rsp,%rbp
       │        push   %r15
       │        push   %r14
       │        mov    %rcx,%r14
       │        push   %r13
       │        mov    %rdi,%r13
       │        push   %r12
  0.48 │        push   %rbx
       │        sub    $0x1c8,%rsp
       │        mov    0x20(%rbp),%eax
  0.96 │        mov    %rdx,-0x1d8(%rbp)
       │        mov    (%rdi),%edx
       │        mov    %rsi,-0x1d0(%rbp)
       │        mov    %eax,-0x1dc(%rbp)
       │        mov    0x4(%rdi),%eax
       │        mov    %edx,%ecx
       │        mov    %r8,-0x1c0(%rbp)
  0.48 │        shr    $0x6,%eax
       │        shl    $0x14,%ecx
  0.48 │        mov    %r9,-0x1e8(%rbp)
       │        add    %ecx,%eax
  0.48 │        add    %edx,%eax
       │        xor    %edx,%edx
  2.40 │        divq   0xc2e98f(%rip)        # 1cc57c8 <srv_buf_pool_instances>
       │        lea    (%rdx,%rdx,8),%rax
       │        lea    (%rdx,%rax,2),%r15
  0.96 │        shl    $0x6,%r15
  0.48 │        add    buf_pool_ptr,%r15
 18.27 │        addq   $0x1,0x198(%r15)
       │        mov    0x8(%rdi),%rax

The addq $0x1 corresponds to buf_pool->stat.n_page_gets++; at the top of the buf_page_get_gen.

So 18.27% of 4.62% buf_page_get_gen hence just under 1% of cpu time was spend incrementing this counter.

I suspect this is so slow is because even with large number of buffer pool instances, this counter will get hit by all threads and and therefore becoming and in-cpu slow point.

How to repeat:
TPCCRunner test:

$  conf/example/mysql/loader.properties 
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost/tpcc
user=root
password=cutpcctool
threads=32
warehouses=1000

$ cat conf/example/mysql/slaveXX.properties 
name=SLAVE_ID
masterAddress=127.0.0.1
masterPort=27891

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://$remotehost/tpcc
user=tpcc
password=cu
poolSize=12

userCount=10
warehouseCount=CNT_WID
startWarehouseID=START_WID

60 slaves 

Fairly standard config with a large number of innodb_buffer_pool_instances in an attempt to reduce this congestion.

#########################################################
[mysqld]
user=root
datadir = /home/dbtest
socket=/home/dbtest_tmp/mysql.sock
tmpdir=/home/dbtest_tmp
log-error=/home/dbtest_tmp/error.log

# DGB - for testing - quicker startups at expense of slower shutdowns
innodb_fast_shutdown=0

autocommit = 1
back_log = 128
binlog_cache_size = 1M
binlog_format = ROW
default_storage_engine=InnoDB
innodb_data_file_path = ibdata1:1000M:autoextend
innodb_log_buffer_size=16M
innodb_log_file_size=4096M
innodb_log_files_in_group = 5
slow_query_log = 1
sync_binlog = 1
transaction_isolation = READ-COMMITTED

innodb_file_per_table = true
lower_case_table_names = 1
skip-external-locking
low_priority_updates=1
skip-name-resolve
memlock
net_read_timeout=3600
net_write_timeout=3600
max_connections = 3000
key_buffer_size = 16384M
join_buffer_size = 4096M
sort_buffer_size = 4096M
read_buffer_size=2147479552
read_rnd_buffer_size=2147483647
table_open_cache = 4096
table_open_cache_instances=64
innodb_open_files=1000
innodb_flush_method = O_DIRECT ## O_DIRECT_NO_FSYNC
innodb_flush_log_at_trx_commit = 2
innodb_buffer_pool_size = 128G
innodb_buffer_pool_instances = 64
innodb_checksum_algorithm=none
innodb_doublewrite=0
innodb_thread_concurrency=0
innodb_thread_concurrency = 0
innodb_adaptive_flushing = 1
innodb_max_dirty_pages_pct=50
innodb_use_native_aio= 1
innodb_purge_threads = 4
innodb_read_io_threads = 64
innodb_write_io_threads = 16
innodb_io_capacity = 20000
innodb_lru_scan_depth=20000
innodb-numa-interleave=0
innodb_adaptive_hash_index=0
innodb-adaptive-hash-index-parts=64
innodb_monitor_enable='%';
performance_schema=OFF
innodb_max_dirty_pages_pct=90
#general_log=on

[mysqld-5.7]
query_cache_size = 0
query_cache_type = 0
read_only = 0

Suggested fix:
buf_pool->stat.n_page_gets gets used in the information schema, SHOW ENGINE INNODB STATUS, and in the global status innodb_buffer_pool_read_requests.

I'm not sure a DBA could make an decision based on this statistic.

I recommend removing it however maybe that isn't acceptable in 5.7 (or 8.0?).

Note - alternatives below only loosely considered.

alternative:

Move the increment to below some of the lock acquisitions as a few lines later. The loop seems to be just for a page-in.

alternate 2:

move increment to before the return(NULL) (mode == BUF_GET_IF_IN_POOL
                    || mode == BUF_PEEK_IF_IN_POOL
                    || mode == BUF_GET_IF_IN_POOL_OR_WATCH)

and copy to after got_block:

The rational for moving it after the locks is the other stats in adjacent memory locations seem to be updated while holding a lock so this could cause less conflicts.
[28 Nov 2019 13:13] MySQL Verification Team
Hello Mr. Black,

Thank you for your performance improvement suggestion.

I like it a lot.

Verified as reported.
[23 Jan 2020 21:57] Daniel Price
Posted by developer:
 
Fixed as of the upcoming 8.0.20 release, and here's the changelog entry:

An internal buffer pool statistics counter (n_page_gets) was partitioned
by page number to avoid contention when accessed by multiple threads.
[24 Jan 2020 13:07] MySQL Verification Team
Thank you, Daniel.
[28 Jan 2020 2:23] Daniel Black
Thank you Daniel P

Portions of this website are copyright © 2001, 2002 The PHP Group

Page generated in 0.042 sec. using MySQL 8.4.7-u6-cloud

Timestamp references displayed by the system are UTC.
Content reproduced on this site is the property of the respective copyright holders.
It is not reviewed in advance by Oracle and does not necessarily represent the opinion of Oracle or any other party.