2019年におけるRedis開発の近況について
昨日、あるRedisユーザーがHacker Newsに次のような投稿をしました。
— https://news.ycombinator.com/item?id=19204436 —
Redisは大好きなのですが、現在開発中のいくつかの変更には少し懐疑的です。respv3プロトコルには、一見面白そうではあるものの、クライアントライブラリのコードを大幅に複雑化させかねない機能があります。また、きめ細かいACLの開発にも多くの労力が費やされています。なぜこれが必要なのか、あるいはマルチスレッド対応、より良い永続化モデル、データ型といった他の変更よりも優先度が高いのか、想像がつきません。
— ユーザーのコメントここまで —
彼なのか彼女なのか(よくわかりませんが)、ACLをRedis Labsの意向、つまり「エンタープライズユーザー向け」といった理由で押し付けられた機能のように見ているのは、この方だけではないような気がしています。また、コメント内の他の指摘も興味深いものです。Redisコミュニティに対して今後の方向性を明確に伝えるためにも、これらすべてについてきちんとお答えする価値が大いにあると考えています。
わかりやすくするため、このブログ記事では元のコメントで言及された機能ごとに節を分けて説明していきます。
RESP3
以前このブログでも書いたとおり、RESP3の目的はむしろクライアントを取り巻く状況をシンプルにすることにあります。理想的には、すべてのクライアントが低レベルのレイヤーを持ち、そこで redis.call(“get”,”foo”) のような高レベルなインターフェースを独自に再発明しようとしなくて済むようにすることです。プロトコル自体が十分に意味を持つようになったため、呼び出し元に返す応答がどのような形であるべきかをクライアントに伝えられるようになり、変換処理をわざわざ組み立てる必要もなくなりました。また、ほとんどのコマンドについて、事前にコマンドの特徴を把握しておく必要もありません。ユーザーが懸念しているのは、おそらくRESP3の帯域外通信のサポート、すなわち応答の「attributes(属性)」のことだと思います。
私は、Redisの将来において「クライアントサイドキャッシュ」が大きなトピックになると本気で考えています。スケーラブルなシステムであれば当然の流れです。しかし、サーバー側の支援なしにクライアント側のキャッシュを無効化するのは悪夢のような作業になります。これが、RESP3が応答にattributesを持たせている主な理由です。とはいえ、おそらくRedis 6ではそのいずれも実装されません。Redis unstable(のちのRedis 6)にはすでにほぼ完成したRESP3の実装がありますが、そこにattributesはありません。RESP3を実装するクライアントは、本当に将来に備えたいのであればattributesを単に破棄すると決めてしまえばよく、仮に将来のRedisバージョンでも、ユーザーが何らかの特別な機能を有効にしない限り、attributesが送られることはまずないでしょう。たとえばクライアントサイドキャッシュであれば、コネクションを特別なモードに切り替える必要があります。さらに、ご存知のとおりRedis 6はRESP2との完全な後方互換性を保ちます。実際、RESP2のサポートは今後も決して削除されないのではないかとさえ考え始めています。実装コストはほぼゼロに等しく、RESP2とRESP3の間に抽象化レイヤーを実装する苦労をした以上、後方互換性をわざわざ壊す理由がないからです。
普段、私は正当な理由なく物事を変えることを好みません。しかし、RESP2の制約はクライアントのエコシステムに大きな影響を与えていました。私が目指しているのは、ユーザーがどのクライアントから別のクライアントに移っても違和感なく使え、APIがクライアント作者の考えた層ではなく、Redis本来のAPIであるような世界です。もちろん、低レベルなAPIに加えて高レベルなAPIが存在すること自体には反対ではありません。ただ、共通の基盤があるべきですし、クライアントはそのコマンドについて何も知らなくてもコマンドを送信できるべきだと考えています。
ACL
ACLの仕様は、私自身が4年前に起草したものです。実装するのに本当に今が適切な時期なのかを確信するまで、長い時間をかけました。これまでACLなしでも、主にコマンドのリネームといった工夫でなんとかやってこれました。しかし、ACLの主な動機がセキュリティを求めるエンタープライズ顧客にあるとは考えないでください。副次的な効果として、ACLはセキュリティ目的でのユーザー認証も可能にしますが、この機能の主目的は運用上のものなのです。
例を挙げてみましょう。Redisインスタンスが一つあり、そこで新しいこと、たとえば遅延ジョブの処理を始めようとしています。インターネットからライブラリを入手し、一見うまく動いているように見えます。ところで、一行一行まで把握していないそのライブラリが、なぜ「FLUSHALL」を呼び出してデータベースを一瞬で消し去ることができてしまうのでしょうか。もしかするとライブラリのテストの中にそのコマンドが含まれていて、手遅れになってから気づくかもしれません。あるいは、入社したばかりのジュニアな開発者がRedisインスタンスに対して「KEYS *」を連発していて、社内のRedisポリシーでは「KEYSコマンド禁止」となっている、といったこともあるでしょう。
別のシナリオとして、クラウドプロバイダーの場合があります。彼らは管理コマンドを慎重にリネームし、さらには何らかの理由でそれらのコマンドが漏洩しないように隠蔽する必要さえあります。さらなる工夫として、たとえばMONITORの出力にコマンドが表示されないようにするといったことも行われています。ACLを使えば、認証なしのデフォルトユーザーには、管理系や危険なコマンドは何も実行させないようにRedisを設定できます。これは運用にとって大きな改善になると考えています。
さらに、私の知る限り、ACLは私がRedisのために書いた中でも最も出来の良いコードの一つです。キーパターンを使用する場合を除きCPUコストはほぼゼロですし、使用したとしてもごくわずかです。実装はacl.cファイルの中に完全に自己完結しており、コアの残りの部分にはACL APIへの数回の呼び出しがあるだけです。完全にモジュール化されているため、システムに複雑さを加えることもありません。実際、ACLのコードのおかげで、AUTHコマンド周辺の良いリファクタリングも実現できました。
マルチスレッド
Redisが対応し得るマルチスレッド化には、2つの可能性があります。ユーザーが指しているのは、おそらく「memcachedのような」マルチスレッド化、すなわち単一のRedisインスタンスを複数スレッドでスケールさせ、GETやSETといったシンプルなコマンドなどで1秒あたりの処理数を増やす機能のことだと思います。これにはI/Oやコマンドのパースといった部分をマルチスレッド化することが含まれます。これをここでは「I/Oスレッディング」と呼ぶことにします。
もう一つのマルチスレッド化のアプローチは、遅いコマンドを別スレッドで実行できるようにし、他のクライアントがブロックされないようにするものです。このスレッドモデルを「スローコマンドのスレッディング」と呼びます。
さて、計画はこうです。私の知る限り、I/OスレッディングはRedisでは実現しません。よく検討した結果、正当な理由がないまま複雑さを大幅に増やすことになると考えたからです。実際、多くのRedis環境はネットワークやメモリがボトルネックになっています。加えて、私はシェアードナッシングな構成を強く信じています。そのため、私が考えるRedisのスケール方法は、同じホスト上で複数のRedisインスタンスを実行するためのサポートを改善することであり、特にRedis Clusterを通じてそれを実現したいと考えています。2019年にその点で予定されていることは2つあります。
A) Redis Clusterの複数インスタンスが連携し、ローカルインスタンスのディスクを賢く使えるようになります。つまり、AOFの書き換えが同時に発生しないようにする、ということです。
B) Redisプロジェクトの一環としてRedis Clusterプロキシを提供する予定です。これにより、ユーザーはクライアント側でClusterプロトコルの優れた実装を持たなくても、クラスターを抽象化して利用できるようになります。
もう一つ指摘しておきたいのは、RedisはMemcachedではないものの、memcachedと同様にインメモリシステムであるということです。memcachedのような非常にシンプルなデータモデルを持つインメモリシステムをマルチスレッド化するのは大いに理にかなっています。オンディスクのストアでマルチスレッド化が必須であるのも当然です。複雑なインメモリシステムのマルチスレッド化は、その中間にあって事態が厄介になる領域です。Redisのクライアントは分離されておらず、データ構造も複雑です。LPUSHを実行するスレッドは、LPOPを実行する他のスレッドに対応する必要があります。得られるものは少なく、追加される複雑さは大きいのです。
それよりも私が本当に強く望んでいるのは、遅い処理のスレッディングです。Redisモジュールシステムによって、すでに正しい方向に進んでいます。ただ、将来(Redis 6になるか7になるかはわかりませんが)、モジュールシステムにキーレベルのロックが導入され、スレッドがキーの制御を完全に取得して遅い処理を行えるようになります。現在、モジュールはコマンドを実装し、クライアントへの応答を完全に分離された形で作成できますが、共有データセットにアクセスするには依然としてグローバルロックが必要です。これは将来的になくなる予定です。
より良い永続化
最近、こうしたRedisの根幹的な機能を改善するために何度も取り組みを行ってきました。最近実装されたものの中で最も優れたものの一つが、AOFファイル内のRDBプリアンブルです。また、Redis 4と5の両方でレプリケーションについても多くの作業が行われ、以前とはまったく別次元のものになりました。そして、こうした部分の改善は今も私の主要な注力分野の一つです。
データ構造
現在、RedisはRedis 5からStreamsを備えています。Redis 6および7では、まずいくつかの実装を変更することで、既存のものをよりメモリ効率の良いものにすることが計画されています。ただ、新しいデータ構造を追加するには多くの検討が必要です。時系列やストリーミングという文脈で、リスト、pub/sub、ソート済み集合の間の隙間をStreamsでどう埋めるべきかを理解するのに、私は何年もかかりました。私は、Redisをユーザーが組み合わせて使える直交したデータ構造の集合としたいのであり、すぐに使える道具の寄せ集めにはしたくありません。Streamsは抽象的なログであり、非常に価値のある追加だと考えています。ただ、それ以外のものについては、長い検討を経なければコアに入れる価値があるかどうか確信が持てません。いずれにせよ、ここ数年は新しいデータ構造の追加に確実に力が注がれてきました。HyperLogLog、より高度なビット操作、Streams、ブロッキングなソート済み集合操作(ZPOP*やBZPOP*)などが良い例です。
結論
Redisコミュニティは、何がなぜ行われ、何がなぜ先送りされるのかを把握しているべきだと考えています。私はついTwitterで盛んに発信してしまい、まるでみんながそこにいるかのように振る舞ってしまうのですが、多くの人はちゃんと生活があって :-D 、そんなことは気にしていなかったりします。ブログの方がコミュニティに知らせるにはずっと良い方法です。もっとブログを書く時間を確保しなければなりません。ちなみに私は記事を書くのが大好きなので、一石二鳥です。理解しておいていただきたい重要なこととして、Redisには堅固なロードマップがありません。長年の経験から、ロードマップを持つよりも、機会を捉えた開発の方がはるかに有益だとわかってきました。需要があるか? 必要性を感じるか? コードを書く気分になっているか? 他に大きな優先事項がなく、今が適切なタイミングか? 設計プロセスを手伝い、ヒントやアイデアをくれたり、テストをしてくれるユーザーたちがいるか? 今がその時だ、やろう、という具合です。Redisに固定的なロードマップを持つのは馬鹿げています。OSSのコアチームは小規模で、時にはランダムなクラッシュ一つに何週間も足止めされることもあります。固定的な長期計画など機能しません。さらに、Redisコミュニティからフィードバックをもらうことで私の考えも大きく変わるので、毎月ロードマップを書き直すことになってしまいます。それでも、ブログは少なくとも現在の優先事項やアイデアがどのバージョンにあるのか、そしてなぜ他のアイデアが見送られたのかを示す良い手段です。
最後に一言。オープンソースプロジェクト側に何を入れるかについて、Redis Labsから与えられている自由度はほぼ無限です。これは業界ではある種の奇跡のようなことだと思いますし、あるいは単に、私が一緒に働いているRedis Labsの人たちが、私たちのやっていることはオープンソースのムーブメントから生まれたものであり、そのまま続けていくことが賢明だと理解してくれている、優れた人たちだからなのかもしれません。しかし、これは決して一般的ではありません。Redisのロードマップで何か間違いを犯すとすれば、それは確実に私自身の間違いです。
記事をランダムに読む