MySQLのサポート業務では、(ユーザー視点では)突然 Signal 6
や Signal 11
をmysqldが受け取ってダウンするという問い合わせを頂く事が度々あります。
MySQLは数多くのバグフィックスや改善によって、頻繁にダウンすることはありません。
ちなみにGithub上のここ10年のCOMMIT数を数えてみると、77649 commit
されていました。
1 2 |
# git shortlog -nse --since="10 year ago" --all | awk 'BEGIN{sum=0}{sum=sum+$1}END{print sum}' 77649 |
それでも進化を続けているソフトウェアである以上、新しいプラグインや、新機能の影響、デグレ、またはハードウェア起因の問題によって、ダウンが発生することはあります。
今回は、ダウンが発生した際の原因調査に役立つコアファイルの出力方法についてご紹介します。
コアファイルとは
コアファイルとは、MANページに記載の通り、プロセスが保持していた仮想メモリ空間の情報をファイルに出力したものです。
https://man7.org/linux/man-pages/man5/core.5.html
The default action of certain signals is to cause a process to
terminate and produce a core dump file, a file containing an
image of the process’s memory at the time of termination. This
image can be used in a debugger (e.g., gdb(1)) to inspect the
state of the program at the time that it terminated. A list of
the signals which cause a process to dump core can be found in
signal(7).
ファイルサイズは、ps aux
で確認した際の、VSZ の値以下になります(core_filterなどの影響により変動)。
試しに出力してみたところ、VSZは 1,778,140(KB)
でしたが、コアファイルは 1,685,248(KB)
でした。
1 2 3 4 5 6 7 |
# ps --format=pid,vsz,rss,command -C mysqld PID VSZ RSS COMMAND 2279 1778140 421916 /usr/sbin/mysqld # pkill -SEGV mysqld # lz4 -d /var/lib/systemd/coredump/core.mysqld.27.b241f98db05442c99bcd9931534dc2b8.2279.1662451258000000.lz4 Decoding file /var/lib/systemd/coredump/core.mysqld.27.b241f98db05442c99bcd9931534dc2b8.2279.1662451258000000 /var/lib/systemd/cor : decoded 1725693952 bytes |
コアファイルには、そのプログラムがシグナルを受けてダウンした瞬間のメモリ上の情報が含まれています。
つまり、mysqldのソースコードレベルで何の変数にどのような値を設定し、どのような関数を通ってダウンに至ったかといった情報をあとから確認することができます。
コアファイルの情報をもとに、C++のソースコードをデバッグするのは容易なことではありません。
しかし、何の機能に関連した問題だったのかということだけでもわかれば、回避策がわかるかもしれませんし、サポートへの調査依頼もスムーズになることでしょう。
MySQLでのコアファイルの出力準備
今回の環境は、CentOS 8 Streamを使用しています。
RHEL8系でのOSパラメータの調整
RHEL8系より、kernel.core_pettern=|/usr/lib/systemd/systemd-coredump %P %u %g %s %t %c %h %e
が設定されており、
コアファイルはデフォルトで /var/lib/systemd/coredump
に出力されます。
また、デフォルトではコアファイルの出力サイズのソフトリミットが0となっているため、rootで起動しているアプリケーション以外ではコアファイルは出力できません。
今回はDefaultLimitCORE=infinity
を設定し、コアファイルのサイズ上限を無限としています。
値は、=<soft>:<hard>
の形式で指定ができます。
システム全体への影響を鑑みるとファイルサイズ制限をかけたくなりますが、中途半端に出力されたコアファイルは調査の役に立たないため、基本的にはinfinityとして、十分なサイズのある出力先を kernel.core_pattern
に指定するというのがよいでしょう。
1 2 |
# sed -i 's/^DefaultLimitCORE.*$/DefaultLimitCORE=infinity/g' /etc/systemd/system.conf # systemctl daemon-reexec |
Linuxではsetuid()
で起動ユーザを変えているアプリケーション(まさにmysqldではデフォルトでmysqlユーザをsetuidしています)からデフォルトでコアダンプできないようになっていますので、fs.suid_dumpable
に 1
or 2
を指定します。
1
は単純にすべてのアプリケーションにコアダンプを許可するモードであり、2
はsetuid()
をしているアプリケーションのコアファイルのオーナーをrootにしてセキュアにするという設定です。
運用次第ですが、2
を設定するのが望ましいでしょう。
1 2 |
# echo fs.suid_dumpable=2 >> /etc/sysctl.conf # sysctl -p |
また、デフォルトではコアダンプに含まれる情報はフィルタされています。
1 2 |
# cat /proc/$(pgrep -x mysqld)/coredump_filter 00000033 |
ファイルサイズが増えますが、全ての情報を出力する設定にしたほうが後々有用な情報とはなります。
1 2 |
# すべてのフラグを立てて、全情報をダンプする # echo 0xF > /proc/$(pgrep -x mysqld)/coredump_filter |
以上でOS側でコアファイルを出力する準備が整いました。
MySQLがコアファイルを出力するための準備
コアファイルの出力はmysqldではデフォルトでは無効です。
my.cnf
に core_file
パラメータを追加します。
1 2 |
[mysqld] core_file |
mysqld_safeを使用している環境では以下も設定する必要があります。
1 2 |
[mysqld_safe] core-file-size = unlimited |
また、別の環境にコアファイルを移してデバッグするというようなケースでは、コアファイルを出力したアプリケーションと、そのアプリケーションが利用していたすべてのライブラリ、及びデバッグシンボルも合わせて移す必要があります。
Percona社がそれらを簡単に収集するためのスクリプトを公開しておりますので、これを準備しておくのがよいでしょう。
使用方法は後述します。
最後に、RedHat系の一般的なソフトウェアバイナリでは開発時に設定していたデバッグ情報などを削除して軽量化したものを配布する事が多くあります。
※ なおel7
用の /usr/sbin/mysqld
はデバッグ情報が埋め込まれたものが配布されています
例えば以下は、MySQLのデバッグシンボルパッケージをインストールしていない状態で取得した情報です。
MySQLには最低限のデバッグ情報は含まれており、関数名はわかるのである程度あたりがつきますが、処理までは確認することができません。
1 2 3 4 5 |
#0 0x00007f4499598848 in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0 #1 0x0000000002240eaa in os_event::timed_wait(timespec const*) () #2 0x0000000002241c29 in os_event::wait_time_low(std::chrono::duration<long, std::ratio<1l, 1000000l> >, long) () #3 0x0000000002222a67 in log_writer(log_t*) () : |
以下はデバッグシンボルパッケージをインストールした状態で取得したものです。
コードや行まで特定が可能になっています。
より具体的にバグを特定するにはこのような情報が必須になります。
1 2 3 4 5 6 7 |
#0 0x00007f132e482848 in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0 #1 0x0000000002240eaa in os_event::timed_wait (this=<optimized out>, abstime=0x7f12f1ffabb0) at /usr/src/debug/mysql-community-8.0.30-1.el8.x86_64/mysql-8.0.30/storage/innobase/include/sync0types.h:549 #2 0x0000000002241c29 in os_event::wait_time_low (this=0x7f131801f030, timeout=..., reset_sig_count=reset_sig_count@entry=16) at /usr/src/debug/mysql-community-8.0.30-1.el8.x86_64/mysql-8.0.30/storage/innobase/os/os0event.cc:487 #3 0x0000000002241d09 in os_event_wait_time_low (event=<optimized out>, timeout=..., timeout@entry=..., reset_sig_count=reset_sig_count@entry=16) at /usr/src/debug/mysql-community-8.0.30-1.el8.x86_64/mysql-8.0.30/storage/innobase/os/os0event.cc:574 : |
デバッグシンボルパッケージは、以下のようにインストールします。
1 2 |
# dnf install mysql-community-server-debuginfo # dnf --enablerepo='*debug*' install /usr/lib/debug/usr/sbin/mysqld-8.0.30-1.el8.x86_64.debug |
コアファイル出力~解析用環境でコアファイルを読む
まずは、ともかくコアファイルを出力してみます。
デフォルトの出力先は、/var/lib/systemd/coredump
です。
1 2 3 4 |
# pkill -SEGV mysqld # ls -lt /var/lib/systemd/coredump | head -2 total 348532 -rw-r----- 1 root root 10996965 Sep 7 09:04 core.mysqld.27.b241f98db05442c99bcd9931534dc2b8.2513.1662541463000000.lz4 |
systemd-coredumpを使用している場合、lz4圧縮がかかるのでファイルサイズが10M程度で済んでいます。
とは言えGDBでこれを解析するには解凍が必要となりますが、 coredumpctl gdb
コマンドを使用すると、そのままgdbでアタッチできたりします(とても便利!)
ですので、RHEL8系では必要以上にコアファイルサイズを恐れる必要が無くなっているようですね。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# coredumpctl list TIME PID UID GID SIG COREFILE EXE Tue 2022-09-06 08:01:05 UTC 2279 27 27 11 present /usr/sbin/mysqld Wed 2022-09-07 09:04:27 UTC 2513 27 27 11 present /usr/sbin/mysqld # coredumpctl gdb 2513 # PIDを指定 PID: 2513 (mysqld) UID: 27 (mysql) GID: 27 (mysql) Signal: 11 (SEGV) Timestamp: Wed 2022-09-07 09:04:23 UTC (6min ago) Command Line: /usr/sbin/mysqld Executable: /usr/sbin/mysqld Control Group: /system.slice/mysqld.service Unit: mysqld.service Slice: system.slice Boot ID: b241f98db05442c99bcd9931534dc2b8 Machine ID: c179a64bfdcd4e55a3b06b653c410e16 Hostname: centos8s.localdomain Storage: /var/lib/systemd/coredump/core.mysqld.27.b241f98db05442c99bcd9931534dc2b8.2513.1662541463000000.lz4 Message: Process 2513 (mysqld) of user 27 dumped core. : : (gdb) |
多くの情報とともに(gdb)
のプロンプトが表示されます。
一旦quit
して、必要なファイルを集めましょう。
1 2 3 4 5 6 7 8 9 10 11 12 |
# wget https://raw.githubusercontent.com/Percona-QA/percona-qa/master/ldd_files.sh # chmod +x ldd_files.sh # mkdir core-analysis # cd core-analysis # cp /usr/sbin/mysqld . # ../ldd_files.sh mysqld cp: cannot stat ‘./mysqld: /lib64/libssl.so.1.0.0: no version information available’: No such file or directory # 無視可能 cp: cannot stat ‘./mysqld: /lib64/libcrypto.so.1.0.0: no version information available’: No such file or directory # 無視可能 # coredumpctl dump -o core <PID> # cp /usr/lib/debug/usr/sbin/mysqld-8.0.30-1.el8.x86_64.debug . # tar czf core-analysis.tar.gz . # scp core-analysis.tar.gz [remote env]:/tmp |
[remote env] で指定した解析環境へログインし、ファイルを解凍すればOKです。
1 2 3 |
# mkdir work; cd work # mv /tmp/core-analysis.tar.gz . # tar xf core-analysis.tar.gz |
GDBでアタッチする
GDBでコアファイルにアタッチしてみましょう。
solib-search-path
には収集したライブラリが格納されているディレクトリを指定します。
1 2 3 |
# gdb -ex "set solib-search-path /root/work" /root/work/mysqld /root/work/core : (gdb) |
GDBには非常に多くのコマンドがありケースバイケースで使い分ける必要がありますが、mysqldが急に停止したという場合にまず確認したいのは、
停止時点で各スレッドがどの関数を実行していたかです。
info threads
コマンドでスレッドの一覧と最後に実行していた関数名を確認することができます。
OSレベルから見たスレッドの一覧になりますので、親切にこれはInnoDBのスレッドですよ、というように明記されていません。
1 2 3 4 5 6 7 8 9 10 11 |
(gdb) info threads Id Target Id Frame * 1 Thread 0x7f4499924880 (LWP 10744) 0x00007f4499599a35 in pthread_kill () from /lib64/libpthread.so.0 2 Thread 0x7f4483fff700 (LWP 10748) 0x00007f44979369bd in syscall () from /lib64/libc.so.6 3 Thread 0x7f44837fe700 (LWP 10749) 0x00007f44979369bd in syscall () from /lib64/libc.so.6 : 44 Thread 0x7f4488207700 (LWP 10788) 0x00007f449959845c in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0 45 Thread 0x7f44806f6700 (LWP 11376) 0x00007f4497a221d6 in ppoll () from /lib64/libc.so.6 46 Thread 0x7f446c511700 (LWP 11382) 0x00007f4497a221d6 in ppoll () from /lib64/libc.so.6 47 Thread 0x7f44807f8700 (LWP 11375) 0x00007f449959ba17 in write () from /lib64/libpthread.so.0 48 Thread 0x7f44801ec700 (LWP 11381) 0x00007f4497a24a4f in fdatasync () from /lib64/libc.so.6 |
しかしながら、そのスレッドが実行している関数名からある程度の憶測ができます。
最後に実行した関数だけでは心もとない為、過去実行した関数も表示しましょう。
このようにスレッドが過去実行した関数の履歴をbacktrace(バックトレース)といいます。
バックトレースは、スレッドごとに確認することもできますが、ざっと全体を見渡したい場合は、すべてのスレッドにコマンドを実行することを意味するthread apply all
を指定することになります。
backtraceの取得は、 backtrace
もしくは bt
ですので、以下のようなコマンドになります。
1 |
(gdb) thread apply all bt |
実行すると、例で言えば48スレッドが過去実行した関数名の流れが表示されるわけです。
以下のように出力があり、関数のどの行を実行して現在の状態に至っているか一目瞭然です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
Thread 1 (Thread 0x7f395bb32700 (LWP 16845)): #0 0x00007f396f01b9bd in syscall () from /lib64/libc.so.6 #1 0x00007f396fee3b7e in __io_getevents_0_4 () from /lib64/libaio.so.1 #2 0x0000000002233e1b in LinuxAIOHandler::collect() () at /usr/src/debug/mysql-community-8.0.30-1.el8.x86_64/mysql-8.0.30/storage/innobase/os/os0file.cc:2329 #3 0x00000000022346c2 in LinuxAIOHandler::poll(fil_node_t**, void**, IORequest*) () at /usr/src/debug/mysql-community-8.0.30-1.el8.x86_64/mysql-8.0.30/storage/innobase/os/os0file.cc:2475 #4 0x000000000223bdc4 in os_aio_handler (segment=1, m1=0x7f395bb31cc8, m2=0x7f395bb31cc0, request=0x7f395bb31cd0) at /usr/src/debug/mysql-community-8.0.30-1.el8.x86_64/mysql-8.0.30/storage/innobase/os/os0file.cc:2525 #5 0x00000000024ab8ef in fil_aio_wait (segment=segment@entry=1) at /usr/src/debug/mysql-community-8.0.30-1.el8.x86_64/mysql-8.0.30/storage/innobase/fil/fil0fil.cc:7919 #6 0x00000000022e79c8 in io_handler_thread (segment=1) at /usr/src/debug/mysql-community-8.0.30-1.el8.x86_64/mysql-8.0.30/storage/innobase/srv/srv0start.cc:235 #7 0x00000000022e8346 in std::__invoke_impl<void, void (*&)(unsigned long), unsigned long&> (__f=<synthetic pointer>: <optimized out>) at /opt/rh/gcc-toolset-11/root/usr/include/c++/11/bits/invoke.h:90 #8 std::__invoke<void (*&)(unsigned long), unsigned long&> (__fn=<synthetic pointer>: <optimized out>) at /opt/rh/gcc-toolset-11/root/usr/include/c++/11/bits/invoke.h:96 #9 std::_Bind<void (*(unsigned long))(unsigned long)>::__call<void, , 0ul>(std::tuple<>&&, std::_Index_tuple<0ul>) (__args=..., this=<synthetic pointer>) at /opt/rh/gcc-toolset-11/root/usr/include/c++/11/functional:420 #10 std::_Bind<void (*(unsigned long))(unsigned long)>::operator()<, void>() (this=<synthetic pointer>) at /opt/rh/gcc-toolset-11/root/usr/include/c++/11/functional:503 #11 Detached_thread::operator()<void (*)(unsigned long), unsigned long> (f=@0x7f395c3ca8a0: 0x22e79a0 <io_handler_thread(ulint)>, this=0x7f395c3ca8a8) at /usr/src/debug/mysql-community-8.0.30-1.el8.x86_64/mysql-8.0.30/storage/innobase/include/os0thread-create.h:194 #12 std::__invoke_impl<void, Detached_thread, void (*)(unsigned long), unsigned long> (__f=...) at /opt/rh/gcc-toolset-11/root/usr/include/c++/11/bits/invoke.h:61 #13 std::__invoke<Detached_thread, void (*)(unsigned long), unsigned long> (__fn=...) at /opt/rh/gcc-toolset-11/root/usr/include/c++/11/bits/invoke.h:96 #14 std::thread::_Invoker<std::tuple<Detached_thread, void (*)(unsigned long), unsigned long> >::_M_invoke<0ul, 1ul, 2ul> (this=0x7f395c3ca898) at /opt/rh/gcc-toolset-11/root/usr/include/c++/11/bits/std_thread.h:253 #15 std::thread::_Invoker<std::tuple<Detached_thread, void (*)(unsigned long), unsigned long> >::operator() (this=0x7f395c3ca898) at /opt/rh/gcc-toolset-11/root/usr/include/c++/11/bits/std_thread.h:260 #16 std::thread::_State_impl<std::thread::_Invoker<std::tuple<Detached_thread, void (*)(unsigned long), unsigned long> > >::_M_run (this=0x7f395c3ca890) at /opt/rh/gcc-toolset-11/root/usr/include/c++/11/bits/std_thread.h:211 #17 0x00007f396fa04ba3 in execute_native_thread_routine () from /lib64/libstdc++.so.6 #18 0x00007f3970c771ca in start_thread () from /lib64/libpthread.so.0 #19 0x00007f396f01be73 in clone () from /lib64/libc.so.6 : : |
まとめ
できればデータベースには安定して動いていてほしいものですが、急なダウンが発生した場合、大体はできるだけ早く原因追求して対処したいと考えるでしょう。
そのような場合に、ベンダ側での早期の解決をアシストするためにも、普段からデバッグ情報の出力準備をしておくことをオススメします。
次回(があれば)、さらにGDBコマンドでの解析についてご紹介したいと思います。
Let’s Enjoy MySQL!