2019年におけるRedis開発についてのアップデート
原文は Salvatore Sanfilippo により に公開されました。 このブログを購読する
昨日、ある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インスタンスが1つあり、それを使って新しいこと、例えば遅延ジョブの処理をしようと計画しているとする。インターネットからライブラリを入手し、うまく動いているように見える。だが、一行一行を把握しているわけでもないそのライブラリに、なぜ“FLUSHALL”を呼び出してデータベースを一瞬で吹き飛ばす権限を与える必要があるのか。もしかするとライブラリのテストコードの中にそのコマンドが含まれていて、手遅れになってから気づくかもしれない。あるいは、入ったばかりのジュニアな開発者がRedisインスタンスに対して“KEYS *”を呼び続けているのに、社内のRedisポリシーは「KEYSコマンド禁止」だった、ということもあり得る。
別のシナリオとしてクラウドプロバイダーが挙げられる。彼らは管理用コマンドを慎重にリネームし、さらには何らかの理由でそうしたコマンドが漏洩しないよう隠蔽する必要さえある。さらなるトリックとして、例えばMONITORの出力にコマンドが表示されないようにするといったことも行われている。ACLがあれば、認証なしのデフォルトユーザーには管理系や危険なコマンドは何も実行させないようにRedisを設定できる。これは運用にとって大きな改善になると考えている。
さらに、私の知る限りACLは私がRedisのために書いた中でも最高のコードの一つだ。キーパターンを使わない限りCPUコストはほぼ皆無で、使ったとしてもわずかだ。実装はacl.cファイルの中に完全に自己完結しており、コアの残りの部分はACL APIへの数回の呼び出しがあるだけだ。完全にモジュール化されているため、システムに複雑さを加えることもない。実際、ACLのコードのおかげでAUTHコマンド周辺の優れたリファクタリングも可能になった。
マルチスレッド
Redisが対応し得るマルチスレッド化には2つの可能性がある。ユーザーが指しているのは「memcachedのような」マルチスレッド、つまりGETやSETをはじめとするシンプルなコマンドで、1秒あたりに処理できる操作数を増やすために単一のRedisインスタンスを複数スレッドにスケールさせる能力のことだと思う。これは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でまず計画されているのは、一部の実装を変更することで、既存のものをよりメモリ効率良くすることだ。しかし、新しいデータ構造を追加するには多くの検討が必要になる。時系列やストリーミングという文脈で、Streamsによってリスト、pub/sub、ソート済みセット間のギャップをどう埋めるかを理解するのに、私は何年もかかった。私はRedisを、ユーザーが組み合わせて使える直交するデータ構造の集合であってほしいと思っており、すぐに使える*ツール*の集合であってほしいわけではない。Streamsは抽象的なログであり、だからこそ追加する価値が十分にあると考えている。しかし、他のものについては、長い検討なしにコアに入れる価値があるかどうか、完全には確信が持てない。いずれにせよ、ここ数年は新しいデータ構造の追加が確実に重視されてきた。HyperLogLog、より高度なビット操作、Streams、ソート済みセットのブロッキング操作(ZPOP*やBZPOP*)、そしてStreamsがその好例だ。
結論
Redisコミュニティは、何かがなぜ行われ、何かがなぜ先送りされるのかを認識すべきだと考えている。私はついTwitterで、みんながそこにいるかのように多くを発信してしまうという失敗を犯すが、多くの人はたまたま現実の生活があって :-D 気にしていない。ブログはコミュニティに知らせるはるかに良い手段であり、もっとブログを書く時間を作る必要がある。ちなみに記事を書くのは大好きなので、一石二鳥だ。理解しておくべき重要なことは、Redisには確固としたロードマップがないということだ。長年の経験から、ロードマップを持つよりも機会主義的な開発の方がはるかに有効だと分かってきた。何かが求められているか。必要性を感じるか。コードを書く気分か。他に大きな優先事項がないから今が適切なタイミングか。設計プロセスを手伝い、ヒントやアイデアを与え、テストしてくれるユーザー層がいるか。今がその時だ、やろう。Redisに確固としたロードマップを持つことは馬鹿げている。OSSコアチームの規模は小さく、時にはランダムなクラッシュ一つに何週間も足止めされることもあるのだから。固定的な長期計画など機能しない。さらに、Redisコミュニティからフィードバックをもらうことで私の考えは大きく変わるため、毎月ロードマップを書き直すことになるだろう。それでも、ブログは少なくとも現在の優先事項やアイデアがどのバージョンなのかを示し、なぜ他のアイデアが放棄されたのかを示す良い解決策だ。
最後に一言。オープンソースプロジェクト側に何を入れるかについて、Redis Labsから与えられている自由度はほぼ無限だ。これは業界ではある種の奇跡だと思うし、あるいは単にRedis Labsで一緒に働いている人々が、我々のやっていることがオープンソースのムーブメントから生まれたものであり、その形で続けていくのが賢明だと理解している素敵な人たちだということなのかもしれない。ただ、これは一般的なことではない。もし私がRedisのロードマップで間違いを犯すとすれば、それは確実に私自身の間違いだ。
記事をランダムに読む
コメント
ログインしてコメントする