リーダーノードを設ける3つのメリット

松田丈氏:次に、この問題を単純にしてくれるリーダーノードという考え方について説明します。リーダーノードとは何でしょうか。(まず)リーダーノードを設けるメリットについて見ていきます。

1つ目のメリットは、データの整合性担保をリーダーノード内で完結させられることです。例えば競合する書き込みがあった際に、その順序性をリーダーノード内で整理することで、一貫性を保ったリクエスト処理をシンプルに実現できます。

2つ目のメリットは、操作ごとに他のノードに合意を取る必要がなくなることです。リーダーノードがすべての決断を下すことができ、他のノードはそれに従うため、効率的にリクエストを処理することができます。

最後に多数決、クオラムを用いた分散システムに比べ、実装が容易になります。クオラムは単純な多数決とは少し異なり、実装が複雑になります。クオラムについても本発表では説明しないので、もし興味のある方がいれば調べてみてください。

先ほどの例だと、書き込みをリーダーノードのみに許可することで、座席のダブルブッキングを防ぐことができそうです。それ以外の読み取りのリクエストを他のノードでさばくことで、全体の性能を向上させることができます。これはいわゆるリードレプリカと呼ばれる考え方です。

Amazon RDSの同期レプリケーション、非同期レプリケーション

ここで少し脇道に逸れますが、参考としてAmazon RDSの同期レプリケーション、非同期レプリケーションについてお話しします。Amazon RDSはAWSが提供するクラウドリレーショナルデータベースサービスです。耐障害性、可用性を高めるためのMulti-AZ配置機能では同期レプリケーション、性能向上のためのリードレプリカ機能では非同期レプリケーションが用いられています。

これは、スタンバイはいつでもプライマリーに切り替わるように通信があることが前提で、確実に最新のデータを保持している必要があるため、同期レプリケーションを用いているということ。そして、すべてのリードレプリカと同期レプリケーションをすると、先ほど見たとおり耐障害性が逆に下がってしまうため、非同期レプリケーションを行っているという使い分けであると考えると、丸暗記せずともどんなレプリケーションを行っているかがわかると思います。

分散システムについての理解を深めることがクラウドサービスの理解にもつながるという例として紹介しました。

リーダーノードの決め方

では本題に戻って、リーダーを決めるにはどうすればいいのか見ていきます。

(スライドを示して)記事ではリーダーの決め方としてこれらのものが挙げられています。信頼できるノードを固定してリーダーにする方法、分散合意のアルゴリズムのPaxosやRaftといったものを用いる方法、Apache ZooKeeperなどの既存のソフトウェアを用いる方法、そしてリース(Lease)を実装するという方法です。

記事では、「Amazonではリースを用いることが多い」とされています。

リースの実装と課題

リースについて見ていきます。リースはリーダーである権利を期限付きで取得するような方法です。

(スライドの)図ではノードCが期限付きでリーダーになります。リーダーは他のノードに対してハートビートを行います。この際、他のノードが有効なリースを持っていないことを確認した上で、どのノードがリーダーかを記録するためのデータベースに書き込みます。このデータベースが単一障害点にならないような工夫が別途必要と考えられますが、話が複雑になるため、今回は取り扱いません。

では、実際にリースを実装する場合について考えていきます。イメージがつきやすいよう、疑似コードを掲載しています。この疑似コードではハートビートの送信を省略していますので、注意してください。

疑似コードの内容はシンプルで、while(true)の中の1行目でリクエストを受け付けて、次のセクションでリースの有効期限が十分に、ここでは1万ミリ秒なので10秒以上残っていることを確認して、もし残っていなければリースを再取得します。そしてリースを再度検証した後にリクエストを処理するといった疑似コードになっています。

この疑似コードの注意点として、まず初めにローカルの経過時間を用いてリースの有効期限を確認する必要があります。一般的にNTPサーバーから取得されている実時間を用いると、NTPサーバーとのレイテンシーなど誤差要因が多いです。実時間の分散システム内で合意するのも、ネットワークレイテンシーの問題などから現実的にはほぼ不可能です。

リースの問題点として直面しやすいのが、リクエストの処理時間に関する保証です。(スライドを示して)2つ目のif文の中のprocess(request)というところにかかる時間が保証されておらず、これが許容されるリース時間、ここでは10秒より長引くとリースが切れた状態でリーダーとして振る舞うことになり、バグにつながる恐れがあります。ここで1万ミリ秒を長くしてしまうと、リースの更新を頻繁に行う必要が出てしまい、リースの更新に多くの時間とリソースを費やすことになってしまいます。

そもそもリースが検証された後、リクエストを処理する直前にガベージコレクションが起きた場合はどうなるでしょう。ガベージコレクションに10秒以上費やしたような場合は、完全にリースが切れた状態で処理を開始することになります。

リース実装の最大の課題は、これまで見てきたとおり時間計測に対する保証の弱さです。期限が切れているのにリーダーとして振る舞うノードが現れる可能性があり、これは避けるべき事態です。

元も子もないようですが、この事象を避けるためには十分にテストされたロッククライアントライブラリを用いるのが良いと記事では結論づけられています。例としてDynamoDB Lock ClientとApache ZooKeeperが挙げられています。

リーダーノードの引き継ぎに関する2つの工夫

最後に、リーダーが故障した場合について見ていきます。リースにおいてリーダーが故障すると再選挙が行われ、新しいリーダーが決まります。故障したリーダーが継続中の作業を新しいリーダーに引き継ぐには工夫が必要です。どんな工夫が必要か見ていきましょう。

(この場合は)2つの工夫が必要です。1つ目は処理に冪等性を担保することです。これによってリーダーは自信を持ってリトライを行うことができます。2つ目はリクエストの内容を処理してステートを更新する前に他のノードに伝えておくことです。こうすることで、処理を漏れなく引き継ぐことができます。これはDBMS(Database Management System)のライターヘッドログなどに似た考え方だと思います。

分散システムの理解はアプリケーション構築や運用に活かせる

では最後にまとめです。今回の発表では、3種類の分散システムがあることを見てきました。オフライン分散システム、ソフトリアルタイム分散システム、ハードリアルタイム分散システム。

ハードリアルタイム分散システムの中でデータの一貫性を担保するのは難しいことを説明しました。これはノードやネットワークの障害であったり、レプリケーションによるデータの衝突が起こるためです。

一貫したデータ管理をシンプルに行うための考え方がリーダーというもので、Amazonではリースを用いてリーダーを決めることが多いということを見てきました。リーダーが故障した場合に備えた冪等性担保などの工夫も必要ということがわかりました。

分散システムを理解することはアプリケーション構築や運用に活きてきます。この発表で興味を持ってくださった方は、ぜひ分散システムについてもっと調べてみてください。

ご清聴ありがとうございました。