Redis Cluster、もはや絵に描いた餅ではない。
Redis Clusterについてgitの履歴から確認できる最初のコミットは2011年3月29日付ですが、これは「コピーしてコミット」したマージです。Clusterブランチの履歴は、作業途中のコミットがあまりにも混沌としていたため破棄されました。APIの初期構想と、システムの他の部分とのやり取りの形を整えるためだけのコミットだったからです。
つまり、これはおよそ4年前から続いているプロジェクトです。Redisプロジェクト全体の歴史の、およそ3分の2に相当します。それでも、Redis 3.0.0のリリース候補、しかも最初のリリース候補をリリースするのは今日になりました。Redis 3.0.0は、Clusterをサポートする最初のバージョンです。
紆余曲折の道のり
なぜこれほど時間がかかったのかは、簡単に説明できます。Redisに自動的にスケールする方法がなければ、まったく使いものにならなくなりそうだった時期に、私は焦ってClusterプロジェクトを始めました。しかし、Redis自体がまだ未成熟だったので、始めるタイミングとしては適切ではありませんでした。まだ、堅実な「単一インスタンス」のストーリーを示せる段階ではなかったのです。
タイミングを誤ってプロジェクトを始めてしまった一方で、コミュニティから寄せられる要望を無視するという罠には、少なくとも陥りませんでした。そのため、他の基本的な機能により多くのリソースを割くために、プロジェクトは何度も、数え切れないほど中断されました。永続化、レプリケーション、レイテンシ、イントロスペクションには、Clusterよりもはるかに多くの注意を向けました。ユーザーにとって、そちらのほうが重要だったからです。
このプロジェクトには、もう一つ限界がありました。始めた当時、私は分散プログラミングについてまったく何も知りませんでした。最初に作った設計はひどいものでしたが、「製品」としての要件だけはうまく捉えていました。低レイテンシ、線形スケーラビリティ、小規模クラスタにおける小さなオーバーヘッドです。しかし、細部はすべて間違っており、必要以上に複雑で、使っていたアルゴリズムは安全ではありませんでした。
少しずつ進めながら、私は分散プログラミングの基礎を学び始めました。そしてRedis Clusterを再設計し、その考え方をSentinelの新バージョンにも適用しました。どちらのシステムで使っている分散プログラミングのアルゴリズムも、非同期レプリケーションによる結果整合性システムなので、まだ原始的です。コンセンサスなど、簡単ではない問題に取り組む必要がなかったからです。それでも、少なくともCPストアを作る場合と比べれば単純な問題を扱っているときでさえ、何をしているのか理解していなければ、出来上がるシステムは完全に間違ったものになり得ます。
こうした問題があったにもかかわらず、私はプロジェクトに取り組み続けました。修正を重ね、実装を直し、成熟させようとしました。小さな部屋にいる象のように、Redisコミュニティ全体に浸透していた、単純な事実があったからです。人々は、次の2つを自分たちの手で何度も実現しようとしていました。そして多くの場合、それは完全に壊れた方法で行われていました。
- N個のノードにデータセットを分割して配置すること。
- 特定の障害に耐えられるようにする、応答性の高いフェイルオーバー手順。
「2」の問題はあまりにもひどかったため、Clusterが完成する前にRedis Sentinelプロジェクトを始めることにしました。できるだけ早く高可用性システムを提供するためです。また、「1」ではなく「2」だけを必要とする大多数のユースケースには、Redis Clusterより適したシステムでもありました。
ようやく、この取り組みが現実の世界で最初の成果を見せ始めました。採用を進め、残っているバグを修正し、システムをより段階的に改善していくために必要な、重要なマイルストーンとなるリリース候補が、今ここにあります。
実際に何をするのか
Redis Clusterは基本的に、データを分割して配置するための仕組みです。クラスタを稼働させたまま、あるノードから別のノードへキーを再配置できます。また、特定の種類の障害に耐えられるようにするフェイルオーバー手順も備えています。
分散データベースの観点から見ると、Redis Clusterはネットワーク分断中の可用性を限定的に提供し、整合性も弱い形で提供します。基本的には、CPシステムでもAPシステムでもありません。言い換えれば、分散システムで理論上可能な限界を達成することを目指すのではなく、現実の世界で一定の性質を得るために、あえて制約を受け入れています。
整合性モデルは、有名な「結果整合性」モデルです。ネットワーク分断によってノード間の同期が失われた場合でも、分断が解消されれば、あるキーを提供しているすべてのノードが、その値について合意することが保証されます。
ただし、マージ戦略は「最後に行われたフェイルオーバーが勝つ」です。そのため、ネットワーク分断中に受け付けた書き込みが失われることがあります。よくある例は、マスターが少数側の分断領域に取り残され、クライアントがそのマスターに書き込もうとする場合です。分断が解消されたとき、多数側でスレーブがこのマスターに代わって昇格していれば、古いマスターが受け付けた書き込みは失われます。
この設計により、Redis Clusterは値のマージを試みるためにデータ構造の中へメタデータを持たせる必要がありません。また、Redisがサポートする高度なコマンドやデータ構造も、Redis Clusterでそのまま利用できます。つまり、追加のメモリオーバーヘッドも、APIの制限も、1つの値に格納できる要素数の制限もありません。その代わり、ネットワーク分断中の安全性は低くなります。
Redis Clusterのように設計されたシステムでは、ノード間で状態が食い違うのは望ましくありません。そこでシステムは、2つのノードの状態が食い違う確率と、その食い違いの量を抑えることで、こうした弱点を軽減しようとします。具体的には、次のような方法を使います。
- ネットワーク分断の少数側は利用不能になります。
- 通常、クライアントへの応答とスレーブへのレプリケーションストリームが同時に送信されるよう、レプリケーションを設計しています。
- マスターのフェイルオーバーが可能なスレーブが複数ある場合、停止したマスターとの状態のずれが最も小さいと考えられるスレーブを選ぼうとします。
これらの戦略によってシステムの理論上の性質が変わるわけではありません。しかし、Redis Clusterでよく起きる障害モードに対して、現実の運用における保護を多少強化できます。
RedisのAPIとユースケースを考えれば、この設計には意味があると私は思っています。しかし、過去には多くの人が異なる意見を持っていました。とはいえ、設計者はそれぞれ望むようにシステムを設計してよい、と私は考えています。ルールはただ一つ、真実を語ることです。そのためRedis Clusterでは、公式ドキュメントで制限と障害モードを明確に説明しています。
システムが役に立つかどうかを決めるのは、ユーザーと、そのときのユースケースです。クラスタリングのサポートがまったくなかったにもかかわらず、6年経ってもユーザーがRedisを使い続けていたのは、ユースケースによってそれが可能だったからだと私は感じています。Redisには、特定の問題に取り組むのに非常に適した、固有の機能と性能がありました。Redis Clusterによって、そうしたユーザーの多くの生活が少しでも楽になることを願っています。
これからの道のり
ようやく、出荷できる最小限の実用的な製品ができました。ユーザーが本格的にテストを始められ、場合によってはすでに採用できる程度には安定しています。採用が進めば進むほど、さらに改善できます。RedisとSentinelで、私はこのことを知りました。これからは、ソフトウェアを使える状態から成熟した状態へと前進させる、段階的なプロセスが始まります。ユーザーの声に耳を傾け、バグを修正し、テストでカバーするコードを増やす、といったことです……。
同時に、Redis Clusterの次のバージョンについても考え始めています。現在は追加できなかった便利な機能を多数盛り込み、v1を改善する予定です。たとえば、複数データセンターのサポート、コマンドのリプレイによって少数側の分断領域で書き込みの安全性を高める機能、自動的なノード間のバランシングなどです。現在は、あるノードが空きすぎ、別のノードが詰まりすぎている場合、手動で再配置する必要があります。そのほかにも、多くの機能を考えています。
さらに、Redis Clusterはキャッシュ専用に設計した特別な実行モードから恩恵を受けられると考えています。このモードでは、ノードが担当していないハッシュスロットへの書き込みも受け付け、ネットワーク分断の少数側でも利用可能な状態を保ちます。
実装や設計を改善し、修正する時間はいつでもあります。しかし、ソフトウェアをどういうものにしたいかにこだわりすぎると、必要以上に長く「絵に描いた餅」のカテゴリーにとどまり続ける危険があります。もう、世に送り出す時です。Redis Clusterを楽しんでください。
Redis Cluster RC1は、Githubの3.0.0-rc1タグ、またはRedis.ioのダウンロードページ(http://redis.io/download)にあるtarballとして入手できます。
記事をランダムに読む