Talos
Mozilla で私たちが最初に作成した自動化システムは Talos と呼ばれるパフォーマンステストフレームワークだった。Talos の所有者が変更される中で開発初期の仮定や設計判断は多くが正当な理由を持たなくなったにもかかわらず、Talos は 2007 年に誕生してから大きな変更を受けることなく着実なメンテナンスだけを受け続けた。
2011 年の夏、私たちは Talos で計測される様々な数値に含まれるノイズや変動にようやく目を向け、システムを改善するために小規模な変更ができないかを考え始めた。これがパンドラの箱を開けることになるとは思ってもいなかった。
本章では、Talos のレイヤーを下へ下へと掘り進む中で私たちが発見した問題と試した解決策を説明する。私たちの失敗と成功から読者が何かを学ぶことを願っている。
概観
Talos を部分ごとに説明しよう。Talos の心臓部にはテストハーネスがある。このテストハーネスは新しい Firefox プロファイルを作成し、プロファイルを初期化し、ブラウザを較正し、指定されたテストを実行し、最後に結果の概要を報告する。Talos レポジトリ内にあるテストは二つに分けられる: 単一のページを使って単一の数値 (例えば、特定のウェブページを開いたときに onload
ハンドラが実行されるまでのスタートアップ時間) を報告するテストと、固定されたページの集合に対してページの読み込み時間を計測するテストである。テストの実行では、様々なページのオープン、メモリ使用量やページの読み込み時間といった情報の収集、ブラウザの異なるモードのテストといった操作が Firefox 拡張機能を通して行われる。テストハーネスを可能な限り一般的にして、様々なパフォーマンス属性を測定する多種多様なテストを実行できるようにする、というのが当初の意図だった。
収集されたデータを報告するとき、Talos のテストハーネスは JSON を Graph Server に送信する。Graph Server はインハウスのグラフ描画ウェブアプリケーションであり、実行したテスト、測定された値、プラットフォーム、構成を含む事前に定義されたフォーマットのデータを Talos から受け取る。Graph Server は計測値のトレンドやパフォーマンスのレグレッションを調べるためのインターフェースとしても機能する。テストの実行中は標準的な Apache ウェブサーバーのローカルインスタンスによってページがサーブされる。
最後に、Talos はレグレッションを報告するツールを持つ。Firefox レポジトリにチェックインがあるたびに一部の Talos テストが実行され、その結果が Graph Server に送信され、別のスクリプトが Graph Server からデータを取得し、レグレッションの有無を判定する。レグレッションが見つかる (スクリプトの解析によって、直前のチェックインがテストのパフォーマンスを大きく低下させたことが判明する) と、スクリプトはコードをチェックインした個人およびメーリングリストにメッセージを送信する。
以上のアーキテクチャ (図 8.1) は非常に単純だったものの、Mozilla に新しいプラットフォーム、プロダクト、テストが追加されるにつれ Talos の各要素は変容していった。システム全体をエンドツーエンドのソリューションとして見る視点が欠けていたために、Talos は大量の作業を必要とする状態になっていた。Talos が抱えていた問題を次に示す:
- 測定値に含まれるノイズがレグレッションとして報告されることが多くあり、信頼性が低かった。
- レグレッションを判定するとき、スクリプトは前後三つずつのチェックインに対してテスト結果を比較する。このため、チェックインしてからテストの結果が得られるまで数時間かかることもあった。
- Graph Server に「送られてくるデータは事前に定義されたプラットフォーム、ブランチ、テストタイプ、構成に関連付く必要がある」という厳格な要件があるため、新しいテストを追加するにはデータベースに対する SQL 文の実行が必要であり、難易度が高かった。
- Talos のテストハーネスが要件を一般的に捉えすぎており、実行がそもそも難しかった ── 構成スクリプトを生成する「構成ステップ」があり、その構成スクリプトがテストを実行していた。
2011 年の夏、Talos のテストハーネスに新しいプラットフォームとテストを追加するハックをしたとき、私たちは Jan Larres の修士論文が示した研究結果を発見した。この研究は Talos テストの測定値で生じる大きなノイズを調査したものであり、ハードウェア、オペレーティングシステム、ファイルシステム、ドライバ、Firefox といった Talos テストに影響を及ぼす可能性のある要因を調査していた。この結果を元に、Stephen Lewchuk はインターンの間に Talos テストのノイズを統計的に削減する作業に取り組んだ。
彼らの作業をベースとして、私たちは Talos テストのノイズを除去あるいは低減する計画を立てた。テストハーネスをハックしたことがある開発者はテストハーネス自身に取り組み、ウェブ開発者は Graph Server を更新し、統計の知識がある開発者はノイズの小さい予測可能な結果を生成するための最適な方法を見つけることになった。
計測対象を理解する
パフォーマンステストを行うときは、プロダクトの開発者にとって価値ある情報を提供し、特定の条件におけるプロダクトの振る舞いをユーザーに分かりやすく伝えるテストを用意することが重要となる。また、必要になったとき結果を再現できるように再現可能な環境も用意しておくことも重要となる。しかし、最も重要なのは実行するテストを理解すること、そしてテストが測定する値の意味を理解することである。
プロジェクトが始まって数週間が経過するとシステム全体に関する理解が進み、様々なテストを異なる構成で実行する実験が行われるようになった。その中で開発者の頭にたびたび浮かんだ疑問は「この値は何を意味しているんだ?」だった。ドキュメントがほとんど (あるいは全く) 存在せず、何年も前に追加されてそのままになっているテストが多くあった。
さらに悪いことに、自動的に実行されたテストが報告する結果をローカルで再現するのは不可能だった。テストハーネスが「統計処理」 (ページごとに最大値を排除して、それ以外の値の平均を取る) を行っており、Graph Server も同様の処理 (最大値を報告したページを排除して、残りのページの平均値を取る) を行っていることが判明した。この結果、過去の測定値がほとんど意味を持たず、何を測定しているかを誰も理解していなかった。
ただ、私たちがいくらか知識を持っていたテストが一つあった: トップ 100 のウェブサイト (のスナップショット) を一つずつ読み込むことを 10 回繰り返すテストである。このテストで Talos はページを開き、mozAfterPaint
イベント (Firefox がウェブページの描画を終えると発火する標準的なイベント) が発火するまでの時間を計測する。一度の実行で得られる 1000 個のデータ点を見ると、明らかなパターンがあった。これらのデータ点を単一の数値に変換して、その数値の変動だけに注目するとき何が起こるかを想像してほしい。CSS のパースが高速化されると同時に画像の読み込みが低速化したら? 17 番目のページだけが遅くなり、他の 99 個のページは同じだったら? そういった状況は検出できない。オリジナルの Talos が使った計算方法の問題点が分かる例を次に示す。
3 つのページに対する読み込み時間の測定値がそれぞれ次の通り (単位はミリ秒) だったとする:
- ページ 1: 570, 580, 600, 530, 560
- ページ 2: 780, 650, 620, 700, 750
- ページ 3: 1220, 980, 1000, 1100, 1200
まず、Talos のテストハーネスは各ページの測定値から最大値を排除し、残りの値の平均値を計算する:
- ページ 1: (570 + 580 + 530 + 560) ÷ 4 = 560
- ページ 2: (650 + 620 + 700 + 750) ÷ 4 = 680
- ページ 3: (980 + 1000 + 1100 + 1200) ÷ 4 = 1070
これらの値が Graph Server に送信される。Graph Server は送信された値から最大値を排除し、残りの値の平均値を計算する:
この値が記録され、グラフとして表示される。しかし、この例からも分かるように、最終的な値はパフォーマンスの非常に粗い推定値でしかない。さらに、この値からレグレッションが検出されたとしても、そこから問題が発生したページを特定するのは非常に難しい。
この 100 ページテストが報告する値に含まれるノイズを取り除く作業に私たちは取り組んだ。このテストはページの読み込み時間を測定するので、まずはキャッシュといったシステム内の影響からテストを隔離する必要があった。そこで、全てのページを一つずつ読み込む処理を何度か繰り返すのではなく、同じページを何度か読み込む処理を全てのページに対して繰り返すことでページがキャッシュされるように変更した。このアプローチはユーザーがウェブをブラウズする動作を忠実にトレースしているわけではないものの、記録されるデータのノイズは削減される。
また、一つのページにつき 10 個のデータ点は残念ながら十分ではなかった。様々なサンプルサイズにおけるページ読み込み時間の標準偏差を測定したところ、最低でも 20 回のページ読み込みが必要だと判明した。さらに実験は続き、最終的には「ページを 25 回読み込み、最初の 5 回は無視する」という方法に落ち着いた。具体的には、私たちはページ読み込み時間の標準偏差を調査することで、ノイズの 95% が最初の 5 回の読み込みから生じていることを発見した。なお、最初の 5 個のデータ点は利用されないものの、保存はされる。
こうした実験から、Talos が行うデータ収集における新しい要件が定められた:
- 収集されたデータ (生データ) を全てデータベースに保存する。
- 一つのテストは最低でも 20 個の意味あるデータ点を収集する。
- あるページでの改善が別のページでレグレッションを起こす状況を検出できるように、レグレッションはページごとに判定する。ページ全体で平均は取らない。
- テストは一人の開発者によって「所有」され、どのようなデータをなぜ収集するかを説明するドキュメントを持つ。
- テストが完了して結果を報告するとき、任意のページで起こったレグレッションを検出できる。
こういった要件を Talos が関係するシステム全体に適用するのは正しいことではあるものの、Talos 周辺で成長したエコシステムを新しいモデルに切り替えるには相当な労力が必要となるように思われた。ここで、私たちはシステムをリライト (書き直し) するかリファクタリングするかの選択を迫られた。
リライト vs リファクタリング
以上の調査によって Talos で変更が必要な箇所が明らかになると、これから行うのが根本的な変更だと私たちは確信した。しかし、Talos に加えられてきた変更はどれも「数値を狂わせる」ことを恐れていた: Talos の様々な部分は善意を持ったコントリビューターによって長い時間をかけて形作られてきた。彼らの変更は当時は理にかなったものだったとしても、ツールチェインの方向性が定まっていなかったりドキュメントが不足していたりしたために、Talos の多くの部分はテスト・改変・理解が難しいコードのパッチワークと化していた。
コードベース中に存在する未ドキュメントのダークマターへの恐怖、そして完全に書き直すと新しい測定値と古い測定値が合致することを検証しなければならない問題があったので、私たちは Talos と Graph Server をインプレースに書き換えるリファクタリングを開始した。しかし、データベーススキーマを大規模に再設計しなければ、パフォーマンステストで収集された生データを全て Graph Server のシステムに格納できないことがすぐに明らかになった。さらに、Graph Server のバックエンドに新しく提案された統計的手法を組み込む綺麗な方法も分からなかった。そこで私たちは Graph Server のリライトを決定し、Datazilla という新たなプロジェクトを開始した。Graph Server をフォークしてパフォーマンステストの自動化に使用していたオープンソースプロジェクトもあったので、この判断は軽々しく下せたわけではない。Talos のテストハーネスに関しては、完全に新しく書かれたプロトタイプが作成された。このプロトタイプは単純なテストを実行でき、以前のものに比べて約 2000 行短かった。
Graph Server のリライトを進めているとき、私たちには Talos の新しいテストハーネスのプロトタイプに関する心配事があった。それは「テストを古い方式で実行できなくなり、新しいアプローチと古いアプローチを比較できなくなってしまうのではないか」というものだった。これが理由となり、最終的にプロトタイプは破棄され、元々のテストハーネスを少しずつ書き換えつつ、古い Graph Server システムに情報を送信する部分は残すことになった。これは非常に悪い判断だった。別のテストハーネスを新しく開発し、それを古いテストハーネスと比較する手法も開発するアプローチを取るべきだった。
オリジナルのテストハーネスが持っていたデータの流れと新しい (各ページからデータを収集する) データの流れを両方サポートするのは困難だった。ポジティブに考えれば、このためにフレームワーク内部のコードが再構成され、多くの要素がストリームライン化されたと言える。しかし、こういった作業を稼働中の自動化ツールに対して行う必要があったので、継続的インテグレーションに関連する課題がいくつか生じた。
古いコードを全て捨て、テストフレームワーク Talos とデータ報告システム Datazilla を何もない状態から同時に開発した方がずっと良い結果となっていたはずである。特に、稼働中の自動化システムに開発中の Datazilla 用のデータ生成機能を加えるよりも、新しいシステムを個別に書いた方がステージングは格段に簡単になっていただろう。実際のビルドと実際の負荷を使ってテストデータを生成しなければ、新しい設計がスケールするかどうかは分からないと私たちは考えていた。しかし、プロジェクトの完了に予定の半年ではなく一年かかると知っていれば、Talos とデータ収集フレームワークを最初からリライトしたに違いない。
パフォーマンス文化を作る
私たちが取り組むのはオープンソースプロジェクトなので、プロジェクトがどのように進行するかを理解している開発監督は存在しない。そのため、外部の人物やプロジェクトからのアイデアや批判を取り入れることは欠かせない。できる限りの情報を収集して正しい判断を下すには、様々なチームに属する多くの人々からの意見を聞く必要がある。Talos プロジェクトはテストハーネスに取り組む二人の開発者、Datazilla と Graph Server に取り組む二人の開発者、そしてメトリックチームから借りた二人の統計家をメンバーとして始まった。私たちはプロジェクトをボランティアに開放し、多くの新人を Mozilla に招き入れ、加えて Talos のテストや Graph Server を自身のプロジェクトで利用していた開発者にも声をかけた。協力して取り組む中で、ノイズの少ない結果を生成するテストの組み合わせが理解されたので、数人の Mozilla 開発者に協力を求めた。最初のミーティングは当然のことながらスムーズに進まなかった。私たちが提案する変更が非常に大きかったためである。Talos には上述した「ミステリー」があるので、パフォーマンスを重視する開発者の多くは Talos の書き直しに難色を示した。
システム内の大きなコンポーネントを書き直すのが良いアイデアで、「壊れたところだけ直す」ではいけないという重要なメッセージを納得させるのには時間がかかった。既存のシステムに小さい変更を加えていくアプローチを提案するフィードバックが多かったものの、そういった意見を出す人は誰も内部のシステムの動作を理解していなかった。私たちは何度もプレゼンテーションを行い、多くの人々をミーティングに招き、一回限りの特別なミーティングを何度も開き、ブログ記事を書き、お知らせを書き、ツイートをした。情報を広めるためにできることは何でもした。優れたシステムを作るのに大変な労力を費やすことより恐ろしいのは、完成したシステムが使われないことだけである。
Talos のノイズに関する問題が初めてレビューを受けてから一年が経過すると、他の開発者たちは私たちが何をリリースするかに注目し始めた。Talos フレームワークは Datazilla と古い Graph Server の両方にデータを報告できるようにリファクタリングされ、内部構造が綺麗になった。Datazilla が予想される量 (半年で 1 TB) のデータまでスケールすることが確認され、測定結果の解析に使うメトリックの入念な調査も行われた。最もエキサイティングなこととして、レグレッションと改善に関する解析を変更ごとにリアルタイムで Mozilla ツリーに提供できるようになった。これは開発者にとって非常に有用だった。
新しい Talos では、誰かが Firefox に変更をプッシュすると次の処理が行われる:
- 各ページに対して 25 個のデータ点を収集する。
- データ点を全て Datazilla にアップロードする。
- Datazilla が最初の 5 個を除いたデータ点を統計的に解析する (ノイズの 95% は最初の 5 個のデータ点で生じる)。
-
Welch の t 検定1を使って数値が解析され、これまでのトレンドや過去数回のプッシュと比べたときに外れ値となる数値があるかどうかをページごとに検証する。
-
Welch の t 検定の結果を False Discovery Rate フィルタに入力し、ノイズによる偽陽性を検出する2。
-
最後に、トレンドからの乖離が一定の閾値に収まるなら、Datazilla は実験的な平滑化アルゴリズムを実行して新しいトレンド直線を計算する3。もし乖離が閾値を外れるならトレンド直線は計算されず、そのページはパフォーマンステストに失敗したとみなされる。
- テスト全体の成功/失敗は成功したページの割合で決定する。95% のページでテストが成功したならテスト全体も成功とみなされる。
この結果は Talos のテストハーネスにリアルタイムに返され、その後 Talos はパフォーマンスのレグレッションがあったかどうかをビルドスクリプトに伝える。毎分 10–20 個の Talos が上述の処理を完了し、そのとき集計された値の格納と計算は同時に行われる。
以上のアプローチの動作が確認できたとしても、既存のアプローチを置き換えるには Firefox の完全なリリースで両方のアプローチを実行する必要がある。ここではオリジナルの Graph Server が報告するレグレッションの中で本当にレグレッションであるものが Datazilla によっても報告されることが確認される。また、Datazilla はテストスイート単位ではなくページ単位でレグレッションを報告するので、レグレッションを開発者に報告する UI にも変更が必要になる。
今考えると、古い Talos テストハーネスを完全に置き換えるアプローチを取った方が開発自体は速く完了していただろう。しかし、リファクタリングを通して Mozilla は Talos プロジェクトに多くの新しいコントリビューターを迎えることができた。また、リファクタリングによってテストの理解が深まり、壊れていたテストの修正や意味のないテストの削除につながった。つまり、リライトとリファクタリングのどちらが優れるかを議論するとき、開発にかかる時間はメトリックの一つでしかない。
結論
昨年、私たちは Mozilla における自動パフォーマンステストの全ての部分を詳しく調査した。テストハーネス、結果の報告ツール、計算される結果の統計的妥当性が調査され、その調査で判明した事実を使って Talos フレームワークの管理、セットアップ、実行を簡単にする作業がその年を通じて行われた。まず、Talos などのパフォーマンステスト自動化システムから任意のパフォーマンスメトリックを収集できる拡張可能なシステム Datazilla が開発された。次に、パフォーマンスの統計的解析手法を見直し、プッシュごとに実行できて統計的に意味のあるレグレッション/改善の検出機構が作成された。こういったシステムはオープンにされ、全てのコントリビューターがコードを確認し、ときにはパフォーマンスのデータに対する新しい統計的解析手法を試すことができるようにされた。プロジェクトのマイルストーンごとにデータのレビューを欠かさず行い、決定的でないデータや正当でないデータを削除したおかげで、私たちは Talos の改修という大規模なプロジェクトを前に進めることに集中できた。Mozilla 内の異なるチームに属する人々や新たなボランティアの参加は、労力を正しい方向に向ける上で役立った。さらに、Mozilla が取り組んでいる様々な領域でパフォーマンス監視とデータ解析が復活し、データ駆動とパフォーマンスを重視する文化がさらに強まる結果となった。
-
https://github.com/mozilla/datazilla/blob/2c369a346fe61072e52b07791492c815fe316291/vendor/dzmetrics/ttest.py. ↩︎
-
https://github.com/mozilla/datazilla/blob/2c369a346fe61072e52b07791492c815fe316291/vendor/dzmetrics/fdr.py. ↩︎
-
https://github.com/mozilla/datazilla/blob/2c369a346fe61072e52b07791492c815fe316291/vendor/dzmetrics/data_smoothing.py. ↩︎