この記事では、いくつかの高可用性ソリューションについて考察を行います。
このシリーズの前回の記事(訳注:日本語版はこちら)では、長きにわたって利用されてきたMySQLの高可用性ソリューションを取り上げました。私はこれらを「The Elders (長老たち)」と名づけました。レプリケーションなどは今でもよく使われており、MySQLの新バージョンがリリースされるごとに改善されています。
この記事では直近5年間で登場し、なおかつコミュニティの中で牽引力を増している高可用性ソリューションに着目します。私はその中から2つのソリューションを選択しました。それは「Galera」と「RDS Aurora」です。ここで言う Galera は、Galera Cluster / MariaDB Cluster / Percona XtraDB Clusterを全て包括しています。また、Auroraを対象に含めるかどうかは少し悩みました。なぜなら、私はそれらがクローズドなソースコードを使っている点を好ましく思っていないからです。AWS環境と密接に統合されていることを考えれば、ソースコードを公開することに何の商業的な危険があるでしょう?しかし、私は技術におけるビジネス側の人間ではないので、この疑問については避けることにします。
Galera
私が "Galera" という時、それは Codership 社(フィンランドの企業)が提供するライブラリーでサポートされているレプリケーションプロトコルを意味しています。そのライブラリーは、レプリケーションプロトコルを動作させるためにMySQLのソースコード内のフックを必要とします。Percona XtraDB Cluster では、“wsrep”という語句が含まれた .cc ファイルが66個ありました。このように、MySQLに Galera のサポートを加えることは簡単な作業ではありません。また、全ての実行が同じわけではありません。例えば Percona の場合、安定性と使いやすさに重点を置いて新機能の開発を行っています。
それでは、Galera-based cluster の簡単な説明を始めたいと思います。スプリットブレインを考慮しない場合を除き、Galera clusterは最低3台のノードを必要とします。Galeraレプリケーションプロトコルは、ほとんど同期レプリケーションであるため、MySQLの一般的なレプリケーションと比較して大きな利点があります。トランザクションは全てのノード上でほぼ同時に実行され、プロトコルによってコミットの順番も同じであることが保証されます。性能向上のため各ノード内に受信キューが存在し、それがトランザクションが「ほぼ同期」となる所以です。受信キューが存在するために、「certification」という余分なステップが発生してしまいます。certification ステップでは、新たに受け取ったトランザクションと、既に受信キューに存在するトランザクションとを比較します。もしそれらが競合していたら、デッドロックエラーを返します。
性能面の理由から certification プロセスは高速でなければならず、そのため受信キューはインメモリです。トランザクションの数で受信キューのサイズが決まるため、巨大なトランザクションが存在すると多くのメモリを使用してしまいます。メモリの過使用を防ぐためのセーフガードは存在し、以下のようなトランザクションは失敗する可能性が高いです。
update ATableWithMillionsRows set colA=1;
「トランザクションのサイズが制限されている」というのが、Galera-based cluster における重要な1つ目の制限事項です。
また、矛盾する行を独自に見つけることも重要です。効果的に列比較を行う上で最もベストな方法は、全てのテーブルにPKを持たせることです。Galera-based cluster においては、例え困難であってもテーブルにPKが必要となります。これが、2つ目の制限事項です。私個人としては、常にテーブルに対してPKを設定すべきだと考えていますが、そうではないケースも沢山見たことがあります…。
もう一つのデザイン上の特徴は、トランザクションをコミットした時に全ノードからの応答が必要となる点です。これは、2ノード間のネットワークに大きな遅延が発生した場合に、トランザクション性能の低下に直結することを意味しています。WAN上に Galera-based cluster をデプロイする上で考えるべき重要なファクターとなります。同様に、過負荷な状態のノードがあるとトランザクションに対しての了承ができず、クラスタ全体の性能がダウンしてしまう可能性があります。大半のケースでは、slave threads を増やすことでネットワーク遅延による性能限界を克服することができます。個々のトランザクションは遅延の影響を受けてしまいますが、トランザクションを同時並列に処理することでスループットを向上させます。
ただし、"hot rows." が存在する場合は例外となります。hot row とは、頻繁に更新される行のことです。hot row に対するトランザクションは並列で実行できず、そのためネットワーク遅延の影響を受けてしまいます。
それでも Galera-based cluster は非常に人気があります。それは、いくつかの良い点も持っているからです。まず最初の、かつ最も明らかな点は完全な耐久性です。例えトランザクションを実行したノードがコミット直後にダウンしてクライアントに結果が返ってこなかったとしても、データは他のノードの受信キュー内に存在します。私の考えでは、これは共有ストレージが採用される主な理由であると思います。Galera-based cluster が登場する前は、クラッシュ時のデータ損失を回避するための唯一のソリューションが、共有ストレージでした。
しかし、共有ストレージの場合はスタンバイノードを使用することはできません。一方、Galera-based clusterの場合は全ノードが使用でき、ほぼ同期も取られています。レプリケーション遅延を殆ど気にすることなく、全ノードに対して参照クエリを発行することもできます。デッドロックエラーのリスクを許容できるのであれば、全ノードに対して更新クエリも発行できます。
最後は、こちらも同様に重要ですが、「SST」と呼ばれる新しいノードを自動でプロビジョニングする機能が存在することです。SSTを実行すると、新しいノード(joiner)がクラスタに対して donor を要求します。既存のノードの内の1つが donor ノードになることを了承すると、そこからフルバックアップが取得されます。このバックアップはネットワーク経由で joiner ノードに転送され、リストアが実行されます。それが完了したら、joiner ノードは最新の差分である IST を適用し、完了後クラスタに参加します。SSTの方式として最も一般的なのは、Percona XtraBackupです。XtraBackupによるSST中は、パフォーマンスは落ちますが、クラスタ全体を変わらず使用することが出来ます。この機能によって運用面はかなり単純になります。
この技術はとても人気があります。もちろん、私が Percona 社で働いており、その主要製品が Percona XtraDB Cluster であることから、多少バイアスがかかっていると思います。MySQLの標準的なレプリケーションを除き、Galera は私の顧客の間で使われている最も一般的なHAソリューションです。
RDS Aurora
2つ目の「大人」は、RDS Aurora です。オープンソースの技術ではないため、ここに Aurora を加えるのは躊躇していました。Auroraに関する最新の開発状況について、余り熱心に追いかけていなかったことは認めなければいけません。それでは、Auroraの解説から始めましょう。
Auroraには3つの主なパーツがあります。まず最低限必要となるのが、writer ノードとなるデータベースサーバとストレージです。
Auroraの特別な点は、ストレージレイヤーが独自の処理ロジックを持っているところです。ソースコードを確認できないため、私には処理ロジックが writer ノード(AWSインスタンス)の一部なのか、もしくはストレージサービスの一部なのかが分かりません。ひとまず、このレイヤーのことは「applier」と呼ぶことにします。applier の役割は、writer ノードが redo ログへの書き込みをするだけで済むよう、redo ログの内容を適用することです(通常は InnoDB ログファイルに書き込まれます)。applier は、更新内容を読み取ってストレージ内のページを変更していきます。ノードが redo ログの内容がまだ適用されていないページを要求した場合は、結果を返す前に変更の適用を終わらせます。
writer ノードから見ると、書き込み処理の数が大幅に少なくなります。また、キューに入れられる更新処理の数に上限が無くなるため、 innodb_log_file_size 変数をかなり大きな値に設定できます。さらに、Aurora はページをフラッシュしなくても良いということは、writer ノードがストレージからデータを読み込む必要があり、バッファプールに空きページがない場合でも、"dirty"なページすら捨ててしまうことができます。つまり、バッファプールにダーティページが存在しないのです。
現在のところ、それは書き込み処理の高負荷やスパイクに対してとても有効であるようです。では、reader ノードはどうでしょう? reader ノードは、writer ノードから更新を受け取ります。もし更新によって reader ノードのバッファプール内のページが変更される場合は、そのまま内容に沿って変更するか、該当ページを破棄してストレージから読み込むことができます。繰り返しとなりますが、ソースコードが読めないため、詳しい実装は不明です。ポイントとなるのは、reader ノードがマスタと 同じ データを持っており、遅延は発生しないということです。
読み込み時に遅延が発生しない点以外のAuroraの良いところは、ストレージです。InnoDBのページは、ファイルシステム上の一般ファイルとは異なり、オブジェクトストア上にオブジェクトとして保存されます。これは、あらかじめ巨大なストレージを用意しておく必要がないことを意味しています。あなたは自分が使っている分だけ、すなわちこれまでに最大で使用した分だけお金を支払います。ちなみに、たとえAuroraであってもInnoDBのテーブルスペースは縮小できません。
その上、もしあなたが5TBのデータセットを持っており、10台のサーバ(1台の writer ノード/ 9台の reader ノード)を必要とするようなワークロードだった場合、他の AZ にレプリケーションを行わないのであれば 5TB のストレージ1つで済ますことができます。通常のMySQLレプリケーションと比べると、1台のマスターと9台のスレーブを使用するとなると各サーバに5TBのストレージが必要であり、合計50TBとなります。それに加え、最低でも10倍の書き込みIOPSが発生します。
つまり、ストレージが賢く、巨大なデータセットを持ち、高並列でREADがヘビーなワークロードのアプリケーションにとって興味深いものとなっています。あなたは reader ノードを writer ノードに自動昇格させる機能を用いて高可用性を担保することができます。さらに、エンドポイントを通じて、primary ノードか reader ノードの正しい方に自動で接続してくれます。最後に、DRを考慮して異なる AZ にストレージを複製することもできます。
もちろん、こうしたアーキテクチャにはトレードオフの面もあります。もしあなたがAuroraを使ってみたら、とても大きなインスタンスタイプでは期待以上の働きをする一方で、ごく小さなインスタンスタイプは平均以下のパフォーマンスしかでないことにすぐ気が付くでしょう。appliers がオーバーロードすることも容易に起こりえます。以下のクエリを実行したとしましょう。
update ATableWithMillionsRows set colA=1;
select count(*) from ATableWithMillionsRows where colA=1;
ATableWithMillionsRowsテーブルのサイズが、バッファプールよりも大きかった場合、大量のページの更新がかかるせいで appliers がオーバーロードとなり、SELECT文が長時間ハングアップしてしまいます。
採用実績としては、Percona社の顧客の中にもAuroraを利用している方はいますが、多くはありません。Aurora利用者がPercona社にサービスやサポートを求めることはあまりないでしょう。また、私はソースコードを非公開のままでいるという意思について不思議に思います。それはきっとMySQLコミュニティのようなコミュニティの間ではプラスとなるマーケティング要素にはならないでしょう。Auroraの技術はそのエコシステムに囲まれているのに、競合相手に再利用されてしまう技術的リスクは本当にあるのでしょうか?ソースコードに対してオープンにアクセスできるようになり、より理解が進めば、Amazon社は価値のあるコントリビューションを受け取ることができます。Auroraを理解し、チューニングし、推奨することが遥かに簡単になるでしょう。
更に情報を知りたい方はこちら: Marco’s benchmarks Marco’s benchmarks part-2