見出し画像

やはりM4 Maxは爆速!E2Eテストでベンチマーク

こんにちは、IPGエンジニアチームの小山内です!
かなり久しぶりの投稿になってしまいました。

2025年、IPG developer diary は はてなブログからnoteへの引越しを行いまして、これを機に再始動して行きたいと思っております!


IPGのエンジニアチームでは、テレビおよびラジオの番組情報やVOD等のメタ情報をAPI出力し、さまざまなサービスで円滑に利用できるようにすべく、アップデートを続けています。
様々なニーズに対してスピード感のある開発を可能にするための工夫の一つとして、APIに対する独自のリグレッションテストを行っており、今回はその手法の概要と工夫したポイント、その実行時間を使った最新のMacBookのベンチマーク結果をご紹介します。

リグレッションテストの概要

内容を固定したポータブルなデータベースが欲しい

リグレッションテストとしてやりたかったことは「APIの挙動の変化が想定通りかどうか、差分で知りたい」というものでした。
一部を除き、APIはデータベース(MySQL)にのみ依存しているため、データベースを固定出来れば、APIの結果も固定されます。

「必要なテストケースを備えた独立したデータベース」と「API実装」の組み合わせが想定した通りの結果になるかどうか、エンド・ツー・エンドで確認する E2Eテスト を実施すれば、リグレッションテストとして機能すると考えました。

テストデータ入りのMySQLイメージ

当初、MySQLへのテストデータの投入方法として、MySQLの公式Dockerイメージで説明されている /docker-entrypoint-initdb.d に初期データを用意してロードさせる方法を採用しましたが、テストに必要なデータを全てロードさせると30分ほどかかってしまい、また起動する都度待たされるため、E2Eテストとしても開発環境で使用するにしても、実用度が低いものでした。

そこで、MySQL公式イメージをコンテナ起動して必要なデータを投入してからdocker commitでバンドルする方法を考えましたが、以下のようにしても保存されませんでした。

$ docker run --name mysql57 -e MYSQL_ROOT_PASSWORD=password -d mysql:5.7-debian
$ echo 'create database testdb1' | docker exec -i mysql57 mysql -uroot -ppassword
$ echo 'show databases' | docker exec -i mysql57 mysql -uroot -ppassword
Database
information_schema
mysql
performance_schema
sys
testdb1
$ docker stop mysql57
$ docker commit mysql57 testdb1:latest

docker commitしたイメージに testdb1 が無い

$ docker run --name testdb --rm -d testdb1:latest
$ echo 'show databases' | docker exec -i testdb mysql -uroot -ppassword
Database
information_schema
mysql
performance_schema
sys

この原因は、MySQL公式イメージのDockerfileに書いてある以下の部分で、これにより /var/lib/mysql はdockerの匿名ボリュームに書かれるため docker commitでは保存の対象にならず、起動したMySQLに対していくらデータを投入してもイメージに含められないのでした。

VOLUME /var/lib/mysql

これを解決する方法として、以下のようなDockerfileを用意し、VOLUMEとして指定されている /var/lib/mysql ではなく、別のディレクトリ /var/lib/mysql-no-volume を使用してMySQLを起動させるようにしました(どこかの記事を参考にしたと思いますが、すみません失念してしまいました…)

FROM mysql:5.7-debian
RUN mkdir /var/lib/mysql-no-volume
CMD ["--datadir", "/var/lib/mysql-no-volume"]

この方法であれば、大量のテストケースを投入してからdocker commitで保存し、テストケース入りのイメージを作成して配布することが出来ました。

現在日時への依存

テストケース入りのデータベースイメージに含まれている日時などのデータは、時間と共に古くなっていきます。
テスト対象のAPIの一部では現在時刻に依存しているものがあり、テストを実行する日時によって結果が変化してしまうという課題がありました。

APIの現在日時

libfaketime

libfaketimeはプロセス単位で現在日時を騙すことが可能なもので、 /etc/ld.so.preload を設定すると全てのプロセスを対象にすることも可能な、強力なものです。
任意の日時から時計を開始させたり、完全に固定させて時計が進まないようにしてしまうことも出来ますが、APIの応答を固定するためには時計を完全に固定する必要がありました。
しかし、OSレベルで時計が進まないようにすると様々な副作用が起きる場合があり、APIの実装に使用しているNode.jsではMySQLライブラリからの応答が得られなくなってしまい、libfaketimeで時計を止めるのは断念しました。

Sinon.JS

Sinon.JSはJavaScript用のテストツールで、Sinon.JSの現在日時を騙す機能は以下を満たすことが出来たため、こちらを使うことにしました。

  1. APIが正常に動作

  2. 実装への介入が最小限

MySQLの現在日時

MySQLへのクエリでも now() などの現在日時に依存したものがあり、これにより結果が固定できない場合がありました。

MySQLでは SET TIMESTAMP により現在時刻を固定することが可能でした。
ただし、管理権限のあるユーザーには効果が無いため、MySQLのdockerイメージ内にE2Eテスト用の一般ユーザーを作成する必要がありました。

dockerの mysqld.cnf に以下を追加することで、テストデータを作成した日時に現在時刻を偽装することが出来ました。

init-connect = SET NAMES utf8mb4;SET TIMESTAMP=UNIX_TIMESTAMP('2022-02-14 10:17:30');

E2Eテストの運用

「内容を固定したデータベース」と「現在日時を固定したAPI」が実現出来たので、「MySQLのDockerイメージID」と「API実装のID(GitのコミットID)」の組合せに対し、必ず同じ結果が得られるようになり、以下の内容でE2Eテストを実現出来るようになりました。

  1. docker compose upでMySQLコンテナとAPIコンテナを起動

  2. docker compose run で「APIコンテナにcurlし結果を保存する」テストランナーを実行

  3. APIの結果のSHA1を想定と比較することでデグレを検知

これらについては満足する結果が得られ、実際にデグレを検知したこともありました。

パフォーマンス

E2Eテストの課題のひとつとして、パフォーマンスがありました。

APIのテストは700ほどのテストケースを直列にHTTPリクエストするスクリプトで、Intel上のLinuxでは5分ほどで完了しましたが、Intel MacBookではおそらくdockerのパフォーマンスがボトルネックとなり、15分ほどかかっていました。
MacOSでのdockerのパフォーマンスが良くないのは体感していましたが、Appleシリコンを搭載したMacを使うようになると更に顕著で、M1では30分ほど待たされるようになりました。
Docker Hubに用意されているMySQL5.7の公式イメージはx86_64のみでARM版が無いため、Appleシリコン上ではQEMUによるエミュレーションでx86_64のMySQLを動作させており、それがボトルネックと思われました。

Rosetta for Linux

Appleシリコン搭載のMacOSでは、Apple謹製のRosetta2によりx86_64バイナリを高速に実行することが可能ですが、DockerデスクトップのアップデートによりdockerからRosetta2が使えるようになり、大幅にパフォーマンスが向上しました。
M1でおよそ30分かかっていたテストが5分ほどになり、Intel Macとほぼ同等になりました。

ベンチマーク

最後に、このE2Eテストの所要時間を使ったベンチマークの結果をご紹介します。
独自の内容なので、IPG社内でしか指標とならないものですが、おおよそのスペックが想像できるものにはなってそうかなと思います。

M2 Proの計測では、Rosetta for Linuxのbeta版と正式リリース版で30秒も速くなっているのが興味深かったです。
また予想通り、M4 Maxがトップでした。

いいなと思ったら応援しよう!