Why RESP3 will be the only protocol supported by Redis 6

Salvatore Sanfilippo

なぜRedis 6でサポートされるプロトコルはRESP3だけになるのか

[EDIT! Stack OverflowのMarc Gravell氏から、後方互換性のためにコネクションごとにプロトコルを切り替え、RESP3を有効にするコマンドを送ればよいのではないかという提案があり、この件全体について考え直しています。つまり、サーバーの挙動を切り替えるグローバルな設定が不要になるということです。この形であれば私にとってはずっと受け入れやすく、記事の趣旨自体を見直しているところです]

Redis 5のリリースから数週間が経ち、私はRESP3の実装に着手しました。数日作業してみて、ついにこれが実現しつつあるのを目の当たりにするのは実に気持ちのよいものです。RESP3は、Redis 6から採用される新しいクライアント・サーバー間のプロトコルです。仕様はhttps://github.com/antirez/resp3で公開しており、古いプロトコルであるRESP2からどのように進化し、Redisエコシステムをどう改善するのかを明確に説明しているはずです。ただ、最も重要な点を一言でいえば、RESP3はRESP2よりも「セマンティック」になったということです。たとえば、mapやset(順序を持たない要素のリスト)、返されるデータに付随するattributesといった概念があり、付加的な情報でリプライを補強できるといった具合です。最終的なゴールは、新しいRedisクライアントが担う作業を減らすことです。つまり、あらかじめ決められた一連のルールに従って、RESP3のあらゆるリプライ型を、そのクライアントライブラリの言語における適切な型へ変換するだけで済むようにすることです。

私が思い描くRedisの未来では、クライアントは内部ではより賢く、コネクションやパイプライン、状態の管理に最善を尽くす一方で、ユーザーが触れる側では一見するとずっとシンプルになります。理想的なRedisクライアントは、たとえば次のような形です。

result = redis.call(“GET”,keyname);

もちろん、その上にさらに高度な抽象化を築くこともできます。ただ、最も下のレイヤーはこのような見た目であるべきですし、返されるリプライもコマンドごとに場当たり的なフィルタリングを必要とすべきではありません。RESP3のリターン型自体が、適切なデータ型を返すのに十分な情報を持っているべきなのです。たとえばHGETALLはRESP3の「map」を返し、LRANGEは「array」を、EXISTSはRESP3の「boolean」を返す、といった具合です。

これにより、新しいコマンドも、クライアントライブラリが*わざわざ*そのために設計されていなくても期待どおりに動作するようになります。RESP2では、たとえば「method missing」のような仕組みを使ってコマンド自体は動いたとしても、あとからそのコマンドがクライアントライブラリに*正式に*実装された際に、返される型が変わってしまい、微妙な非互換が生じることがありました。

ただ、新しいプロトコルは旧プロトコルからの段階的な改善ではあるものの、クライアントライブラリ側はもちろん、*アプリケーション層*にも破壊的な非互換をもたらします。たとえばZSCOREは、これまで文字列を返していましたが、今後はdoubleを返すようになるため、アプリケーション側のコードを更新する必要があります。あるいは、クライアントライブラリ側でRESP3のリプライを元のRESP2の型に戻す互換オプションを用意するという手もあります。

Luaスクリプトも、新しいプロトコルに合わせて修正しなければ動かなくなります。redis.call()が返す型も、よりセマンティックなものになるからです。同様に、LuaからもRESP3で実装されたすべての新しいデータ型を返せるようになります。

こうした事情から、私の決断を不安に思う人もいます。私はRedis 6を*RESP3のみ*をサポートする形で出荷するつもりです。Redis 6のサーバーをRESP2に切り替える互換モードは用意しません。つまり、クライアントライブラリをアップグレードし、アプリケーションもアップグレードする(あるいはクライアントライブラリの後方互換モードを使う)か、さもなければRedis 6には移行できないということです。

そうするのには十分な理由があります。なぜこの決断をしたのか、そしてユーザーやクライアントライブラリの作者のためにどのような緩和策を用意しているのかを説明したいと思います。まずは緩和策から見ていきます。

  • Redis 5は、Redis 6のリリース後も2年間は完全にサポートされます。重要な修正はすべてRedis 5にバックポートされ、パッチリリースが継続的に提供されます。
  • Redis 6のリリースは1年から1年半後になる見込みです。ただ、Redis 6がRESP3に切り替わるのは約1か月後の予定です。つまり、不安定版(unstable)のRedisが新しいプロトコルを使った状態で、長い期間使われ、試され、検証されることになります。他の多くのソフトウェアとは異なり、RedisのunstableはGitHubでデフォルトのブランチであることも、そもそも伝統的にそれほど不安定ではないこともあって、カジュアルに使う人が大勢います。そのため、事前に広く触れられる機会が十分に確保されるはずです。
  • まだ100%決めたわけではありませんが、LuaスクリプトエンジンにはRedis 5と同じ型を返す互換モードを用意するかもしれません。ただしこの互換性はデフォルトでは有効にならず、スクリプトごとにオプトインする形になります。具体的には、Redisコマンドを呼ぶ前に特別なredis.resp2_compat()関数を呼び出すことで有効にします。そうすることで、設定にかかわらずすべてのRedis 6サーバーが同じように振る舞うことになります。これは過去10年間のRedisが常にそうであったのと同じです。

以上が緩和策です。次に、なぜRedis 6で両方のバージョンをサポートしないのか、その理由を説明します。

  1. ほぼ完全に無意味だからです。もしRedis 6をRESP2モードに切り替えられるようにしても、結局は過去に留まるだけで、RESP2のサポートがないRedis 7が出たときにすべてが壊れるのを待つだけになります。その間、Redis 6のインストールごとに*何を返すのか*が設定によって変わってしまうため、同じクライアントライブラリでも同じコマンドに対してHashを返したりArrayを返したりすることになりかねません。
  2. 正当な理由もなく、作業と複雑さが増すからです(「1」を参照)。多くのコマンドで、どの形式でリプライすべきかを判断するために旧プロトコルかどうかのチェックが必要になります。
  3. Redis 6の新機能とプロトコル変更をセットにすることで、ユーザーに移行してクライアントやアプリケーションを移植する十分な動機を与えられるからです。いずれすべてが終われば、私たちは新しいことに集中できます。そうでなければ、Redis 6の新機能を求めて新しいサーバーに切り替えたのに古いプロトコルを使い続けるユーザーが一定数残り、Redis 7でも同じ混乱が繰り返されることになります。
  4. クライアントライブラリの対応は大変だと言う人もいるかもしれませんが、私はそうは思いません。たしかに変更は必要ですが、今まさにサーバー側を実装している身からすると、そこまで大変なものではありません。むしろ大変なのは、ほとんどのクライアント開発が無償で、情熱と共有の精神だけで成り立っているということです。RESP3の実装はすぐに数多く現れてくると確信しています。
  5. RESP3は、クライアントが自動的にRESP2なのかRESP3なのかを検出し、切り替えられるように設計されています。そのため、新しいクライアントはRedis <= 5でもRedis 6でも動作します。

以上です。私の考えとその理由、そしてプロトコル切り替えにあたって用意する緩和策が、決して「ハード」な断絶にはならないことをご理解いただく一助になれば幸いです。

原文は Salvatore Sanfilippo により に公開されました。

この記事は「muse-spark-1.2-contributor」を使用して翻訳されました。