CLOVER🍀

That was when it all began.

Infinispan 15.0のRESPエンドポイント(Redisプロトコル)でサポートされているコマンドとクラスタリングを確認してみる

これは、なにをしたくて書いたもの?

Infinispan 15.0.0.Finalがリリースされました。

Infinispan 15.0.0.Final

今回は変更内容が山盛りになっていてちょっと驚くのですが、RESPエンドポイント(Redisプロトコル)でサポートされているコマンドが
大量に増えているので、こちらを確認してみたいと思います。
あと、クラスタリングもやってみようと思います。

InfinispanのRESPエンドポイント

InfinispanのRESPエンドポイントは、Infinispan 14.0で追加されたRedis互換のエンドポイントです。

Infinispan 14.0.0.Final

Infinispan 14.0のRESPエンドポイントをRedis CLIで試す - CLOVER🍀

つまり、Infinispan ServerをRedisの代わりとして使うことができます。

Infinispan 14.0のリリースブログの時には少し名前とサポートしているコマンドが出ていたくらいなのに、15.0になってだいぶクローズアップ
されている感じがします。

Infninspan 15.0のリリースブログでは、Redisのほとんどのデータ型と90以上のコマンドを実装していて、以下の点が通常のRedisと
比較した際の利点としています。

  • マルチスレッド … クラスタリングをしなくても複数のコアを活用できる
  • P2Pクラスタリングクラスター内のノードにはマスター/レプリカの区別はなく、実行中のノードの追加/削除も容易
  • 専用の設定を行ったキャッシュ … 複数のキャッシュを定義でき、それぞれ異なる設定が可能。クラスターでも動作する
  • クロスサイトレプリケーションのサポート
  • 複数の永続化オプション … ディスク、データベースやその他の外部ストアに保存可能
  • 外部のIDプロバイダーとの統合 … OAuth2、LDAP、Kerberos、クライアント証明書による認証/認可
  • 完全なパイプライン受信処理によるバッチ処理スループットの向上
  • Kubernetes Operator

など。

RESPエンドポイントで複数のキャッシュが使えるということですが、Infinispan ServerのエンドポイントにひとつのRedis用キャッシュを
紐付ける設定だったと思うので、複数のRedis用キャッシュを扱おうと思うとInfinispan Serverにエンドポイントを複数定義する必要が
あるように思いますね…。

InfinispanのRESPエンドポイントのドキュメントはこちら。

Using the RESP protocol endpoint with Infinispan

15.0の時点でも、実験的モジュール扱いなことには注意が要りそうですね。

Infinispan Server includes an experimental module that implements the RESP3 protocol. The RESP endpoint allows Redis clients to connect to one or several Infinispan-backed RESP servers and perform cache operations.

ところで、Redisコマンドのサポートが増えたという話でした。

Infinispan 14.xの頃からすると、それはもうびっくりするくらい増えています。

ドキュメントからサポートしているコマンドを抜き出してみます。

Infinispan 14.xの頃は16個でした。

$ curl -s https://infinispan.org/docs/14.0.x/titles/resp/resp-endpoint.html | grep redis.io | perl -wp -e 's!.+<a href.+">(.+?)</a>.+!$1!' | sort | wc -l
16

Infinispan 15.0ではなんと154個になっています。

$ curl -s https://infinispan.org/docs/15.0.x/titles/resp/resp-endpoint.html | grep redis.io | perl -wp -e 's!.+<a href.+">(.+?)</a>.+!$1!' | sort | wc -l
154

インメモリー・データグリッドでRedis互換の機能を持つものはありましたが、ここまで多くコマンドをサポートしているものは
見たことない気がしますね。

ところで、そもそもRedisのコマンドっていくつあるのでしょう?

Commands | Redis

確認してみます。この時点でのRedisのバージョンは、7.2.4です。

$ curl -s https://redis.io/commands | perl -wnl -e 'if (m!.+<h1 class.+>! .. m!</h1>!) { if (not /h1/) { s!^ +!!g; print } }' | sort | wc -l
464

464個あるらしいです…。154個実装しても、まだ半分にも満たない感じですね…。

差分をとってみます。

$ diff <(curl -s https://infinispan.org/docs/15.0.x/titles/resp/resp-endpoint.html | grep redis.io | perl -wp -e 's!.+<a href.+">(.+?)</a>.+!$1!' | sort) <(curl -s https://redis.io/commands | perl -wnl -e 'if (m!.+<h1 class.+>! .. m!</h1>!) { if (not /h1/) { s!^ +!!g; print } }' | sort)

Infinispanにしかないものってあるんでしょうか?

$ diff <(curl -s https://infinispan.org/docs/15.0.x/titles/resp/resp-endpoint.html | grep redis.io | perl -wp -e 's!.+<a href.+">(.+?)</a>.+!$1!' | sort) <(curl -s https://redis.io/commands | perl -wnl -e 'if (m!.+<h1 class.+>! .. m!</h1>!) { if (not /h1/) { s!^ +!!g; print } }' | sort) | grep '<'
< CLIENT GETINFO
< MEMORY-STATS
< MEMORY-USAGE

ありました…。

RedisにCLIENT GETINFOというコマンドはないようなので、間違っている気がします…。

ソースコードを見ると、これはCLIENT GETNAMEの誤りみたいです。

https://github.com/infinispan/infinispan/blob/15.0.0.Final/server/resp/src/main/java/org/infinispan/server/resp/commands/connection/CLIENT.java

これはPull Requestを出しておきました。

残り2つは、「-」があるので差分として出ているようです。

Redisのみにある方はこんな感じです。

$ diff <(curl -s https://infinispan.org/docs/15.0.x/titles/resp/resp-endpoint.html | grep redis.io | perl -wp -e 's!.+<a href.+">(.+?)</a>.+!$1!' | sort) <(curl -s https://redis.io/commands | perl -wnl -e 'if (m!.+<h1 class.+>! .. m!</h1>!) { if (not /h1/) { s!^ +!!g; print } }' | sort) | grep '>'
> ACL CAT
> ACL DELUSER
> ACL DRYRUN
> ACL GENPASS
> ACL GETUSER
> ACL LIST
> ACL LOAD
> ACL LOG
> ACL SAVE
> ACL SETUSER
> ACL USERS
> ACL WHOAMI
> ASKING
> BF.ADD
> BF.CARD
> BF.EXISTS
> BF.INFO
> BF.INSERT
> BF.LOADCHUNK
> BF.MADD
> BF.MEXISTS
> BF.RESERVE
> BF.SCANDUMP
> BGREWRITEAOF
> BGSAVE
> BITCOUNT
> BITFIELD
> BITFIELD_RO
> BITOP
> BITPOS
> BLMOVE
> BLMPOP
> BRPOPLPUSH
> BZMPOP
> BZPOPMAX
> BZPOPMIN
> CF.ADD
> CF.ADDNX
> CF.COUNT
> CF.DEL
> CF.EXISTS
> CF.INFO
> CF.INSERT
> CF.INSERTNX
> CF.LOADCHUNK
> CF.MEXISTS
> CF.RESERVE
> CF.SCANDUMP
> CLIENT CACHING
> CLIENT GETNAME
> CLIENT GETREDIR
> CLIENT KILL
> CLIENT NO-EVICT
> CLIENT NO-TOUCH
> CLIENT PAUSE
> CLIENT REPLY
> CLIENT TRACKING
> CLIENT TRACKINGINFO
> CLIENT UNBLOCK
> CLIENT UNPAUSE
> CLUSTER ADDSLOTS
> CLUSTER ADDSLOTSRANGE
> CLUSTER BUMPEPOCH
> CLUSTER COUNT-FAILURE-REPORTS
> CLUSTER COUNTKEYSINSLOT
> CLUSTER DELSLOTS
> CLUSTER DELSLOTSRANGE
> CLUSTER FAILOVER
> CLUSTER FLUSHSLOTS
> CLUSTER FORGET
> CLUSTER GETKEYSINSLOT
> CLUSTER INFO
> CLUSTER KEYSLOT
> CLUSTER LINKS
> CLUSTER MEET
> CLUSTER MYID
> CLUSTER MYSHARDID
> CLUSTER REPLICAS
> CLUSTER REPLICATE
> CLUSTER RESET
> CLUSTER SAVECONFIG
> CLUSTER SET-CONFIG-EPOCH
> CLUSTER SETSLOT
> CLUSTER SLAVES
> CMS.INCRBY
> CMS.INFO
> CMS.INITBYDIM
> CMS.INITBYPROB
> CMS.MERGE
> CMS.QUERY
> COMMAND COUNT
> COMMAND DOCS
> COMMAND GETKEYS
> COMMAND GETKEYSANDFLAGS
> COMMAND INFO
> COMMAND LIST
> CONFIG GET
> CONFIG RESETSTAT
> CONFIG REWRITE
> CONFIG SET
> COPY
> DUMP
> EVAL
> EVALSHA
> EVALSHA_RO
> EVAL_RO
> FAILOVER
> FCALL
> FCALL_RO
> FT.AGGREGATE
> FT.ALIASADD
> FT.ALIASDEL
> FT.ALIASUPDATE
> FT.ALTER
> FT.CONFIG GET
> FT.CONFIG SET
> FT.CREATE
> FT.CURSOR DEL
> FT.CURSOR READ
> FT.DICTADD
> FT.DICTDEL
> FT.DICTDUMP
> FT.DROPINDEX
> FT.EXPLAIN
> FT.EXPLAINCLI
> FT.INFO
> FT.PROFILE
> FT.SEARCH
> FT.SPELLCHECK
> FT.SUGADD
> FT.SUGDEL
> FT.SUGGET
> FT.SUGLEN
> FT.SYNDUMP
> FT.SYNUPDATE
> FT.TAGVALS
> FT._LIST
> FUNCTION DELETE
> FUNCTION DUMP
> FUNCTION FLUSH
> FUNCTION KILL
> FUNCTION LIST
> FUNCTION LOAD
> FUNCTION RESTORE
> FUNCTION STATS
> GEOADD
> GEODIST
> GEOHASH
> GEOPOS
> GEORADIUS
> GEORADIUSBYMEMBER
> GEORADIUSBYMEMBER_RO
> GEORADIUS_RO
> GEOSEARCH
> GEOSEARCHSTORE
> GETBIT
> HGETALL
> HSTRLEN
> JSON.ARRAPPEND
> JSON.ARRINDEX
> JSON.ARRINSERT
> JSON.ARRLEN
> JSON.ARRPOP
> JSON.ARRTRIM
> JSON.CLEAR
> JSON.DEBUG
> JSON.DEBUG MEMORY
> JSON.DEL
> JSON.FORGET
> JSON.GET
> JSON.MERGE
> JSON.MGET
> JSON.MSET
> JSON.NUMINCRBY
> JSON.NUMMULTBY
> JSON.OBJKEYS
> JSON.OBJLEN
> JSON.RESP
> JSON.SET
> JSON.STRAPPEND
> JSON.STRLEN
> JSON.TOGGLE
> JSON.TYPE
> LASTSAVE
> LATENCY DOCTOR
> LATENCY GRAPH
> LATENCY HISTOGRAM
> LATENCY HISTORY
> LATENCY LATEST
> LATENCY RESET
> LOLWUT
> MEMORY DOCTOR
> MEMORY MALLOC-STATS
> MEMORY PURGE
> MEMORY STATS
> MEMORY USAGE
> MIGRATE
> MODULE LOAD
> MODULE LOADEX
> MODULE UNLOAD
> MONITOR
> MOVE
> OBJECT ENCODING
> OBJECT FREQ
> OBJECT IDLETIME
> OBJECT REFCOUNT
> PEXPIREAT
> PFADD
> PFCOUNT
> PFDEBUG
> PFMERGE
> PFSELFTEST
> PSYNC
> PUBSUB CHANNELS
> PUBSUB NUMPAT
> PUBSUB NUMSUB
> PUBSUB SHARDCHANNELS
> PUBSUB SHARDNUMSUB
> REPLCONF
> REPLICAOF
> RESTORE
> RESTORE-ASKING
> ROLE
> SAVE
> SCRIPT DEBUG
> SCRIPT EXISTS
> SCRIPT FLUSH
> SCRIPT KILL
> SCRIPT LOAD
> SETBIT
> SHUTDOWN
> SLAVEOF
> SLOWLOG GET
> SLOWLOG LEN
> SLOWLOG RESET
> SMISMEMBER
> SPUBLISH
> SREM
> SSUBSCRIBE
> SUNSUBSCRIBE
> SWAPDB
> SYNC
> TDIGEST.ADD
> TDIGEST.BYRANK
> TDIGEST.BYREVRANK
> TDIGEST.CDF
> TDIGEST.CREATE
> TDIGEST.INFO
> TDIGEST.MAX
> TDIGEST.MERGE
> TDIGEST.MIN
> TDIGEST.QUANTILE
> TDIGEST.RANK
> TDIGEST.RESET
> TDIGEST.REVRANK
> TDIGEST.TRIMMED_MEAN
> TFCALL
> TFCALLASYNC
> TFUNCTION DELETE
> TFUNCTION LIST
> TFUNCTION LOAD
> TOPK.ADD
> TOPK.COUNT
> TOPK.INCRBY
> TOPK.INFO
> TOPK.LIST
> TOPK.QUERY
> TOPK.RESERVE
> TOUCH
> TS.ADD
> TS.ALTER
> TS.CREATE
> TS.CREATERULE
> TS.DECRBY
> TS.DEL
> TS.DELETERULE
> TS.GET
> TS.INCRBY
> TS.INFO
> TS.MADD
> TS.MGET
> TS.MRANGE
> TS.MREVRANGE
> TS.QUERYINDEX
> TS.RANGE
> TS.REVRANGE
> UNLINK
> WAIT
> WAITAOF
> XACK
> XADD
> XAUTOCLAIM
> XCLAIM
> XDEL
> XGROUP CREATE
> XGROUP CREATECONSUMER
> XGROUP DELCONSUMER
> XGROUP DESTROY
> XGROUP SETID
> XINFO CONSUMERS
> XINFO GROUPS
> XINFO STREAM
> XLEN
> XPENDING
> XRANGE
> XREAD
> XREADGROUP
> XREVRANGE
> XSETID
> XTRIM
> ZMSCORE
> ZRANK
> ZREVRANK

まだまだたくさんありますね。HGETALLなどもあったりします。

これで今後も差分比較はできそうです。

ここからは、Infinispan Serverで少しRedisプロトコルを試してみましょう。

環境

今回の環境はこちら。

$ java --version
openjdk 21.0.2 2024-01-16 LTS
OpenJDK Runtime Environment Temurin-21.0.2+13 (build 21.0.2+13-LTS)
OpenJDK 64-Bit Server VM Temurin-21.0.2+13 (build 21.0.2+13-LTS, mixed mode, sharing)


$ bin/server.sh --version

Infinispan Server 15.0.0.Final (I'm Still Standing)
Copyright (C) Red Hat Inc. and/or its affiliates and other contributors
License Apache License, v. 2.0. http://www.apache.org/licenses/LICENSE-2.0

Infinispan Serverは172.18.0.3で動作しているものとして、以下のコマンドで起動させます。

$ bin/server.sh \
    -b 0.0.0.0 \
    -Djgroups.tcp.address=$(hostname -i)

Infinispan Serverには、以下のコマンドで管理用ユーザーとアプリケーション用ユーザーを作成しているものとします。

$ bin/cli.sh user create -g admin -p password ispn-admin
$ bin/cli.sh user create -g application -p password ispn-user

RedisへのアクセスにはRedis CLIを使います。

$ bin/redis-cli --version
redis-cli 7.2.4

Redis CLIからInfinispan ServerのRESPエンドポイントへ接続する

アプリケーション用ユーザーの認証情報を使って、RESPエンドポイントに接続します。

$ bin/redis-cli -h 172.18.0.3 -p 11222 --user ispn-user --pass password
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.

Infinispan Serverはデフォルトで認証必須なので、先にユーザーを作っておく必要があります。

で、接続さえできればあっさり使えます。

172.18.0.3:11222> set key1 value1
OK
172.18.0.3:11222> get key1
"value1"

このあたりは、14.0の時にも確認しているので同じですね。

Infinispan Serverにログインして、キャッシュの定義を確認しておきましょう。

$ bin/cli.sh -c http://ispn-admin:password@localhost:11222

キャッシュの一覧。

> ls caches
memcachedCache
___script_cache
respCache

respCacheというのは、Infinispan Serverが起動時に作成するデフォルトのキャッシュです。

確認。

> describe caches/respCache
{
  "respCache" : {
    "distributed-cache" : {
      "key-partitioner" : "org.infinispan.distribution.ch.impl.RESPHashFunctionPartitioner",
      "mode" : "SYNC",
      "statistics" : true,
      "encoding" : {
        "key" : {
          "media-type" : "application/octet-stream"
        },
        "value" : {
          "media-type" : "application/octet-stream"
        }
      }
    }
  }
}

Distributed Cacheになっています。確か、14.0の時はReplicated Cacheだったと思います。

定義が変わったのでしょうか?

そのようです。

            if (cacheManager.getCacheManagerConfiguration().isClustered()) { // We are running in clustered mode
               builder.clustering().cacheMode(CacheMode.DIST_SYNC);
               // See: https://redis.io/docs/reference/cluster-spec/#key-distribution-model
               builder.clustering().hash().keyPartitioner(new RESPHashFunctionPartitioner());
            }

https://github.com/infinispan/infinispan/blob/15.0.0.Final/server/resp/src/main/java/org/infinispan/server/resp/RespServer.java#L86-L90

こちらの変更ですね。

ISPN-15637 Make the respCache distributed by default by tristantarrant · Pull Request #11906 · infinispan/infinispan · GitHub

respcache should default to distributed

特に理由は書かれていませんが、デフォルトをDistributed Cacheにするべきだと判断したようです。

ちなみに、RESPエンドポイントで登録したデータをInfinipsan Serverでも見ることができます。

> cache respCache
> get key1
value1

クラスタリングしてみる

Infinispan 14.0の時はRESPエンドポイントに設定するキャッシュの変更をしてみたりしたのですが、今回はクラスタリングしてみたいと
思います。

ちょうど、デフォルトがDistributed Cacheになっていましたし。

Infinispan Serverを3ノード(172.18.0.3〜5)にします。

$ bin/cli.sh -c http://ispn-admin:password@localhost:11222
> ls /cluster
a65add5acb25-63023
06834bc0e989-48561
1ce7dbd93833-65191

Redis CLIから接続してみます。

$ bin/redis-cli -h 172.18.0.3 -p 11222 --user ispn-user --pass password
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.

CLUSTER NODESコマンドを実行してみます。

172.18.0.3:11222> cluster nodes
a65add5acb25-63023 0.0.0.0:11222@39380 master - 0 1710676040 9 connected 256-1087 5440-6015 7360-8511 9536-10367 10944-11775 11904-12543 13504-13887 14848-14911
06834bc0e989-48561 0.0.0.0:11222@39380 myself,master - 0 1710676040 9 connected 1344-2111 2368-2431 4096-4799 6080-7359 8512-9535 10368-10943 14208-14655 14976-15551 15680-15807
1ce7dbd93833-65191 0.0.0.0:11222@39380 master - 0 1710676040 9 connected 0-255 1088-1343 2112-2367 2432-4095 4800-5439 6016-6079 11776-11903 12544-13503 13888-14207 14656-14847 14912-14975 15552-15679 15808-16383

3ノード認識しています。Redis Clusterとして認識されている、ということでよさそうです。

データを10個ほど登録してみます。

172.18.0.3:11222> set key1 value1
OK
172.18.0.3:11222> set key2 value2
OK
172.18.0.3:11222> set key3 value3
OK
172.18.0.3:11222> set key4 value4
OK
172.18.0.3:11222> set key5 value5
OK
172.18.0.3:11222> set key6 value6
OK
172.18.0.3:11222> set key7 value7
OK
172.18.0.3:11222> set key8 value8
OK
172.18.0.3:11222> set key9 value9
OK
172.18.0.3:11222> set key10 value10
OK

取得。

172.18.0.3:11222> get key1
"value1"
172.18.0.3:11222> get key2
"value2"
172.18.0.3:11222> get key3
"value3"
172.18.0.3:11222> get key4
"value4"
172.18.0.3:11222> get key5
"value5"
172.18.0.3:11222> get key6
"value6"
172.18.0.3:11222> get key7
"value7"
172.18.0.3:11222> get key8
"value8"
172.18.0.3:11222> get key9
"value9"
172.18.0.3:11222> get key10
"value10"

Redis CLI-cオプションをつけなくても、ふつうに動きますね?

ここまでくると予想がつきますが、-cオプションをつけても特にリダイレクトしたりしません。

$ bin/redis-cli -h 172.18.0.3 -p 11222 --user ispn-user --pass password -c
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.

一応、Infinispan ServerのManagement Consoleで確認すると、データは各ノードに分散されて配置されているようです。

Redis Clusterではあるものの、データを取得する対象のノードに向けてリダイレクトが行われないRedis Clusterといった感じでしょうか。

まあ、なんとなくこうなる気はしていたので、予想通りではあるのですが。

ところで、こうなるともしかして1ノードの時はシングルノードのRedis Clusterとして認識されているのでしょうか?

$ bin/redis-cli -h 172.18.0.3 -p 11222 --user ispn-user --pass password
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
172.18.0.3:11222> cluster nodes
9b7484f2d27a-3856 0.0.0.0:11222@54440 myself,master - 0 1710676876 1 connected 0-16383

そのようです。

実装をもう少し

Infinispan 14.0で試した時も少し実装を見たのですが、15.0で見るとだいぶ変わっていそうです。

実装されているコマンドは、以下のパッケージ(サブパッケージ含む)にあります。

https://github.com/infinispan/infinispan/tree/15.0.0.Final/server/resp/src/main/java/org/infinispan/server/resp/commands

前はResp3Handlerなどにコマンド名が書かれていたのですが、だいぶ変わりましたね。

また、以前はRESPサーバーの実装にLettuceが使われたりしていましたが、今はそうではなさそうです。
Nettyをメインに使っている感じですね。

Redisのコマンドのデコード処理はNettyのデコーダーの仕組みで行われるようなのですが、

https://github.com/infinispan/infinispan/blob/15.0.0.Final/server/resp/src/main/java/org/infinispan/server/resp/BaseRespDecoder.java

https://github.com/infinispan/infinispan/blob/15.0.0.Final/server/resp/src/main/java/org/infinispan/server/resp/RespChannelInitializer.java#L39-L41

ここで使われているRespDecoderというクラスがないぞ?と思ったら、その実装は定義からパーサーを自動生成しているようです。

https://github.com/infinispan/infinispan/blob/15.0.0.Final/server/resp/src/main/resources/resp.gr

https://github.com/infinispan/infinispan/blob/15.0.0.Final/server/resp/pom.xml#L79-L95

こういう仕組みでできているんですね。

おわりに

Infinispan 15.0で実装コマンドが大きく増えた、RESPエンドポイントを試してみました。

さらっと使う分には特に14.xの時と変わりはないのですが、内部実装がだいぶ変わっている感じがします。

また14.xの時はクラスタリングも試していなかったので、実際に使うとどういう挙動になるのかちょっと気になっていたので確認する
いい機会になりました。

それにしても、この勢いでRedisのほとんどの機能をカバーするんでしょうか?

Redis、それからDragonflyあたりと競うことになったり?

Dragonfly - The Fastest In-Memory Data Store