Debugging MySQL/MariaDB (1): Build and Test

MySQL/MariaDBデバッグする実践的方法を解説する。この記事ではデバッグを行うための準備を行い、具体的なデバッグの方法については (2) 以降の記事で説明する予定である。なお、一連の記事すべてにおいて MySQL 8.0.24 および MariaDB 10.5.9 を前提として解説する。

Building MySQL/MariaDB

MySQL/MariaDB のそれぞれについて、ビルドする方法を簡単にまとめておく。以下の記述は筆者の開発環境 (Vagrant Box bento/ubuntu-20.04) を前提としたものである。

MySQL

Boost 同梱版のソースを公式からダウンロードし展開する。GitHub から取得すると Boost のバージョンを合わせるのがかなり面倒なので、Boost 同梱版を使うのが吉である。

wget https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-boost-8.0.24.tar.gz
tar xfv mysql-boost-8.0.24.tar.gz

次に必要なパッケージをインストールし、デバッグビルドする。

apt-get install g++ cmake pkg-config   # ビルドに必要
apt-get install libjson-perl zip unzip # テストに必要
mkdir -p mysql-8.0.24/bld && cd $_
cmake -DWITH_BOOST=../boost .. -DCMAKE_BUILD_TYPE=Debug
cmake --build . --config Debug -j 4

MariaDB

Building MariaDB on Ubuntu に従って依存パッケージをインストールする。

apt-get install software-properties-common devscripts equivs
apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com:80 0xF1656F24C74CD1D8
add-apt-repository --update --yes --enable-source \
       'deb [arch=amd64] http://nyc2.mirrors.digitalocean.com/mariadb/repo/10.5/ubuntu '$(lsb_release -sc)' main'
apt-get build-dep mariadb-10.5

GitHub からソースを取得し、デバッグビルドする。

git clone -b mariadb-10.5.9 --depth=1 git@github.com:MariaDB/server.git mariadb-server
mkdir mariadb-server/bld && cd $_
cmake -DCMAKE_BUILD_TYPE=Debug ..
cmake --build . --config Debug -j 4

デバッグビルドしようとすると、コンパイル時に1つでも警告が出るとコンパイルエラーになる (-Werror)。HEAD で開発する場合はこの動作で問題ないが、例えば git-bisect のために古いバージョンをビルドする場合に邪魔になることがある。-DMYSQL_MAINTAINER_MODE=OFF することで警告を警告のままにできる。

cmake -DCMAKE_BUILD_TYPE=Debug -DMYSQL_MAINTAINER_MODE=OFF ..

Testing MySQL/MariaDB

同じ SQL を何度も手で投入するのは苦痛であり効率も悪いので、デバッグを始める前にテストケースを作成しよう。MySQL Test Framework、特に mysql-test-run.pl を使えば、SQL を書く感覚で E2E テストを書くことができる。なお、筆者が知る限りにおいて、MySQL におけるテストは E2E テストが主体であり、ユニットテストを書くことはあまりないようだ。

Running Test Cases

まずは MySQL のテストについて簡単に説明しよう。以下、MySQL を例に取って説明するが、MariaDB の場合もほとんど同様である。

各々のテストケースは、suite という単位でまとめられている。mysql-test/suite/ディレクトリ1つ1つが suite に一対一対応し、その中に個々のテストケースが配置されている。

> ls -A mysql-test/suite
audit_null              innodb_fts             network_namespace
auth_sec                innodb_gis             opt_trace
binlog                  innodb_stress          parts
binlog_gtid             innodb_undo            perfschema
binlog_nogtid           innodb_zip             query_rewrite_plugins
clone                   interactive_utilities  rpl
collations              jp                     rpl_gtid
component_keyring_file  json                   rpl_ndb
connection_control      json_ndb               rpl_nogtid
encryption              large_tests            secondary_engine
engines                 lock_order             service_status_var_registration
federated               max_parts              service_sys_var_registration
funcs_1                 memcached              service_udf_registration
funcs_2                 ndb                    special
gcol                    ndb_big                stress
gcol_ndb                ndb_binlog             sys_vars
gis                     ndb_ddl                sysschema
group_replication       ndb_opt                test_service_sql_api
information_schema      ndb_rpl                test_services
innodb                  ndbcluster             x

MariaDB の場合もほぼ同様だが、main suite のみ mysql-test/ の直下に配置されている。main suite は特別扱いされているようで、他の suite とはやや扱いが異なるので注意すること。

MySQL Test Framework のテストは、様々な単位での実行が可能である。以下に例を示す。

cd bld/mysql-test/
./mysql-test-run.pl                      # すべてのテストを実⾏
./mysql-test-run.pl --suite innodb       # innodb suite のテストをすべて実⾏
./mysql-test-run.pl innodb.create_table  # innodb suite の create_table を実⾏

複数のテストを同時に実行する場合は、--parallel--force という2つのオプションを与えるのがオススメだ。--parallel は読んで字の如く並列数を指定するもので、--force はいくつかのテストケースが失敗しても最後までテストを実行するよう指示するものである。

./mysql-test-run.pl --parallel=8 --force

Writing Test Cases

新規にテストケースを作る方法を説明する。なお、同様にして既存のテストケースにテストを追加することもできるが、デバッグのしやすさを考えると新規に作成する方がよいだろう。

まず、適切な suite を選び、テストファイルと結果ファイルを作成する。例示のためなのでどこでもよいのだが、仮に innodb suite を使うこととしよう。 ​

touch mysql-test/suite/innodb/t/example.test
touch mysql-test/suite/innodb/r/example.result

テストファイルにはSQLテストコマンド が書ける。結果ファイルにはテストファイルに書いた SQL とその望ましい返り値を書けばよい。以下に簡単な例を挙げる。 ​ ​

# mysql-test/suite/innodb/t/example.test

CREATE TABLE t1 (a INT);
INSERT INTO t1 VALUES (1);
SELECT * FROM t1;
DROP TABLE t1;
--error ER_BAD_TABLE_ERROR
DROP TABLE t;
# mysql-test/suite/innodb/r/example.result

CREATE TABLE t1 (a INT);
INSERT INTO t1 VALUES (1);
SELECT * FROM t1;
a
1
DROP TABLE t1;
DROP TABLE t;
ERROR 42S02: Unknown table 'test.t'

​ 結果ファイルを手で書くのが面倒な場合は、結果ファイルは空のままテストを実行する。すると実⾏結果が結果ファイルと同じディレクトリに example.reject という名前で生成されるので、それをベースにして結果ファイルを作ることができる。

テスト開始時にサーバーにオプションを渡したい場合や、特定の my.cnf を使わせたい場合は、*.opt および *.cnf ファイルを用いる。どのテストにどの設定ファイルが適用されるかの判定ルールはやや複雑なので、その説明はドキュメント に譲ることとする。

Running Server without Installation

mysql-test-run.pl の使い道はテストの実行だけではない。--start オプションを与えることで、make install せずとも、ビルド済みバイナリから MySQL/MariaDB サーバーを起動できる。これは、テストケースを作るために試行錯誤する段階で役に立つ。

例えば以下のようにすると、innodb suite の create_table.test の起動設定 (*.opt, *.cnf) で、サーバーを起動することができる。

./mysql-test-run.pl innodb.create_table --start

ソケットのパスが表示されるので、それを使って起動したサーバーに接続する。

mysql -u root -S ${SOCKET}