Bitbucketの差分およびマージ時のパフォーマンスについて

本ブログは、こちらの英文ブログの翻訳です。万が一、内容に相違がある場合は、原文が優先されます。

7月に公開した記事で、Bitbucket Cloudを、データセンターからアトラシアンのクラウドプラットフォームに移行中であることを初めてお知らせしました。この作業は終盤に差し掛かっており、完了すれば、Jira CloudやConfluence Cloud、Statuspageをはじめとする、その他アトラシアンクラウド製品やサービスと共通の基盤の上で稼働することになります。

この移行の過程において、特定の操作に関して遅延が見受けられ、特にdiffのレンダリングやプルリクエストのマージにおいて重大なパフォーマンスの低下が確認されていましたが、この1か月に実装された多くの改善についてお知らせします。要点は次の通りです:

  1. 7月中旬に最適化を実施し、差分の返答時間が40%改善され、タイムアウトの発生頻度も大幅に減少しました。
  2. この1か月にデプロイした変更により、トラフィックのピーク時におけるマージ開始から終了までの時間が、合計で30秒以上、短縮されました。

差分の応答時間

7月の記事では、次のようにお伝えしました:

diffとdiffstatエンドポイントを最適化するために、プルリクエストの作成中に先回りしてdiffを生成し、キャッシュするソリューションを実装しました。プルリクエストを表示すると、diff、diffstat、および競合情報をキャッシュから取得し、ファイルシステムをバイパスして、遅延の増加を回避します。

Bitbucket規模のWebサービスの場合、このような課題を解決しても、想定どおりにならないことがあります。アトラシアンのエンジニアは、問題解決に取り組みながら、ローカルメモリで差分を生成するのに必要な作業をより多く処理できる最適化の糸口を発見し、ファイルシステムのI/Oを大幅に削減しました。この変更は7月13日に公開され、diffおよびdiffstat APIの平均応答時間が40%近く短縮されています。

diff APIの応答時間の推移
diff APIの応答時間の推移
diffstat APIの応答時間の推移
diffstat APIの応答時間の推移

上のグラフは、これらのAPIの平均応答時間を示しています。90および99パーセンタイルでの改善はさらに劇的で、レンダリングに最も時間を要していた差分(応答時間が長い上位1%)である99パーセンタイルでは、150%以上速くなりました。この効果は、これらAPIからの5xx応答(主にタイムアウト)の割合を見ると明らかで、桁違いに低下しています。

diffおよびdiffstat APIエンドポイントのからタイムアウトが減少
diffおよびdiffstat APIエンドポイントのからタイムアウトが減少

では、7月にお伝えしたキャッシュのソリューションはどうなったのかというと、まだ有効にしていません。上記の最適化により、差分の応答時間はデータセンターを利用していた頃と同等になりました。応答時間をさらに改善すべく、キャッシュのソリューションの作業も完了する予定ですが、先にユーザーにとって次に影響の大きい課題である、プルリクエストのマージ時間の改善に取り組むことにしました。

プルリクエストのマージ実行時間

早い段階で、特定のお客様に対して低レベルのプロファイリングを有効にしてマージを実行するコードを導入し、詳細なプロファイルデータを保存してエンジニアが調査できるようにしました。これにより、最適化の取り組みで注力すべきポイントを特定することができたので、その一部を以下に説明します。

フック受け取り前後の検証

マージを効率化するために、プロファイルデータに基づいて特定した最初の領域の1つは、プルリクエストのマージを含む、プッシュごとにバックエンドで実行する一連のフックでした。こうしたフックは、ブランチ権限やマージチェックといったセキュリティ機能の施策を扱うため、重要なものがあります。しかし、実行されるすべてのフックを確認したところ、スピード向上のために、マージ実行の同期フローから削除できそうなフックをいくつか特定できました。

以下にお見せするグラフはすべて、改善を説明するために一番深刻なケースとなっています。 毎日、Bitbucketは数百万ものGit操作を処理し、その大部分はこれよりはるかに高速に行われます。ただし私たちは、こうした深刻なケースが、ユーザーにとってインパクトの大きいポイントであると考え、解決に力を注ぎました。

一例として、極端な場合、実行に最大50秒かかり得ることが判明したフックを見つけたのですが、これは、古い参照用のロックファイルを検出し、削除するためのクリーンアップコードでした。そこで、同期処理からこのフックを除外することで、一部のマージを大幅に高速化しました。

他に発見した同様のフックは、一定の割合のプッシュで参照を集めてパックするものでした。これは、ブランチやプルリクエストの一覧表示といった、リポジトリ参照の繰り返し処理が必要となる特定の機能を高速化するために、先回りする方法です。このタスクを、プッシュの完了後に非同期で実行されるバックグラウンドフックに移動したことで、マージを1分以上、短縮することができました。

また、他にもこの領域で行った改善があります。たとえば、ウェブサイトから開始されたマージのためにターミナルへのPRを開くリンクを表示するフックは、これらのリンクはユーザーには表示されないため、削除しました。いずれにせよ、前述した2つの対応は、マージ時間の改善に大きなインパクトがありました。

ロックによる操作の同期

私が以前指摘した、遅延のもう1つの原因は、データの一貫性を保護するために、リポジトリを変更する特定の操作を同期するロックの多用です。実際、こうしたロックの一部は、BitbucketがGitとMercurialの両方をサポートしていた時期にまでさかのぼります。Mercurialのユーザーには必要でしたが、Gitには一貫したデータを保つためのより強力なメカニズムが組み込まれており、これを活用できます。

マージコードをリファクタリングし、過度のロックを削除した後、古い実装と新しい実装のパフォーマンスの違いを測定したところ、最大3秒の改善を確認されました。

古い実装と新しいロックフリーな実装のパフォーマンス比較
古い実装と新しいロックフリーな実装のパフォーマンス比較

バックグラウンドタスクの処理の遅延

前述した改善点の特定と実装に取り組みながら、見落とした可能性のある遅延の原因を特定すべく、メトリクスの確立に取り組みました。そして、目を見張る発見がありました。

特に遅延の著しかったいくつかのマージ操作の詳細なログを確認したところ、バックグラウンドタスクをスケジュールおよび実行するためのシステムから、タスクの受信と完了のタイムスタンプの間に、ランタイムでは説明できない大きな不一致があることがわかりました。下のグラフが示すように、この不一致はトラフィックのピーク時に不自然なレベルにまで増え、タスクの残り実行時間が数秒のまま、実測時間は30秒を超えた期間を確認しました。

これは起こり得る現象です。タスクがキューに溜まっていたり、ワーカーによってプリフェッチされたものの、実行に時間がかかっている他のタスクによって遅延することで、表示される実行時間と実測時間に差異が発生することがあります。ただし、メトリクスによると、キューは溜まっておらず、プリフェッチは無効となるようにワーカーは構成されていました。

最終的に、バックグラウンドタスクのスケジュールと実行のために、サービスで使用していたライブラリのバグにより、多数のワーカーがタスクを実行せずにアイドル状態になっていることがわかりました。主となるオーケストレーターの処理は、メッセージのバックログを防ぐのに十分な速さでキューから消費していましたが、これらのタスクは、人為的に小さなプールに割り当てられており、効果的な並列化が妨げられていました。このバグは、データセンターで実行されているサービスには影響せず、構成の微妙な違いから、新しいクラウドプラットフォームへの移行した後に影響が出始めました。

詳細なテストを通じて、プルリクエストのマージを実行するサービスの構成における違いを特定し、このバグを回避することで、意図した動作が復元されました。これにより、表示される実行時間と実測時間の差が十分に小さく(通常は1秒未満内)なりました。

まもなく移行完了

今回の差分とマージにおけるパフォーマンスの問題については、解決を待たずに、常に進捗を共有したいと考えてきました。この記事も、優秀なエンジニアリングチームが継続的に最適化の糸口を見つけ、実装を続けたため、何度も編集しました。現時点でのBitbucketのパフォーマンスは、全体的に、データセンターのパフォーマンスと同等かそれ以上になっています。

そして今、楽しみにしているのは、お客様のリポジトリの最後のバッチをクラウドストレージレイヤーに移動し、移行が完了した後、最後の進捗を共有することです。この作業は、最もアクティブなストレージボリューム上のものを含む、リポジトリの約85%まで完了しています。今月末には、Bitbucket Cloudは完全にデータセンターを離れ、クラウドインフラストラクチャで動作することになるでしょう。これにより、スケーラビリティやセキュリティが大幅に向上されます。

最後に、開発者コミュニティの皆さんが、日々、Bitbucketを使い続けてくださることに感謝いたします。チームがコラボレーションし、高品質のソフトウェアを構築、テスト、および提供できるようにすることが、私たちの任務です。お礼を申し上げると共に、今後のアップデートを楽しみにお待ちいただければ幸いです。