lettuce icon indicating copy to clipboard operation
lettuce copied to clipboard

RESP3 negociation in sentinel

Open fcerbell opened this issue 2 years ago • 1 comments

Lettuce 6.1.x Redis Enterprise 6.2

Lettuce tries to negociate the RESP protocol version, 2 or 3, using the "HELLO 3" command also on the sentinel connections. If the result is "ERR command not found", lettuce assumes that RESP3 is not supported and remains in RESP2. On the Redis side, in case of error, the result is "ERR xxx", and "xxx" is not in the specs, it can be any arbitrary string. For example, in Redis Enterprise, the database returns "ERR unknown command", but the sentinel returns "ERR command not found", both are perfectly valid answer. Lettuce receives from the sentinel "ERR command not found", but test the failure against the string "ERR unknown command", thus it does not detect the failure and switches to RESP3 which is not yet supported by Redis Enterprise sentinels.

The easiest fix is : receiving "ERR xxx", whatever the explanation string is, means that the server did not switch to RESP3 and that RESP2 should be used

Another solution would be to check the positive result of "HELLO 3" and use the "proto" value, if received.

These fixes make the negociation more robust. Currently, negociation fails with Redis Enterprise sentinels, the workaround is to inject a bean in the SpringApplication to force the protocolVersion value.

fcerbell avatar Feb 15 '22 08:02 fcerbell

Thanks for bringing this topic to our attention. Protocol fallback is based on an error response. We primarily intend to catch a narrow error and to not attempt a fallback on other errors (e.g. when the server has internal issues or a different error has happened). We can generalize the fallback to ERR and attempt another handshake. In case of internal server errors, the subsequent command would fail later and we would not fail early anymore.

Paging @itamarhaber.

mp911de avatar Feb 15 '22 09:02 mp911de

I tested Redis Sentinel with HELLO and it works for me:

127.0.0.1:26379> hello 3
1# "server" => "redis"
2# "version" => "255.255.255"
3# "proto" => (integer) 3
4# "id" => (integer) 4
5# "mode" => "sentinel"
6# "modules" => (empty array)

Closing as done.

mp911de avatar Nov 22 '22 13:11 mp911de

For those who still run into this problem, the issue can be bypassed by configuring Lettuce to use ProtocolVersion.RESP2.

Example with RedisClient:

RedisURI redisUri = RedisURI.Builder.sentinel("node1.local", 8001, "mydb")
        .withSentinel("node2.local", 8001)
        .withSentinel("node3.local", 8001)
        .build();
RedisClient client = RedisClient.create(redisUri);
client.setOptions(ClientOptions.builder().protocolVersion(ProtocolVersion.RESP2).build());
StatefulRedisConnection<String, String> conn = client.connect();

Example with LettuceConnectionFactory:

RedisSentinelConfiguration rsc = new RedisSentinelConfiguration().master("mydb")
        .sentinel("node1.local", 8001)
        .sentinel("node2.local", 8001)
        .sentinel("node3.local", 8001);
return new LettuceConnectionFactory(rsc, LettuceClientConfiguration.builder().clientOptions(
        ClientOptions.builder().protocolVersion(ProtocolVersion.RESP2).build()
).build());

nerg4l avatar Dec 05 '22 08:12 nerg4l