BLOG.tass.io

大規模プロジェクトでの依存関係アップデート

2015-05-30

Googleのエンジニア Patrik Höglundさんが、Chromiumでの複数リポジトリ開発について記事を書いておられました。

Chromiumでは、依存しているサードパーティライブラリが100を超えており 、とんでもなく複雑です。それぞれのライブラリの依存関係を維持しつつ、バグフィックスやフィーチャを取り入れながらアップデートしていく事は、それはもう大変な作業です。 そこで、依存関係のアップデート(Googleでは"Rolling"と呼ぶ)と、それを専属で行うボット(FYIボット)を使っての開発について解説してくれています。

元記事

備忘録的に、以下にざっくり書き出してみます。

依存関係のアップデートは難しい!

前述のとおり、Chromiumは非常に多くのライブラリに依存しています。レンダリングエンジンのBlink、2D描画ライブラリのskia、リアルタイム通信ライブラリのWebRTCなど、活発なプロジェクトばかりです。Chromium本体は勿論のこと、それらのライブラリもバグフィックスや新たな機能の追加など、日々コードが修正されています。

これらの依存ライブラリのアップデートは頻繁に必要な上に、非常に重労働でもあります。

アップデートすることでアプリケーションを壊してしまわないか、綿密なテストに時間を費やさなければなりませんし、もしインターフェースが更新されたらコードも修正しなければなりません。とはいえ、長い間アップデートせずに放置してしまうと、コードの乖離が大きくなり、より更新が難しくなっていきます。また、依存関係のアップデートを行わない事はセキュリティ上のリスクも大きいですし、何より新たな機能を導入することができません。

the “Rolling”

Googleでは、依存関係をアップデートする作業を"Rolling"と呼んでいます。

Chromiumでは、複雑で巨大な依存関係を DEPSと呼ばれる テキストファイルで依存関係を管理しています。(余りに複雑過ぎるので、git submodule等では無理らしい) 例えば下記のように、WebRTCの参照リポジトリとcommitハッシュが書かれています。

{
  # ...
  'src/third_party/webrtc':
      'https://chromium.googlesource.com/' +
      'external/webrtc/trunk/webrtc.git' +
      '@' + '5727038f572c517204e1642b8bc69b25381c4e9f',
}

“Rolling"はまず、このDEPSファイルの新しいバージョンをつくるところから始まります。 ただし、依存関係をアップデートした後、ビルドして、全てのテストを走らせるまで本当に更新が成功したか知ることはできません。

"Rolling"パッチは他のパッチに比べ、より苦痛を伴うリスキーなパッチです。

FYIボットの運用

依存関係のアップデートは放置しておくと後が大変、しかし頻繁にやるには過酷すぎるということで、"Rolling"パッチを作って試すための専用ボットが作られました。それが「FYIボット」です。

開発者がわざわざ手動でパッチを作成して試すのではなく、まずはFYIボットに試してもらおうというわけです。

FYIボットはDEPSファイルの更新を受けて、Windows/OSX/Androidなど様々なプラットフォーム向けにビルドを行い、テストを実行します。

  • 全てのFYIボットが✅グリーン(テスト成功)ならば、この依存関係のアップデートはスムーズに作業できる(はず)
  • FYIボットが🚫コンパイルに失敗した場合、インターフェースの変更が必要
  • FYIボットが❌レッド(テスト失敗)であれば、何かしらのバグを持っている可能性があり、コードの修正が必要

段階的なインターフェース変更

これで"Rolling"の負担は軽減されましたが、それだけでは充分ではありませんでした。FYIボットが毎日警報を出し続け、むしろ作業は大変になってしまったのです。

そこでチームは、より慎重な更新ポリシーを採用することにしました。

インターフェースを更新するとき、いきなり旧インターフェースから新インターフェースに置き換えるのではなく、 新旧2つのインターフェースが存在する中間バージョンを作成し、呼び元が置き換えたのを確認してから、新旧を入れ替えて、旧インターフェースを削除するようにしました。

例えば、以下のようなインターフェースがあったとします。

class WebRtcAmplifier {
  ...
  int SetOutputVolume(float volume);
}

ここで、以下のようにインターフェースを変更したいと思いました。しかしいきなりインターフェースを書き換えてしまうと、WebRTCに依存しているChromiumのコンパイルに失敗してしまい、FYIボットが警報を発してしまいます。

class WebRtcAmplifier {
  ...
  int SetOutputVolume(float volume, bool allow_eleven1);
}

そこで、まずは新旧両方のインターフェースを準備した状態でリリースし、置き換えが完了した後に旧インターフェースを除去するよう段階的で、より慎重な更新方法にしたのです。

class WebRtcAmplifier {
  ...
  int SetOutputVolume(float volume);
  int SetOutputVolume2(float volume, bool allow_eleven1);
}

「FYIボットは原則的に常にグリーンであること」をルールとし、それを破るcommitはすぐにロールバックするようにしました。常時グリーンを維持することで精神的な安心感を得た上で、本当に重要なエラーを見逃さないようになりました。

結果

結果、大きなパッチはなくなり、小さなパッチを気軽に運用するようになりました。

FYIボットを運用した結果、もう1つの利点として、よりきめ細やかにパフォーマンステストが行われるようになったことがあります。大きなパッチでは、何が原因でパフォーマンスが下がったのか調べるのは大変です。FYIボットのおかげでより細かくテストが実行されるようになり、より楽に問題となったcommitを発見できるようになりました。

今後の取り組み:楽観的自動Rolling

今後やりたいのは、依存関係の自動アップデートです。既にBlinkチームは、Blink AutoRollBotを開発し、運用しています。WebRTCチームもこれに続きたいと考えています。


ざっくりですが、元記事のまとめは以上です。

Chromiumほど巨大なプロジェクトは稀ですが、依存関係のアップデートだけでもかなり大変なことが伝わってきます。できるだけ自動化して負担を減らした上で、より安全に運用できるようルールを定めるというのは大事ですね。

また、Googleでは当然のことですが、テストコードがあることが大前提なんですよね。テスト書こうずら!(^◇^)


Michael Kuroneko

Written by Michael Kuroneko. Follow me on twitter, github.