Upgrade to Pro — share decks privately, control downloads, hide ads and more …

mysql-build-debug-and-test

kentsu
June 17, 2023

 mysql-build-debug-and-test

OSC Online/Hokkaido 2023 で発表した内容です。

以下、参考文献
実例で学ぶ MySQL/MariaDB デバッグ
- https://nayuta-yanagisawa.hatenablog.com/entry/learning-mysql-mariadb-debugging-by-example

- Run MySQL Test Suite
- https://dev.mysql.com/doc/dev/mysql-server/latest/PAGE_MYSQL_TEST_RUN_PL.html

- Expert MySQL
- High Performance MySQL 4th Edition

- 日々の覚書
- https://yoku0825.blogspot.com/
- 困ったら大体ここ見てどうにかなる

kentsu

June 17, 2023
Tweet

More Decks by kentsu

Other Decks in Technology

Transcript

  1. 自己紹介 - けんつ (@lrf141) - 本職: しがないバックエンドエンジニア - 好きなこと -

    MySQL をいじめる、DBMS 的な話題 - ストレージエンジン、オプティマイザ - 好きな MySQL のバージョン - 8.0.16 - 苦戦した MySQL のバージョン - 8.0.18, 8.0.28
  2. 今まで踏んだ問題 - 特定条件下で temporary table のサイズ計算をミスって死ぬ - サイズ計算で何故か結果 Null になる場合があったっぽい?

    - date > “2023-01” みたいな比較ができなくなった - 日付を雑に文字列で扱うと DATE, DATETIME にキャストできなくなった - 一度に大量に insert すると commit で固まる - 全く原因がわからないが replica thread を 1 にしたらなんか動いた - CPU リソースを使い切っている状態で接続しまくると MySQL が死ぬ - 新しいスレッドを作れなくて例外で落ちる
  3. 今まで踏んだ問題 - 特定条件下で temporary table のサイズ計算をミスって死ぬ - サイズ計算で何故か結果 Null になる場合があったっぽい?

    - date > “2023-01” みたいな比較ができなくなった - 日付を雑に文字列で扱うと DATE, DATETIME にキャストできなくなった - 一度に大量に insert すると commit で固まる - 全く原因がわからないが replica thread を 1 にしたらなんか動いた - CPU リソースを使い切っている状態で接続しまくると MySQL が死ぬ - 新しいスレッドを作れなくて例外で落ちる
  4. 今日の目的 - MySQL のビルド・デバッグ出来る - 興味のある機能の実装に到達できる - バグっぽい動きを見つけてどう調べたら良いか分かる - ついでにテストベースで楽にデバッグ出来る

    - 巨大なソースコードの歩き方が少し分かった - MySQL の実装を読んで盛り上がれる人を増やしたい - 純粋培養の邪念です。
  5. 今日話すこと 1. 普段使っている開発環境 2. MySQL のビルドと調査前準備 3. ディレクトリの構成 4. mysql-test-run

    5. MySQL の実装を読むための色々 6. 読み解くのにやや気合のいる箇所 7. rr-debugger を使う
  6. 今日話すこと 1. 普段使っている開発環境 2. MySQL のビルドと調査前準備 3. ディレクトリの構成 4. mysql-test-run

    5. MySQL の実装を読むための色々 6. 読み解くのにやや気合のいる箇所 7. rr-debugger を使う
  7. 普段使っている環境 - Pop!_OS 22.04 - Linux がやはり快適。macOS は clang が厳しい。

    - Core i7-11800H - 16 スレッドもあればビルドは速い。 - 64 GB RAM - 半分も使ったことがない。快適に IDE を使うため。 - SSD 1TB - 大量のオブジェクトファイルが生成されるので書き込み速度が物を言う。 - Jetbrains CLion - これが最強だと思っている。但しメモリを食う。 - VS Code も良い。Remote development は魔法だと思っている。
  8. 今日話すこと 1. 普段使っている開発環境 2. MySQL のビルドと調査前準備 3. ディレクトリの構成 4. mysql-test-run

    5. MySQL の実装を読むための色々 6. 読み解くのにやや気合のいる箇所 7. rr-debugger を使う
  9. MySQL のビルドと調査前準備 1. MySQL のソースコードを手元に落とす 公式から配布されているものなら以下を wget などで取ってくる。 boost 同梱版とそうでないものがある。

    https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-boost-8.0.XX.tar.gz github で管理されているものなら https://github.com/mysql/mysql-server
  10. MySQL のビルドと調査前準備 3. ビルドする ちょっとした解説。 - -DCMAKE_BUILD_TYPE=Debug - この指定によって debug

    build となる。 - debug trace などを出力できるようにする - -DWITH_BOOST=./boost - boost が存在すると仮定されるディレクトリ。 - build ディレクトリの下に作ってやればビルドし直す時も楽。 - -DDOWNLOAD_BOOST=1 - WITH_BOOST オプションによって与えられたディレクトリに boost を展開する
  11. MySQL のビルドと調査前準備 3. ビルドする: トラブルシュート - Makefile の生成でコケた場合 - 必要な依存関係がない場合がほとんど

    - 直前にあるログから各種ライブラリのバージョンが正しく取得できているか - make でコケた場合 - VERBOSE=1 make でどこでコケたのかを調べる - cmake/ 以下か、実装がある各ディレクトリ下の CMakeLists.txt から原因を探る - 必要に応じて抑制するか、気合でどうにか対処するかの二択
  12. MySQL のビルドと調査前準備 余談: 自分が踏んだビルド時のトラブル - MySQL 8.0.28 以下では OpenSSL v3

    を使ってビルドできない - OpenSSL のヘッダーファイルからバージョン情報を取り出している - そのフォーマットが変更されたため、インストールしていても無い判定になる - OpenSSL v3 未満を指定することで解決できる。 - MySQL 8.0.31 ~ 8.0.33 でロケール依存のビルドがある - build-id の検証でコケる - readelf コマンドの出力が en 以外だとその後の grep で Build Id を取得できない - export LANG=C or -DWITH_BUILD_ID=0 で解決できる
  13. MySQL のビルドと調査前準備 4. mysqld の初期設定を行う  そのままでは起動しないので諸々の初期設定を行う。 mysql-server/build$ ./bin/mysqld --initialize ...

    2023-06-14T11:37:42.100824Z 1 [System] [MY-013576] [InnoDB] InnoDB initialization has started. 2023-06-14T11:37:42.491830Z 1 [System] [MY-013577] [InnoDB] InnoDB initialization has ended. 2023-06-14T11:37:46.307536Z 6 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: bo:?9u=1:RiL
  14. MySQL のビルドと調査前準備 4. mysqld の初期設定を行う 出力されたパスワードで root ユーザーとして操作できるので ログインできたら適当なパスワードに変更しておく。 mysql-server/build$

    ./bin/mysql -uroot -p"bo:?9u=1:RiL" --protocol=TCP mysql> alter user 'root'@'localhost' identified by 'root'; Query OK, 0 rows affected (0.01 sec) mysql> flush privileges; Query OK, 0 rows affected (0.01 sec)
  15. 今日話すこと 1. 普段使っている開発環境 2. MySQL のビルドと調査前準備 3. ディレクトリの構成 4. mysql-test-run

    5. MySQL の実装を読むための色々 6. 読み解くのにやや気合のいる箇所 7. rr-debugger を使う
  16. ディレクトリの構成 1. data ディレクトリの構成  data ディレクトリの下には mysqld が利用する様々なファイルがある  例)   binlog.0000X:

    バイナリログが格納   undo_00X: undo ログが格納   mysql, sys, etc: 各データベースの実データが格納
  17. ディレクトリの構成 1. data ディレクトリの構成 mysql-server/build$ tree -L 1 data data

    |-- #ib_16384_0.dblwr |-- #ib_16384_1.dblwr |-- binlog.000001 |-- ib_buffer_pool |-- ibdata1 |-- mysql |-- mysql.ibd |-- performance_schema |-- sample <- これが DB の dir |-- sys |-- undo_001 mysql-server/build$ tree -L 1 data/sample data/sample `-- t1.ibd <- テーブルの実データ
  18. ディレクトリの構成 2. MySQL のソースコードレイアウト - sql/ - mysql-server に関わる諸々の実装 (多分)

    - replication, query parser, optimizer, etc… - storage/ - storage engine の実装 - innobase/ 以下に InnoDB の実装がある - mysql-test/ - mysql-server の E2E テスト - cmake/ - 各種ライブラリの依存などを解決する cmake ファイル - boost.cmake もここにある mysql-server$ tree -L 1 . |-- CMakeLists.txt … |-- build |-- client |-- cmake |-- mysql-test |-- mysys |-- sql |-- sql-common `-- storage
  19. 今日話すこと 1. 普段使っている開発環境 2. MySQL のビルドと調査前準備 3. ディレクトリの構成 4. mysql-test-run

    5. MySQL の実装を読むための色々 6. 読み解くのにやや気合のいる箇所 7. rr-debugger を使う
  20. mysql-test-run 1. mysql-test-run とは何か  mysql-server の E2E テストを実行するコマンド。  テストケース、期待結果を共に SQL

    ライクな構文で記述されている。  様々なデバッガと共に実行することも可能。  手動で SQL を流すのは面倒なのでテストケースに全て任せたい。
  21. mysql-test-run 2. mysql-test-run のディレクトリ構成 mysql-test$ tree -L 1 . |--

    CMakeLists.txt |-- extra |-- include |-- lib … |-- suite <- 機能別テストスイート |-- mysql-stress-test.pl |-- mysql-test-run.pl |-- r <- 期待結果 |-- t <- テストケース `-- valgrind.supp mysql-test/suite/innodb$ ls ./t/ | head -n 2 add_foreign_key.test alter_crash.test … mysql-test/suite/innodb$ ls ./r/ | head -n 2 add_foreign_key.result alter_crash.result …
  22. mysql-test-run 3. テストを実行する  build/mysql-test/ の下に mtr という実行可能ファイルが存在する。  これを実行することで E2E テストを実行できる。

    mysql-server/build/mysql-test$ ./mtr Logging: /home/lrf141/mysqlProject/mysql-server/mysql-test/mysql-test-run.pl MySQL Version 8.0.33 Checking supported features - Binaries are debug compiled Using suite(s): auth_sec,binlog ... ============================================================================== TEST NAME RESULT TIME (ms) COMMENT ------------------------------------------------------------------------------ [ 0%] clone.ddl_compress [ skipped ] Test needs 'big-test' or 'only-big-test' option. [ 0%] clone.ddl_compress_encrypt [ skipped ] Test needs 'big-test' or 'only-big-test' option. [ 0%] clone.ddl_encrypt [ skipped ] Test needs 'big-test' or 'only-big-test' option. ...
  23. mysql-test-run 5. デバッグしながらテストを実行する  例えば gdb でデバッグしながら実行したい場合。  --manual-gdb オプションをつけて実行する。 build/mysql-test$ ./mtr

    innodb.add_foreign_key --manual-gdb … To start gdb for mysqld.1, type in another window: gdb -cd /home/lrf141/mysqlProject/mysql-server/mysql-test -x /home/lrf141/mysqlProject/mysql-server/build/mysql-test/var/tmp/gdbinit.mysqld.1 /home/lrf141/mysqlProject/mysql-server/build/runtime_output_directory/mysqld
  24. mysql-test-run 5. デバッグしながらテストを実行する  main 関数に breakpoint が置かれ停止する。 $ gdb -cd

    /home/lrf141/mysqlProject/mysql-server/mysql-test \ -x /home/lrf141/mysqlProject/mysql-server/build/mysql-test/var/tmp/gdbinit.mysqld.1 \ /home/lrf141/mysqlProject/mysql-server/build/runtime_output_directory/mysqld … Breakpoint 1, main (argc=8, argv=0x7fffffffddf8) at /home/lrf141/mysqlProject/mysql-server/sql/main.cc:25 25 int main(int argc, char **argv) { return mysqld_main(argc, argv); } (gdb)
  25. mysql-test-run 6. テストケースを書く  mysql-test/suite/innodb 以下に書くことを想定 --echo # Sample Test Case

    CREATE TABLE t1(id int not null primary key) Engine=InnoDB; INSERT INTO t1(id) VALUES(1); --error ER_DUP_ENTRY INSERT INTO t1(id) VALUES(1); SELECT id FROM t1; DROP TABLE t1; # Sample Test Case CREATE TABLE t1(id int not null primary key) Engine=InnoDB; INSERT INTO t1(id) VALUES(1); INSERT INTO t1(id) VALUES(1); ERROR 23000: Duplicate entry '1' for key 't1.PRIMARY' SELECT id FROM t1; id 1 DROP TABLE t1;
  26. 今日話すこと 1. 普段使っている開発環境 2. MySQL のビルドと調査前準備 3. ディレクトリの構成 4. mysql-test-run

    5. MySQL の実装を読むための色々 6. 読み解くのにやや気合のいる箇所 7. rr-debugger を使う
  27. MySQL の実装を読むための色々 1. git bisect を使った調査  中間バージョンへのチェックアウトとテスト実行をよろしくやるヤツ。   → first bad

    commit が巨大な場合は無力  前は上手く行っていたのに今はバグる、みたいなパターンに有効。  原因となったコミットが判明すれば読むべき範囲が確定する。   →特に見つかったコミットの変更が小さい場合は勝利である
  28. MySQL の実装を読むための色々 1. git bisect を使った調査  例) 8.0.30 では通ったビルドが 8.0.31

    で通らない  まずは good commit, bad commit を設定する。 # git bisect start <bad-commit> <good-commit> $ git bisect start 8.0.31 8.0.30
  29. MySQL の実装を読むための色々 1. git bisect を使った調査  テストスクリプトを用意する #!/bin/bash # testMySQLBuild.sh

    mkdir build && cd $_ mkdir boost cmake ../ -DCMAKE_BUILD_TYPE=Debug \ -DWITH_BOOST=./boost -DDOWNLOAD_BOOST=1 cmake --build . --config debug -j14 BUILD_STATUS=$? cd `git rev-parse --show-toplevel` rm -rf build exit $BUILD_STATUS
  30. MySQL の実装を読むための色々 1. git bisect を使った調査 テストスクリプトを実行してひたすら待つ。 $ git bisect

    run ../testMySQLBuild.sh … 321f106987a3fd62035057c8b544e7a874bc76d3 is the first bad commit commit 321f106987a3fd62035057c8b544e7a874bc76d3 …
  31. MySQL の実装を読むための色々 1. git bisect を使った調査 テストスクリプトを実行してひたすら待つ。 $ git bisect

    run ../testMySQLBuild.sh … 321f106987a3fd62035057c8b544e7a874bc76d3 is the first bad commit commit 321f106987a3fd62035057c8b544e7a874bc76d3 …
  32. MySQL の実装を読むための色々 2. debug trace を気合で読む  テストが書きにくい、どこから読めば良いか分からない。   → debug

    trace を読むとコードパスは分かる   但し、とにかくサイズがデカい。  本当にサイズがデカい。
  33. MySQL の実装を読むための色々 2. debug trace を気合で読む  debug trace を吐き出す方法 #1

    /tmp/mysqld.trace mysql-server/build$ ./bin/mysqld --debug #2 build/var/log/mysqld.1.trace mysql-server/build/mysql-test$ ./mtr innodb.add_foreign_key --debug
  34. MySQL の実装を読むための色々 2. debug trace を気合で読む  MySQL の実装を読むとデバッグ用のマクロがある。 - DBUG_ENTER

    - DBUG_RETURN - DBUG_TRACE  これらが debug trace に記録される境界となる。  全ての実装に書かれている訳ではないので自分で書いてビルドするのも良い。
  35. MySQL の実装を読むための色々 2. debug trace を気合で読む ... @2: | |

    >dd::Raw_new_record::insert T@2: | | | >handler::ha_write_row T@2: | | | | >int ha_innobase::write_row T@2: | | | | | >handler::update_auto_increment T@2: | | | | | | >ha_innobase::update_thd    ← DBUG_ENTER T@2: | | | | | | | ha_innobase::update_thd: user_thd: 0x5555abc30790 -> 0x5555abc30790 T@2: | | | | | | | >innobase_trx_init T@2: | | | | | | | <innobase_trx_init T@2: | | | | | | <ha_innobase::update_thd ← DBUG_RETURN T@2: | | | | | | >ha_innobase::innobase_lock_autoinc T@2: | | | | | | <ha_innobase::innobase_lock_autoinc ...
  36. 今日話すこと 1. 普段使っている開発環境 2. MySQL のビルドと調査前準備 3. ディレクトリの構成 4. mysql-test-run

    5. MySQL の実装を読むための色々 6. 読み解くのにやや気合のいる箇所 7. rr-debugger を使う
  37. 読み解くのにやや気合がいる部分 1. partitioning を利用しているパターン  ストレージエンジンは handler インターフェースを実装している。   → mysql-server はそれらを呼び出すことで

    INSERT 等を行う  InnoDB では partition を利用している場合、まず専用の実装を呼び出す。   →継承関係がやや複雑   →ha_innopart が Partition_helper を呼び出し、そいつは ha_innopart を呼ぶ
  38. 読み解くのにやや気合がいる部分 1. partitioning を利用しているパターン  ストレージエンジンは handler インターフェースを実装している。   → mysql-server はそれらを呼び出すことで

    INSERT 等を行う  InnoDB では partition を利用している場合、まず専用の実装を呼び出す。   →継承関係がやや複雑   →ha_innopart が Partition_helper を呼び出し、そいつは ha_innopart を呼ぶ
  39. 読み解くのにやや気合がいる部分 1. partitioning を利用しているパターン  ストレージエンジンは handler インターフェースを実装している。   → mysql-server はそれらを呼び出すことで

    INSERT 等を行う  InnoDB では partition を利用している場合、まず専用の実装を呼び出す。   →継承関係がやや複雑   →ha_innopart が Partition_helper を呼び出し、そいつは ha_innopart を呼ぶ
  40. 読み解くのにやや気合がいる部分 1. partitioning を利用しているパターン  ストレージエンジンは handler インターフェースを実装している。   → mysql-server はそれらを呼び出すことで

    INSERT 等を行う  InnoDB では partition を利用している場合、まず専用の実装を呼び出す。   →継承関係がやや複雑   →ha_innopart が Partition_helper を呼び出し、そいつは ha_innopart を呼ぶ
  41. 読み解くのにやや気合がいる部分 2. mysql_alter_table に関する処理  mysql-server 側の ALTER TABLE に関する処理。  全て(??)

    ALTER TABLE は mysql_alter_table 関数を通過する   →とても長い上に複雑な処理になっている   → mysql_alter_table ではなくその先で呼ばれるものを先に読んだ方が良い
  42. 読み解くのにやや気合がいる部分 2. mysql_alter_table に関する処理  mysql-server 側の ALTER TABLE に関する処理。  全て(??)

    ALTER TABLE は mysql_alter_table 関数を通過する   →とても長い上に複雑な処理になっている   → mysql_alter_table ではなくその先で呼ばれるものを先に読んだ方が良い
  43. 今日話すこと 1. 普段使っている開発環境 2. MySQL のビルドと調査前準備 3. ディレクトリの構成 4. mysql-test-run

    5. MySQL の実装を読むための色々 6. 読み解くのにやや気合のいる箇所 7. rr-debugger を使う
  44. rr-debugger を使う 1. rr-debugger の使い方  trace ファイルを生成  trace ファイルを元に実行する mysql-server/build$

    _RR_TRACE_DIR=/tmp/rr-trace rr record ./bin/mysqld mysql-server/build$ rr replay /tmp/rr-trace/latest-trace 0x00007f11b61532b0 in _start () from /lib64/ld-linux-x86-64.so.2 (rr)
  45. rr-debugger を使う 1. rr-debugger の使い方  InnoDB のテーブルに一行追加を考える (rr) b ha_innobase::write_row

    Breakpoint 2 at 0x55d0c65b7866: file mysql-server/storage/innobase/handler/ha_innodb.cc, line 8972. (rr) c Continuing. Thread 1 hit Breakpoint 2, ha_innobase::write_row 8972 { (rr)
  46. rr-debugger を使う 1. rr-debugger の使い方  InnoDB のテーブルに一行追加を考える (rr) b ha_innobase::write_row

    Breakpoint 2 at 0x55d0c65b7866: file mysql-server/storage/innobase/handler/ha_innodb.cc, line 8972. (rr) c Continuing. Thread 1 hit Breakpoint 2, ha_innobase::write_row 8972 { (rr)
  47. rr-debugger を使う 1. rr-debugger の使い方  既に通過した処理に breakpoint 貼って戻る (rr) b

    handler::ha_write_row Breakpoint 3 at 0x55d0c511ed2c: ... (rr) rc Continuing. Thread 1 hit Breakpoint 3, handler::ha_write_row ... 7937 int handler::ha_write_row(uchar *buf) { (rr) c Continuing. Thread 1 hit Breakpoint 2, ha_innobase::write_row ... 8972 {
  48. ここまでで出来るようになること - MySQL のビルド・デバッグ - プラグインなんかも作れるようになるはず - ストレージエンジンを作るというロマンへ … -

    mysql-test-run を使ったテスト実行・デバッグ - どうやって見たい実装を見つけるか - デバッグを楽にするにはどうしたら良いか - mtr で使えないことが悔やまれる rr-debugger - MySQL/MariaDB にパッチを送れるかも - MariaDB も基本的には同じ
  49. 参考にした・したい資料 - 実例で学ぶ MySQL/MariaDB デバッグ - https://nayuta-yanagisawa.hatenablog.com/entry/learning-mysql-mariadb-debugging-by-example - Run MySQL

    Test Suite - https://dev.mysql.com/doc/dev/mysql-server/latest/PAGE_MYSQL_TEST_RUN_PL.html - Expert MySQL - 内容は古いが内部実装について書かれた良書 - High Performance MySQL 4th Edition - 実は内部実装の前提などが書かれている - 日々の覚書 - https://yoku0825.blogspot.com/ - 困ったら大体ここ見てどうにかなる