GitHub における大規模なモノリポのパフォーマンスの向上

Image of Scott Arbeit

GitHub は、毎日 5600 万人以上の開発者にサービスを提供し、2 億以上のリポジトリをホストしています。これらのリポジトリのごく一部を除いて、世界中の顧客に驚くべきパフォーマンスでサービスを提供しています。

GitHub のような大規模なシステムでは、コードとアーキテクチャのずれというのは限界に達したときに初めて見つかるものです。例えば、何千人もの開発者が毎日同じリポジトリを更新するといったケースです。GitHub は、大規模なモノリポを使用する一部の顧客から、プッシュ操作が失敗するといったパフォーマンスの問題が発生しているというフィードバックを受けました。

そして、それは GitHub においても発生していました。 github/github は GitHub のモノリポですが、私達自身も時々プッシュに失敗することがありました。

調査を開始するにあたり、私たちは社内のチームや顧客と協力して、彼らの GitHub の使用方法を理解して最適化しました。その活動では、複数のプッシュを単一のプッシュにまとめて、書き込みをロックするトランザクションの数を減らしたり、不必要なプッシュ操作をなくすために DevOps プロセスの一部を再構築したりしました。それでも、異常なレベルのプッシュ失敗が発生していました。

これを受けて、GitHub 開発チームは Project Cyclops を立ち上げました。 Project Cyclops は、Git システム部門(Git Storage、Git Protocols、Git Client の各チーム)と、GitHub の upstream にいる Git コントリビューターやウェブフロントエンドチームのエンジニアと協力して、数か月かけてこれらの問題の解決策を探りました。多くの協力と努力を重ね、何度も改良を重ねた結果、 何千人ものコントリビューターを抱える最大規模のモノリポの顧客であっても、プッシュエラーをほぼゼロにすることができました。

改善内容

Project Cyclops では、モノリポのプッシュパフォーマンスを改善するために様々なアプローチを行いました。これらを組み合わせることで、プッシュトラフィックを処理する能力が少なくとも一桁向上しました。

リポジトリのメンテナンスの改善

デフォルトでは、GitHub は 50 回の git push 操作の後、あるいは 40MB のパックされていないファイルを受け取ると、リポジトリのメンテナンスを実行します。この処理では、クローンやフェッチのパフォーマンスを高めるために最新のパックファイルを用意したり、リポジトリ内のデータを整理して重複をなくします。リポジトリのサイズにもよりますが、メンテナンスにかかる時間は数秒から数分程度です。

多くの開発者を抱える大規模なモノリポでは、50 回のプッシュ操作が実行されるのに大した時間がかからないため、開発者がまだリポジトリに変更をプッシュしている間にも頻繁にメンテナンスが行われます。このようなリポジトリの中には、最大時間内にメンテナンスを完了できなかったものが多数ありました。メンテナンスに失敗すると、プッシュと参照更新の両方でパフォーマンスが低下し、私たちのエンジニアが再びリポジトリを整理するために手作業で対応することになります。 このようなメンテナンスの失敗はほぼゼロになりました。 具体的には、 git repack の改善と、メンテナンスの再試行のスケジューリング方法の改善を行いました。

git repack の高速化

リポジトリのメンテナンスの際には、git repack を実行して緩いオブジェクト(loose objects)を圧縮し、クローンやフェッチの高速化に備えます。

オブジェクトの集合をひとつのパックにまとめるために、Git は互いに関連するオブジェクトのペアを探そうとします。オブジェクトのすべての内容をそのまま保存するのではなく、いくつかのオブジェクトは他の関連するオブジェクトとの差分として保存されます。これらの差分を見つけるには時間がかかり、すべてのオブジェクトを他のすべてのオブジェクトと比較することはすぐに実現不可能になります。Git はこの問題を解決するために、パックされているすべてのオブジェクトの配列上のスライドウィンドウの範囲内で差分/元データのペアを検索します。

ウィンドウ内の差分候補の中には、ヒューリスティックな手法ですぐに却下できるものもありますが、中には CPU 負荷の高い比較を必要とするものもあります。

そこで、高価な比較を行う回数を制限するパラメータを実装しました。この値を調整することで、git repack の際に費やす CPU 時間を短縮しつつ、結果として得られるパックファイルのサイズはわずかな増加に留めることができました。この変更により、メンテナンス時の失敗がほぼすべてなくなりました

偽の失敗と再試行の頻度

Project Cyclops 以前は、あるリポジトリが何らかの理由でメンテナンスに失敗すると、そのリポジトリに対して7 日間メンテナンスを再実行しないようにスケジュールしていました。長年、このリズムは私達の顧客にマッチしていましたが、近年のモノリポではそれほど長く待てません。私たちは、特定のリポジトリのメンテナンス失敗(メンテナンス中に大量のプッシュトラフィックが発生した場合など)に対応するために、新たに 偽の失敗(spurious-failure) という状態 を導入し、4 時間ごとにメンテナンスを最大 3 回まで再試行できるようにしました。これにより、プッシュが少ないお客様のオフタイムに再試行できるようになります。この変更により、 残っていたメンテナンスの失敗がなくなり 、オンコールエンジニアの負担が減りました。

人為的な制限の撤廃

私たちのファイルサーバー(実際に git リポジトリを保管するサーバー)では、何年も前からパラメーターを設定して、それぞれのサーバーで処理するプッシュ操作の割合を制限して実行を遅らせていました。GitHub はマルチテナント型のサービスです。もともとこのパラメータは、ある顧客からの書き込みがサーバーのリソースを独占してしまい、そのサーバーを使っている他の顧客からのトラフィックに支障をきたすことがないようにするためのものでした。つまり、サーバーの処理能力に 人為的な上限 を設けていたのです。

このパラメータの値を少しずつ上げていき、100%のプッシュ操作を即時に実行できるようにしたところ、現在のアーキテクチャで十分なパフォーマンスが得られることがわかり、制限値を上げただけでなく、コードからそのパラメータを削除しました。これにより、私たちのモノリポのパフォーマンスが一気に向上し、プッシュに関連する多くのエラーがなくなりました

チェックサムの事前計算

GitHub はデフォルトでサーバーやラック、ネットワーク、データセンターの各レベルでの障害に備えて、それぞれのリポジトリに対して 5 つのレプリカを 3 つのデータセンターにまたがって書き込みます。Git の参照を更新する際には、すべてのデータセンターにあるすべてのレプリカを短時間ロックし、3 フェーズコミット(3PC)プロトコルが成功したときにロックを解放します。

このロックの間に、各レプリカのチェックサムを計算し、それらが一致していること、そしてすべてのレプリカが同期していることを確認します。インクリメンタルチェックサムを使用してこの処理を高速化しており、通常の運用では 50ms 以下で完了しますが、修復作業ではチェックサムを最初から計算し直すため、より長い時間がかかります。大きなモノリポの場合、ロックが 20〜30 秒も保持されていました

そこで、ロックをかける前に、これらのレプリカのチェックサムを計算するように変更しました。チェックサムを事前に計算することで、ロックする時間を 1 秒以下に短縮し、より多くの書き込み操作を即座に成功させることができるようになりました。

お客様の声

大規模なモノリポを使用している顧客の中には、git のパフォーマンスに関する独自の内部メトリクスを計測しています。そして、そのメトリクスによると彼らのプッシュ操作は何ヶ月も失敗しておらず、私たちが行った改善の結果を表しています。

失敗が多すぎると感じていた別のモノリポを使用している顧客は、プッシュの成功率を向上させるために、新しいリポジトリに移行し、リポジトリ内の参照数を最小限に抑える計画を立てていました。今回の変更を受けて、私たちの計測では彼らのプッシュの失敗はほぼ 0 になり、実際に 12 月に彼らの開発者を対象に調査したところ、最近プッシュに失敗したという報告はまったくありませんでした。彼らは移行を取りやめ、引き続き素晴らしいパフォーマンスを実感しています。

グラフが見たいですか?以下は、これらのお客様のグラフで、私たちが修正を行ったことでプッシュの失敗がほぼゼロになったことを示しています。

次のステップ

先に述べたように、プッシュの失敗はほぼゼロになりました。これらの失敗のうちのいくつかは、インターネットネットワークのランダムな問題が原因で、私たちの手に負えないものです。残りの失敗については、可能な限り悩ましい失敗をなくし、GitHub をより速くする方法を検討しています。

Git システムの世界では、ストレージのハードウェアを刷新して高速化を図っています。また、GitHub の有名な Ruby モノリスを解体し、GitHub のすべてのユーザーのためにリポジトリのパフォーマンスを向上させる新しいマイクロサービスを Go で作成しています。

成果

Project Cyclops は、大規模なモノリポを使用している顧客のパフォーマンスを向上させ、障害をなくし、ファイルサーバーの CPU サイクルの無駄を省き、最大の顧客だと何千人もの開発者の GitHub 使用体験を大幅に改善しました。ここには GitHub Enterprise Server を利用している顧客も含まれます。

またこれは、GitHub を利用するすべての人にとっても、小さいながらも顕著な改善です。

私たちは単一リポジトリの更新処理のトラフィックを少なくとも 1 桁改善しました。これで現在のアーキテクチャにおいて、最大規模のモノリポの成長にも対応できるだけの余裕が生まれました。

Special thanks

私たちは、GitHub をより良くするために協力してくれたモノリポを使用しているお客様に、本当に感謝しています 💚 問題を報告してくださったり、時には診断してくださったりすることで、問題を解決することができました。✨ 本当にありがとうございます! ✨