gh-ost:GitHubのMySQL向けオンライン・スキーマ・マイグレーションツール

本日、gh-ostのオープンソース・リリースを発表します。GitHubの、トリガーレスなMySQL向けオンライン・スキーマ・マイグレーション・ツールです。

gh-ostは、MySQLテーブルの修正が必要な、進行中の継続的なプロダクション変更に伴って私たちが直面する問題に答えるために、ここ数ヶ月で開発されました。gh-ostは、負担が小さく、制御しやすく、監査しやすく、操作が簡単なソリューションを提供することによって、現在のオンライン・テーブル・マイグレーションのパラダイムを様変わりさせます。

MySQLテーブルのマイグレーションは、よく知られた問題で、2009年からはオンライン・スキーマ変更ツールによって対処されてきました。ハイペースで成長するプロダクトに伴って、データベース構造の変更が必要になります。列やインデックスなどの追加・変更・削除は、デフォルトのMySQLの動作を妨げる操作です。私たちはそのようなスキーマ変更を1日に何度も行うので、ユーザにかかる負担を最小限に抑えたいと望んでいます。

gh-ostについて説明する前に、既存のソリューションについて、そして、新しいツールを手がける理由について考えてみましょう。

オンライン・スキーママイグレーションの現状

現在、オンライン・スキーマ変更は、主に次の3つの方法のどれかを使って行われています。

  • スキーマをレプリカに移行させ、他の複数のレプリカにクローン化または適用し、リファクタリングしたレプリカを新しいマスターとして登用する。
  • MySQLのInnoDB用オンラインDDLを使用する。
  • スキーママイグレーション・ツールを使用する。現在、最も一般的なものは、pt-online-schema-changeFacebookのOSCで、LHMや、オリジナルのoak-online-alter-tableツールも使われている。

その他の方法にGalera Clusterを使うRolling Schema Upgradeがあり、それ以外には、InnoDBではないストレージエンジンがあります。GitHubでは、共通マスター&レプリカ・アーキテクチャを使って、信頼性の高いInnoDBエンジンを利用します。

では、なぜ私たちは、上記の方法のどれかではなく、新しいソリューションを手がけようとしたでしょうか。既存のソリューションは、すべて、方法に限界があり、その欠点のいくつかについて、下記に簡単に説明します。特に、トリガベースのオンライン・スキーマ変更ツールの欠点については、後に深く掘り下げて考えます。

  • レプリカ・マイグレーションでは動作のオーバヘッドが生じ、多数のホスト、長い配信時間、複雑な管理が必要になる。変更は特定のレプリカまたはトポロジーのサブツリーに対して明示的に行われる。ホストがダウンすることや、以前のバックアップからホストが復旧することや、新しくプロビジョニングされたホストなどの考慮事項はすべて、ホストごとの変更に対する厳密な追跡システムを必要とする。1つの変更について複数回の繰り返しが必要になり、よって多くの時間が必要になることもある。レプリカの登用によって、短時間の停止期間が引き起こされる。同時に複数の変更が行われる場合は、調整が困難になる。私たちは普通、1日に何回かスキーマ変更をデプロイするので、管理オーバーヘッドをなくしたいと考えており、今回のソリューションが使えると認識している。

  • MySQLのInnoDB用オンラインDDLが「オンライン」なのは、それが起動されるサーバ上のみである。レプリケーション・ストリームはalterを連続させるので、それによりレプリケーション遅延が生じる。レプリカごとに別々に実行させようとすることは、前述した管理オーバーヘッドの多くの原因となる。DDLは割り込み不可なので、中断すると、長いロールバックまたはデータ辞書の破壊につながる。DDLは、負荷が高いときにスロットリングしたり一時停止したりといった、洗練された動作ができないのだ。操作を集中的に行うことによってリソースが消耗する可能性がある。

  • 私たちは長年、pt-online-schema-changeを使ってきたが、容量とトラフィックが増えるにつれて、ますます多くの問題に直面するようになり、ついに多くのマイグレーションを「リスキーな操作」だと考えるようになった。マイグレーションには、オフピーク時や週末にだけ実行できるものもあるが、絶えずMySQLの停止を引き起こすものもある。既存のオンライン・スキーマ変更ツールはすべて、MySQLのtriggerを使用してマイグレーションを行い、それには問題がいくつかある。

トリガベースのマイグレーションの問題点

オンライン・スキーマ変更ツールはすべて、類似した様式で動作します。元のテーブルと類似したghostテーブルを作成し、そのテーブルを空白の状態で移行させ、元のテーブルからghostテーブルにデータをゆっくりと、徐々にコピーします。その間に、進行中ghostテーブルへの変更(テーブルに行われるINSERTDELETEUPDATE)を反映させます。テーブルが同期したとの確信が得られると、ツールは元のテーブルをghostテーブルに置き換えます。

pt-online-schema-changeLHMoak-online-alter-tableなどのツールは同期的な手法を使用します。この手法では、テーブルに加えられた変更は毎回、直ちに、同じトランザクション空間を使用して、ghostテーブルに反映された変更へと移行します。Facebookのツールは非同期的な手法を使用して変更を変更ログ・テーブルに書き込んでから、その書き込みとghostテーブルへの変更適用を繰り返します。このようなツールはすべて、現在テーブルに加えられている変更を特定するために、トリガを使用します。

トリガとは、行ごとの、テーブルへのINSERTDELETEUPDATE操作時に呼び出される、保管されたルーチンです。トリガはクエリのセットを含んでいてもよく、これらのクエリは、テーブルを操作するクエリと同じトランザクション空間で実行します。これにより、テーブルに対する元の操作とトリガで呼び出される操作の両方に不可分性が得られます。

一般的にトリガの使用には、具体的にはトリガベースのマイグレーションには、次の問題があります。

  • トリガは保存されたルーチンなので、インタープリタ用のコードである。MySQLはトリガを事前コンパイルしない。トリガはクエリのトランザクション空間に結合すると、マイグレーションされたテーブルに対して行われる各クエリにパーサとインタープリタのオーバーヘッドを追加する。

  • ロック:トリガは元のクエリと同じトランザクション空間を共有し、これらのクエリがテーブルのロックについて競合している間、トリガは別のテーブルのロックについて競合する。このことは、特に、同期手法では深刻である。ロックの競合は、マスタでの書き込み同時性に直接関係する。私たちは、プロダクション中に、ほぼ、または完全なロックダウンを経験したことがあり、ロック競合に起因して、テーブルまたはデータベース全体がアクセス不能にまでなった。トリガ・ロックの別の側面は、メタデータの生成時または破壊時に要求されるメタデータのロックである。マイグレーション操作の最後にビジー状態のテーブルからトリガを削除しようとするときに、数10秒から1分間にもなるストールを起こしたことがある。

  • 一時停止ができない:マスターにかかる負荷が大きくなると、未完のマイグレーションをスロットリングまたは一時停止させたいことがある。しかし、トリガベースのソリューションでは真の意味でそれを行うことはできない。行コピー操作を一時停止させることはできるかもしれないが、トリガを一時停止させることはできない。トリガを削除することは、データ損失につながる。よって、トリガはマイグレーションの間を通じて動作し続けなければならない。ビジー状態のサーバでは、オンライン操作がスロットリングしているときでさえも、マスターがトリガの負荷によってダウンしたことがある。

  • 同時マイグレーション:複数同時マイグレーションが(複数の異なるテーブル上で)可能になることには誰しも興味がある。前述したトリガ・オーバーヘッドがあるために、トリガベースの複数同時マイグレーションを行う準備は整っていない。実務でそれを行っている人がいるかどうかも知らない。

  • テスト:マイグレーションの実験や、負荷の推定をしたい場合がある。トリガベースのマイグレーションは、ステートメントベースのレプリケーションを介したマイグレーションしかシミュレーションできず、レプリカへの作業負荷がシングルスレッドの場合は、真のマスター・マイグレーションにはほど遠い(マルチスレッドのレプリケーション技術を使っているか否かに関わらず、それぞれのテーブルについては常に、ほど遠い)。

gh-ost

gh-ostは、 “GitHub’s Online Schema Transmogrifier/Transfigurator/Transformer/Thingy” (GitHubのオンライン・スキーマ変形(変形、変身など)ツール)の頭文字です。

gh-ost light logo

gh-ostには次の特徴があります。

  • トリガを使用しない
  • 軽量
  • 一時停止が可能
  • 動的制御が可能
  • 監査が可能
  • テスト可能
  • 高信頼性

トリガを使用しない

gh-ostはトリガを使用しません。バイナリログを追うことによってテーブルデータへの変更を捕捉します。したがって、その動作は非同期方式であり、変更がコミットされてから、いくらかの時間の経過後にghostテーブルに変更を適用します。

gh-ostでは、バイナリログがRBR(行ベースのレプリケーション)フォーマットであると期待されますが、SBR(ステートメントベースのレプリケーション)で動作するマスターをマイグレーションするためにgh-ostを使えないという意味ではありません。実際、私たちはそれを行っています。gh-ostは、SBRをRBRに変換するレプリカからバイナリログを簡単に読み出せますし、レプリカを簡単に再構成してマイグレーションを行います。

軽量

トリガを使わないことによって、gh-ostは、マイグレーションの作業負荷を、全体的なマスター作業負荷から切り離します。マイグレーションされたテーブルで実行するクエリの同時性や競合には無関係です。そのようなクエリによって加えられた変更は、バイナリログの中に無駄なく並べられ、gh-ostは、それらを読み込んでgh-ostテーブルに適用します。実際には、gh-ostは、バイナリログ・イベントの書き込みと共に行コピーの書き込みも1列に並べます。よって、マスターは、ただ1つの接続だけに注意すればよく、その接続は連続的にghostテーブルに書き込みを行っています。ETLと似たようなものです。

一時停止が可能

すべての書き込みはgh-ostによって管理され、また、バイナリログの読み込みはそもそも非同期の動作なので、gh-ostは、スロットリングするときマスターへのすべての書き込みを一時停止することができます。スロットリングすると、マスターでの行コピーが一切なく、また、行のアップデートもありません。gh-ostは内部のトラッキングテーブルを生成し、スロットリングしている時でさえ、ハートビートイベントをテーブルに少量、書き込み続けます。

gh-ostはスロットリング管理をさらに洗練させ、スロットリングの管理法もいくつも提供します。

  • ロード:pt-online-schema-changeのユーザには馴染みのある機能だと思うが、Threads_running=30のようにMySQLメトリクスで閾値(しきいち)を設定する。
  • レプリケーション遅延:gh-ostには、レプリケーション遅延を分析に使われるハートビート機構が組み込まれている。コントロールレプリカを指定するか、何も指定しなければgh-ostは最初に接続したそのレプリカを使う。
  • クエリ:スロットリングを始めるかを決めるクエリを提示することもできる。SELECT HOUR(NOW()) BETWEEN 8 and 17はどうだろうか。

上記の方法では、マイグレーションが実行されている間でさえも、大幅に変更されてしまう可能性があります。

  • フラグファイル:ファイルに触れると、gh-ostがスロットリングを開始する。ファイルを削除すると、再開する。
  • ユーザコマンド:ネットワークを介してgh-ost(下記参照)に動的に接続し、スロットリングを開始するように命令する

動的制御が可能

既存のツールではマイグレーションの負荷が大きくなったら、DBAがより小さなchunk-sizeを再設定し、マイグレーションを止め、最初から再開させます。これは無駄な作業だと私たちは考えます。

gh-ostはUNIXソケットのファイルと(設定可能な)TCPを介してリクエストをリッスンします。マイグレーションが実行中でもgh-ostに命令することができます。以下が例です。

  • echo throttle | socat - /tmp/gh-ost.sockでスロットリングを開始する。no-throttleも同様。
  • 実行パラメータを変更する。chunk-size=1500max-lag-millis=2000max-load=Thread_running=30は、gh-ostが動作の変更を受け入れる命令の一例である。

監査が可能

gh-ost状態情報を要求するにも同じインターフェースが使えます。gh-ostは現在の進行状況や、主なコンフィギュレーションパラメータ、関係するサーバのIDなどさまざまなことを進んで報告してくれます。この情報はネットワークを通してアクセス可能なので、進行中のオペレーションの可視性を高めます。これがなければ、今のところ共有スクリーンを使うか、ログファイルを追っていくしかありません。

テスト可能

バイナリログの内容はマスターの作業負荷からは分離されているので、レプリカでマイグレーションを適用することは、真のマスター・マイグレーションにより類似しています(ただし、完全ではなくので、ロードマップ上でもう少し作業が必要です)。

gh-ost--test-on-replicaを使ったテストのビルトインサポートが付いてきます。これによってレプリカでマイグレーションを実行でき、マイグレーションの最後で、gh-ostがレプリカを止め、テーブルをスワップし、そのスワップを戻し、両方のテーブルを同期して所定の場所に置きます。これでレプリケーションが終了します。2つのテーブルを簡単に分析し比べることができます。

GitHubで稼働中のgh-ostをテストする方法は次の通りです。テスト用に指定された複数のプロダクションレプリカがあります。そのレプリカは、トラフィックへのサービスは行いませんが、代わりにすべてのテーブル上のマイグレーションテストを継続的に実行します。それぞれのプロダクションテーブルは、それが空っぽでも数百GBでも、ストラクチャを変更しない普通のステートメント(engine=innodb)を使ってマイグレーションさせることができます。どのマイグレーションも、レプリケーションを終了させることで終わります。私たちは、オリジナルのテーブルとghostテーブルの両方からのテーブルデータ全体の完全なチェックサムを使います。それらは同一であることが期待されます。そして、レプリケーションを再開し、次のテーブルへと進んで行きます。どのプロダクションテーブルも、レプリカ上でgh-ostを使っていくつもマイグレーションを成功させてきたということは言うまでもないでしょう

高信頼性

今まで述べてきた特徴、そしてさらに他の機能も、すべてはgh-ostの動作の信頼性を高めるためのものです。今まで何年もの間、同じツールを使い続けていた環境下では、gh-ostは新しいツールなのです。

  • レプリカでgh-ostをテストする。マスターで試す前に、私たちは
    数千規模のマイグレーションを初めて成功させてきた。つまり、皆さんにもできるはず。レプリカをマイグレーションし、データが完全であることを検証しよう。ぜひやってみてほしい!

  • gh-ostを実行すると、マスターの負荷が大きくなっていると疑うかもしれないが、そんなときはスロットリングを開始しよう。ファイルに触れ、echo throttle。すると、マスターの負荷が通常に戻っていることが分かる。こういうことができると分かっているだけで、気持ちにゆとりができるのではないだろうか。

  • マイグレーションが開始され、ETAが2:00amに終了すると言っている。テーブルがスワップされる最終の切り替えが気になるからって、ずっと張り付いていたくはない。フラグファイルを使って、gh-ostに切り替えを延期するよう命令することができる。gh-ostは行コピーを完了させるが、テーブルをフリップはしない。代わりに、進行中の変更を適用させ続け、ghostテーブルを同期し続ける。翌日、オフィスに出勤する頃には、フラグファイル、もしくはecho unpostponegh-ostに移動し、切り替えが始まる。ソフトウェアの動作をずっと観察しなければならないなんて誰も好まない。そんなことするより、私たちはもっと人間らしいことをすべきだ。

  • ETAと言えば、--exact-rowcountはあなたを笑顔にさせてくれるはずだ。長々とテーブルにSELECT COUNT(*)するという作業さえ最初にやってしまえば、あとはgh-ostが自動的に作業量を正確に推定し、マイグレーションの進行に合わせてその推定を発見的手法で更新してくれる。ETAのタイミングが常に変更の影響を受け、進捗率は正確だ。私たちのように、あなたも、マイグレーション状態が99%と出ているのに、数時間も待たされているような状況である場合、この変更をすればかなり楽になるだろう。

gh-ostの動作モード

gh-ostは、複数のサーバと接続し、そのサーバの中の1つから直接バイナリログ・イベントをストリームするためにレプリカとして接続することで、動作します。動作モードにはさまざまな種類がありますが、セットアップや、設定、そしてマイグレーションをどこで実行するかによって選択するのがよいでしょう。

gh-ost operation modes
注釈:gh-ostのオペレーションモード
a. レプリカと接続
b. マスターと接続
c. レプリカでマイグレーション、もしくはテストする

a.レプリカと接続、マスターでマイグレーションする

このモードはgh-ostのデフォルトの設定です。gh-ostはレプリカを調査し、トポロジーのマスターを見つけ、接続します。マイグレーションは以下のようになります。

  • マスターで行データを読み込み、書き込む。
  • レプリカで、バイナリログ・イベントを読み込み、マスターに変更を適用する。
  • テーブルフォーマット、列、キーを調査し、レプリカで行をカウントする。
  • レプリカから内部のchangelogイベント(ハートビートなど)を読み込む。
  • マスターで切り替え(テーブルを切り替える)。

マスターが、SBRで動作しているならば、このモードが最適です。レプリカは実行可能なバイナリログ(log_binlog_slave_updates)で設定され、binlog_format=ROWを持っているはずです(gh-ostは後者を適用します)。

しかしRBRであっても、マスターが関係する動作モードには、このモードをおすすめします。

b. マスターと接続

レプリカがないか、使いたくない場合は、マスターで直接動作させることもできます。gh-ostはすべてのオペレーションをマスターで直接実行できます。ただ、レプリケーション遅延のことを考えて依頼することもできます。

  • マスターは、バイナリログをRBRフォーマットで生成させる必要がある。
  • --allow-on-masterを使ってこのモードを許可する必要がある。

c. レプリカでマイグレーション、テストする

このモードはレプリカでマイグレーションを実行します。gh-ostはマスターに簡単に接続しますが、その後、マスター上では何の変更をすることなく、レプリカですべてのオペレーションを実行します。オペレーションの中で、gh-ostは、レプリカが最新の状態になるようにスロットリングします。

  • --migrate-on-replicaは、レプリカで直接テーブルをマイグレーションすると、gh-ostに示している。レプリケーションが進行中だとしても、切り換えが実行される。

  • --test-on-replicaは、マイグレーションの目的はテストをするためだけだということを示している。切り替えが始まる前に、レプリケーションは終了する。テーブルはスワップされ、スワップされ返す。そして、オリジナルのテーブルがオリジナルの場所に戻され、レプリケーションが終了すると、両方のテーブルが残される。両テーブルを調査し、データを比較することもできる。

GitHubのgh-ost

gh-ostはこれで、本番移行ができる準備が整いました。技術的なリクエストが来ると、gh-ostを毎日実行させます。1日に複数回、実行させる日もあるでしょう。監査し、性能を管理して、ChatOpsと統合させます。技術者は、マイグレーションの進行をしっかりと観察し、動作を管理することができます。メトリクスとイベントが集められ、稼働中のマイグレーション動作に対して、しっかりとした見通しを提供してくれます。

オープンソース

gh-ostMITのライセンスのもと、オープンソースコミュティでリリースされました。

gh-ostは安定していることは分かったが、まだ改良したい点があります。今回のリリースで、コミュティへの参加と貢献を歓迎します。今後、しばしばコミュニティへの貢献に関しての提案をアップしていくかもしれません。

gh-ostは積極的にメンテナンスをしています。ぜひ使って、テストしてみてください。大きな力を注いで作り上げましたので、自信をもってお勧めします。

謝辞

gh-ostは、GitHubのデータベースインフラクチャ・エンジニアリング・チームによって、デザインされ、開発され、レビューされ、テストされました。

@jonahberquist@ggunson@tomkrouper@shlomi-noach

貴重な情報と助言をくれたGitHubの技術者たちに感謝します。また、このプロジェクトの試作段階でレビューやコメントを残してくれたMySQLのコミュニティの友人たちにも感謝します。