Upgrading GitHub.com to MySQL 8.0

GitHub.com を MySQL 8.0にアップグレード

Image of Ishikawa Setsuna

GitHubは、15年以上前に単一のMySQLデータベースを持つRuby on Railsアプリケーションとしてスタートしました。それ以来、GitHubは高可用性を構築するテスト自動化を実装するデータのパーティショニングを行うなど、プラットフォームのスケーリングと可用性のニーズを満たすために、MySQLアーキテクチャを進化させてきました。今日、MySQLはGitHubのインフラストラクチャの中核を担い、選択可能なリレーショナルデータベースの一部です。

このブログは、1200台以上のMySQLホストを8.0にアップグレードした物語です。私たちのサービスレベル目標(SLO)に影響を与えることなくフリートをアップグレードすることは小手先の技で済むようなものではありませんでした。計画、テスト、そしてアップグレード自体は1年以上かかり、GitHub内の複数のチームが協力しました。

アップグレードの動機

なぜMySQL 8.0にアップグレードするのか?MySQL 5.7が寿命に近づいているため、私たちはフリートを次のメジャーバージョンであるMySQL 8.0にアップグレードしました。私たちは、最新のセキュリティパッチ、バグ修正、およびパフォーマンス向上を受け取るMySQLのバージョンを使用したいと考えています。8.0には、インスタントDDL、目に見えないインデックス、圧縮されたバイナリログなど、テストして新機能の恩恵を受けたいものもあります。

GitHubのMySQLインフラストラクチャ

アップグレード方法について詳しく説明する前に、MySQLインフラストラクチャを10,000フィートの視点から見てみましょう:

  • 私たちのフリートは1200台以上のホストで構成されています。これは、Azure仮想マシンとデータセンターの物理ホストの組み合わせです。
  • 私たちは300TB以上のデータを保存し、50以上のデータベースクラスターを横断して秒間550万クエリを処理しています。
  • 各クラスターは、プライマリプラスレプリカクラスターセットアップで高可用性のために構成されています。
  • 私たちのデータは分割されています。私たちは、MySQLクラスターをスケールするために、水平および垂直のシャーディングの両方を活用しています。特定の製品ドメイン領域のデータを保存するMySQLクラスターを持っています。また、単一プライマリMySQLクラスターを超えて成長した大規模ドメイン領域のために、水平にシャーディングされたVitessクラスターも持っています。
  • Percona Toolkit、gh-ostorchestratorfreno、および社内自動化を含む、大規模なツールエコシステムを持っています。これらはフリートを運用するために使用されます。

これらすべてが、SLOを維持しながらアップグレードする必要がある多様で複雑な展開をまとめています。

旅の準備

GitHubの主要なデータストアとして、私たちは可用性に対して高い基準を持っています。MySQLインフラストラクチャのサイズと重要性のため、アップグレードプロセスにはいくつかの要件がありました:

  • 私たちは、サービスレベル目標(SLO)およびサービスレベル契約(SLA)を遵守しながら、各MySQLデータベースをアップグレードできなければなりません。
  • 私たちは、テストおよび検証段階ですべての障害モードを考慮することはできません。そのため、SLO内に留まるためには、サービスの中断なしに以前のバージョンのMySQL 5.7にロールバックできる必要がありました。
  • 私たちのMySQLフリート全体で非常に多様なワークロードがあります。リスクを軽減するために、各データベースクラスターをアトミックにアップグレードし、他の主要な変更を周囲にスケジュールする必要がありました。これはアップグレードプロセスが長期にわたることを意味していました。したがって、最初から、混在バージョン環境を維持できる必要があることを知っていました。

アップグレードの準備は2022年7月に始まり、単一の本番データベースをアップグレードする前に到達する必要があるいくつかのマイルストーンがありました。

アップグレードのためのインフラストラクチャの準備

私たちは、MySQL 8.0の適切なデフォルト値を決定し、いくつかのベースラインパフォーマンスベンチマークを実行する必要がありました。私たちは2つのバージョンのMySQLを操作する必要があるため、ツールと自動化は混在バージョンを処理できる必要があり、5.7と8.0の間で新しい、異なる、または非推奨の構文を認識する必要がありました。

アプリケーションの互換性を確保する

私たちは、MySQLを使用するすべてのアプリケーションの継続的インテグレーション(CI)にMySQL 8.0を追加しました。私たちはCIでMySQL 5.7と8.0を並行して実行し、長期にわたるアップグレードプロセス中にリグレッションがないことを確認しました。CIでさまざまなバグと非互換性を検出し、サポートされていない構成や機能を削除し、新しい予約済みキーワードを回避しました。

アプリケーション開発者がMySQL 8.0への移行を支援するために、GitHub CodespacesでMySQL 8.0のプリビルトコンテナを選択するオプションを有効にし、追加のプレプロダクションテストのためにMySQL 8.0開発クラスターを提供しました。

コミュニケーションと透明性

私たちは、アップグレードスケジュールを社内でコミュニケーションし、追跡するためにGitHub Projectsを使用しました。アプリケーションチームとデータベースチームがアップグレードを調整するためのチェックリストを追跡した問題テンプレートを作成しました。

Project Board for tracking the MySQL 8.0 upgrade schedule
Project Board for tracking the MySQL 8.0 upgrade schedule

アップグレード計画

私たちの可用性基準を満たすために、私たちはプロセス全体を通じてチェックポイントとロールバックを可能にする段階的なアップグレード戦略を持っていました。

ステップ1: ローリングレプリカアップグレード

私たちは、単一のレプリカをアップグレードして開始し、基本的な機能が安定していることを確認するためにオフラインのまま監視しました。次に、製品トラフィックを有効にし、クエリのレイテンシ、システムメトリクス、およびアプリケーションメトリクスを監視し続けました。8.0レプリカを徐々にオンラインにして、データセンター全体をアップグレードし、次に他のデータセンターを反復処理しました。ロールバックするのに十分な5.7レプリカをオンラインに残しましたが、すべての読み取りトラフィックを8.0サーバーを介して提供するために、製品トラフィックを無効にしました。

The replica upgrade strategy involved gradual rollouts in each data center (DC).
The replica upgrade strategy involved gradual rollouts in each data center (DC).

ステップ2: レプリケーショントポロジーの更新

すべての読み取り専用トラフィックが8.0レプリカを介して提供されるようになったら、次のようにレプリケーショントポロジーを調整しました:

  • 8.0のプライマリ候補が、現在の5.7プライマリの直下にレプリケートするように構成されました。
  • その8.0レプリカの下流には2つのレプリケーションチェーンが作成されました:
    • トラフィックを提供していないが、ロールバックの場合に備えて準備ができている5.7レプリカのみのセット。
    • トラフィックを提供している8.0レプリカのみのセット。
  • トポロジーは次のステップに移るまでの短い期間(最大数時間)だけこの状態にありました。
To facilitate the upgrade, the topology was updated to have two replication chains.
To facilitate the upgrade, the topology was updated to have two replication chains.

ステップ3: MySQL 8.0ホストをプライマリに昇格する

私たちは、プライマリデータベースホストで直接アップグレードを行うことを選択しませんでした。代わりに、Orchestratorで実行される穏やかなフェイルオーバーを通じて、MySQL 8.0レプリカをプライマリに昇格させることにしました。その時点で、レプリケーショントポロジーは、ロールバックの場合のオフラインの5.7レプリカセットと、提供されている8.0レプリカセットの2つのレプリケーションチェーンが付属している8.0プライマリで構成されていました。

Orchestratorはまた、予期しないフェイルオーバーの場合に誤ってロールバックすることを防ぐために、5.7ホストを潜在的なフェイルオーバー候補として拒否リストに登録するように構成されました。

Primary failover and additional steps to finalize MySQL 8.0 upgrade for a database
Primary failover and additional steps to finalize MySQL 8.0 upgrade for a database

ステップ4: 内部向けインスタンスタイプのアップグレード

私たちは、バックアップや非本番ワークロードのための補助サーバーも持っています。これらは一貫性を維持するために順次アップグレードされました。

ステップ5: クリーンアップ

クラスターがロールバックする必要がなく、8.0に成功裏にアップグレードされたことを確認した後、5.7サーバーを削除しました。検証には、ピークトラフィック中に問題がないことを確認するために、少なくとも1つの完全な24時間のトラフィックサイクルが含まれていました。

ロールバックの能力

私たちのアップグレード戦略を安全に保つための中核部分は、MySQL 5.7の以前のバージョンにロールバックする能力を維持することでした。リードレプリカの場合、十分な5.7レプリカをオンラインに保ち、製品トラフィック負荷を処理し、8.0レプリカがうまく機能していない場合にはそれらを無効にすることによってロールバックを開始しました。プライマリの場合、データ損失やサービス中断なしにロールバックするためには、8.0と5.7の間で後方データレプリケーションを維持できる必要がありました。

MySQLは、1つのリリースから次のリリースへのレプリケーションをサポートしていますが、逆方向のサポートは明示的にはありません(MySQLレプリケーションの互換性)。ステージングクラスターで8.0ホストをプライマリに昇格させることをテストしたとき、すべての5.7レプリカでレプリケーションが破損するのを見ました。私たちは克服する必要があるいくつかの問題がありました:

  1. MySQL
    8.0では、utf8mb4がデフォルトの文字セットとして使用され、より現代的なutf8mb4_0900_ai_cisy照合順序がデフォルトとして使用されます。以前のバージョンのMySQL5.7はutf8mb4_unicode_520_ci照合順序をサポートしていましたが、最新バージョンのUnicode utf8mb4_0900_ai_ciはサポートしていませんでした。
  2. MySQL 8.0はロールを導入して権限を管理しますが、この機能はMySQL 5.7には存在しませんでした。クラスターで8.0インスタンスがプライマリに昇格したとき、私たちは問題に直面しました。私たちの構成管理は、特定の権限セットを拡張してロールステートメントを含め、それらを実行することで、5.7レプリカでの下流レプリケーションを破壊しました。この問題を解決するために、アップグレードウィンドウ中に影響を受けるユーザーの定義済み権限を一時的に調整しました。

文字照合順序の互換性に対処するために、デフォルトの文字エンコーディングをutf8に設定し、照合順序をutf8_unicode_ciに設定する必要がありました。

GitHub.comのモノリスの場合、Railsの構成が文字照合順序の一貫性を確保し、データベースへのクライアント構成を標準化することを容易にしました。その結果、私たちは、最も重要なアプリケーションの後方レプリケーションを維持できると高い自信を持っていました。

課題

テスト、パフォーマンスチューニング、および特定された問題の解決の間、私たちはいくつかの技術的な課題に直面しました。

Vitessについては?

私たちは、関係データを水平にシャーディングするためにVitessを使用しています。ほとんどの部分において、MySQLクラスターをアップグレードすることとVitessクラスターをアップグレードすることはあまり変わりませんでした。私たちはすでにCIでVitessを実行していたので、クエリの互換性を検証できました。シャーディングされたクラスターのアップグレード戦略では、1つのシャードを一度にアップグレードしました。VitessプロキシレイヤーであるVTgateは、MySQLのバージョン情報をクライアントに渡し、一部のクライアントの動作はこのバージョン情報に依存します。たとえば、あるアプリケーションは、5.7サーバーのクエリキャッシュを無効にするJavaクライアントを使用していました。クエリキャッシュは8.0で削除されたため、それらにとってブロッキングエラーを生成しました。したがって、特定のキースペースの単一のMySQLホストがアップグレードされると、VTgateの設定も8.0のバージョン情報を渡すように更新する必要がありました。

レプリケーション遅延

私たちは、読み取り可用性を拡張するためにリードレプリカを使用しています。GitHub.comは、最新のデータを提供するために低いレプリケーション遅延を必要とします。

テストの初期段階で、8.0.28でパッチが当てられたMySQLのレプリケーションバグに遭遇しました:

システム変数replica_preserve_commit_order =
1が設定されたレプリカサーバーが、長期間にわたって集中的な負荷の下で使用された場合、インスタンスはコミット順序シーケンスチケットを使い果たす可能性があります。最大値を超えた後の誤った動作により、適用者がハングし、適用者のワーカースレッドがコミット順序キューで無期限に待機することになりました。コミット順序シーケンスチケットジェネレーターは現在正しくラップアラウンドします。Zhai Weixiangへの貢献に感謝します。(バグ#32891221、バグ#103636)

私たちはこのバグを発生させるすべての基準を満たしていました。

  • 私たちはGTIDベースのレプリケーションを使用するため、replica_preserve_commit_orderを使用しています。
  • 私たちの多くのクラスター、特に最も重要なものには、長期間にわたって集中的な負荷があります。ほとんどのクラスターは非常に書き込みが多いです。

このバグはすでに上流で修正されていたので、私たちは8.0.28よりも高いバージョンのMySQLをデプロイしていることを確認するだけでした。

私たちはまた、MySQL 8.0で増加した書き込みがレプリケーション遅延を駆動することを観察しました。これは、書き込みの重いバーストを避けることがさらに重要であることを意味しました。GitHubでは、レプリケーションラグに基づいて書き込みワークロードをスロットルするためにfrenoを使用しています。

クエリはCIを通過しますが、本番環境で失敗してしまいます

私たちは、最初に本番環境で問題を見ることになることを知っていました。そのため、レプリカをアップグレードする段階的なロールアウト戦略を持っていました。実際のワークロードに遭遇すると、CIでは通過するが本番環境で失敗するクエリに遭遇しました。特に、大きなWHERE IN句を含むクエリがMySQLをクラッシュさせる問題に遭遇しました。私たちは、数万の値を含む大きなWHERE INクエリを持っていました。その場合、アップグレードプロセスを続行する前にクエリを書き換える必要がありました。クエリサンプリングは、これらの問題を追跡し、検出するのに役立ちました。GitHubでは、クエリの可視性のためにSolarwinds DPM(VividCortex)、SaaSデータベースパフォーマンスモニターを使用しています。

学びと今後のための教訓

テスト、パフォーマンスチューニング、および特定された問題の解決の間、全体のアップグレードプロセスには1年以上かかり、GitHubの複数のチームのエンジニアが関与しました。私たちは、ステージングクラスター、GitHub.comをサポートする本番クラスター、および社内ツールをサポートするインスタンスを含む、私たちの全フリートをMySQL 8.0にアップグレードしました。このアップグレードは、私たちの観測プラットフォーム、テスト計画、およびロールバック機能の重要性を強調しました。テストおよび段階的なロールアウト戦略により、問題を早期に特定し、プライマリアップグレードの新しい障害モードに遭遇する可能性を減らすことができました。

段階的なロールアウト戦略があったとしても、私たちはすべての段階でロールバックする能力を必要とし、ロールバックが必要であることを示す信号を特定するための可視性が必要でした。ロールバックを有効にする最も困難な側面は、新しい8.0プライマリから5.7レプリカへの後方レプリケーションを保持することでした。私たちは、Trilogyクライアントライブラリの一貫性が、接続動作の予測可能性を高め、メインのRailsモノリスからの接続が後方レプリケーションを破壊しないという確証を得ることを可能にしたことを学びました。

しかし、異なるフレームワーク/言語の複数の異なるクライアントからの接続を持つ一部のMySQLクラスターの場合、後方レプリケーションは数時間のうちに破壊され、ロールバックの可能な時間ウィンドウを短縮しました。幸いなことに、それらのケースは少数であり、レプリケーションが破壊される前にロールバックする必要があるインスタンスはありませんでした。しかし、私たちはこれにより、既知でよく理解されたクライアント側の接続構成を持つことは利点があるということを学びました。そのような構成の一貫性を確保するためのガイドラインとフレームワークを開発する価値を強調しました。

データをパーティショニングするための以前の取り組みは報われました。これにより、異なるデータドメインのためのよりターゲットを絞ったアップグレードを行うことができました。重要なポイントは、1つの失敗したクエリがクラスター全体のアップグレードをブロックし、プロセス中に遭遇した未知のリスクの影響を軽減することができるため、異なるワークロードをパーティション化することでした。これは私たちのMySQLフリートが大きく成長していたということも意味しています。

最後にGitHubがMySQLバージョンをアップグレードしたとき、私たちは5つのデータベースクラスターを持っていましたが、今では50以上のクラスターがあります。成功裏にアップグレードするためには、観測性、ツール、およびフリートを管理するためのプロセスに投資する必要がありました。

結論

MySQLアップグレードは、私たちがフリートで実行するソフトウェアに対してアップグレードパスを持つ必要がある定期的なメンテナンスの一種に過ぎません。アップグレードプロジェクトの一環として、MySQLバージョンアップグレードを成功裏に完了するための新しいプロセスと運用能力を開発しました。それでも、アップグレードプロセスには手動介入を必要とする多くのステップがあり、将来のMySQLアップグレードを完了するために必要な労力と時間を削減したいと考えています。

私たちは、GitHub.comが成長するにつれて、フリートが成長し続けることを予想しており、データをさらにパーティション化することで、時間が経つにつれてMySQLクラスターの数を増やす目標を持っています。運用タスクと自己修復能力の自動化を組み込むことで、将来的にMySQLの運用をスケールするのに役立ちます。信頼できるフリート管理と自動化への投資は、必要なメンテナンスに追いつき、より予測可能で回復力のあるシステムを提供するのに役立ちます。

このプロジェクトからの教訓は、私たちのMySQL自動化の基盤を提供し、将来のアップグレードをより効率的に、しかし同じレベルの注意と安全性で行うための道を開くでしょう。


これらのタイプのエンジニアリング問題に興味がある場合は、採用情報をチェックしてください。