Skip to content

ERR unknown subcommand 'MYID' with Azure Managed Redis (High Availablity) #3495

@KumarHari10

Description

@KumarHari10

Bug Report

Current Behavior

Intermittent Error / Exception when using RedisClusterConfiguration to Connect with Azure Managed Redis with High Availablity enabled.

Stack trace
io.lettuce.core.RedisCommandExecutionException: ERR unknown subcommand 'MYID'
 at io.lettuce.core.internal.ExceptionFactory.createExecutionException(ExceptionFactory.java:151)
 at io.lettuce.core.internal.ExceptionFactory.createExecutionException(ExceptionFactory.java:120)
 at io.lettuce.core.protocol.AsyncCommand.completeResult(AsyncCommand.java:124)
 at io.lettuce.core.protocol.AsyncCommand.complete(AsyncCommand.java:115)
 at io.lettuce.core.protocol.CommandWrapper.complete(CommandWrapper.java:67)
 at io.lettuce.core.protocol.CommandWrapper.complete(CommandWrapper.java:67)
 at io.lettuce.core.cluster.ClusterCommand.complete(ClusterCommand.java:50)
 at io.lettuce.core.protocol.CommandWrapper.complete(CommandWrapper.java:67)
 at io.lettuce.core.protocol.CommandWrapper.complete(CommandWrapper.java:67)
 at io.lettuce.core.protocol.CommandHandler.complete(CommandHandler.java:769)
 at io.lettuce.core.protocol.CommandHandler.decode(CommandHandler.java:704)
 at io.lettuce.core.protocol.CommandHandler.channelRead(CommandHandler.java:621)
 at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
 at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
 at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
 at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1519)
 at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1377) 
 at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1428)
 at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:530)
 at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:469)
 at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:290)
 at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
 at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
 at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
 at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1357)
 at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440)
 at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
 at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:868)
 at io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:799)
 at io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:501)
 at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:399)
 at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:998)
 at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
 at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
 at java.base/java.lang.Thread.run(Unknown Source)

Input Code

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@CustomLog
@Configuration
@EnableCaching
@EnableRetry
public class ApplicationCacheConfiguration {
   

    @Bean(name = REDIS_CACHE_MANAGER)
    @Primary
    public CacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory,
                                          RedisCacheConfiguration customerCacheConfiguration) {
        Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
        cacheConfigurations.put(CUSTOMER_CACHE, customerCacheConfiguration);

        return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
                .withInitialCacheConfigurations(cacheConfigurations)
                .build();
    }



    @Bean
    public RedisCacheConfiguration customerCacheConfiguration() {
        // This is the recommended way to specify a whitelist for a small range
        return RedisCacheConfiguration.defaultCacheConfig()
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new FastJsonRedisSerializer<>(Customer.class)))
                .entryTtl(Duration.ofSeconds(redisCustomerCacheTtlInSeconds));
    }

    @Bean
    public RedisConnectionFactory redisConnectionFactory(LettucePoolingClientConfiguration lettucePoolingClientConfiguration) {
        RedisConfiguration redisConfiguration;
        if (useRedisSentinel) {
            RedisSentinelConfiguration sentinelConfiguration = new RedisSentinelConfiguration()
                    .master("mymaster") // Needs to be in sync with the Redis Helm configuration
                    .sentinel(redisHostName, redisPort); // Note: redisPort == sentinelPort in this case
            sentinelConfiguration.setPassword(RedisPassword.of(redisPassword));
            sentinelConfiguration.setSentinelPassword(RedisPassword.of(redisPassword));
            redisConfiguration = sentinelConfiguration;
        } else if (useCluster) {
            // When useRedisSentinel is false and useCluster is true, connect to Azure Managed Redis in cluster mode
            RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration();
            clusterConfiguration.addClusterNode(new RedisClusterNode(redisHostName, redisPort));
            clusterConfiguration.setPassword(RedisPassword.of(azureRedisPassword));
            redisConfiguration = clusterConfiguration;
        } else {
            // Standalone Azure Managed Redis
            RedisStandaloneConfiguration standaloneConfiguration = new RedisStandaloneConfiguration(redisHostName, redisPort);
            standaloneConfiguration.setPassword(RedisPassword.of(azureRedisPassword));
            redisConfiguration = standaloneConfiguration;
        }

        LettuceConnectionFactory lettuceConnectionFactory = new
                LettuceConnectionFactory(redisConfiguration, lettucePoolingClientConfiguration);
        // remember the app settings
        lettuceConnectionFactory.afterPropertiesSet();
        log.atInfo()
                .with(ApplicationConstants.LOG_SESSION_CACHE_SIZE, JSON.toJSONString(lettucePoolingClientConfiguration))
                .log("Building lettuce connection pool");
        return lettuceConnectionFactory;
    }

    @Bean
    public LettucePoolingClientConfiguration lettucePoolingClientConfiguration(GenericObjectPoolConfig<StatefulConnection<?, ?>> genericObjectPoolConfig) {
        SocketOptions socketOptions = SocketOptions.builder()
                .connectTimeout(Duration.ofSeconds(redisConnectionTimeoutInSeconds))
                .keepAlive(true) // Enable TCP keepalive for long-lived connections
                .build();
        ClientOptions clientOptions = ClientOptions.builder()
                .socketOptions(socketOptions)
                .autoReconnect(true)
                .disconnectedBehavior(ClientOptions.DisconnectedBehavior.REJECT_COMMANDS)
                .build();

        LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder lettucePoolingClientConfigurationBuilder = LettucePoolingClientConfiguration.builder()
                .clientOptions(clientOptions)
                .commandTimeout(Duration.ofSeconds(redisCommandTimeoutInSeconds))
                .poolConfig(genericObjectPoolConfig);

        if (useRedisSentinel) {
            // Only in real Sentinel environments
            lettucePoolingClientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
        }
        // Enable SSL/TLS for Azure Redis Enterprise
        if (isSslEnabled) {
            lettucePoolingClientConfigurationBuilder
                    .useSsl()
                    .disablePeerVerification(); // Required for Azure private endpoints with self-signed certs
        }
        return lettucePoolingClientConfigurationBuilder.build();
    }

    @Bean
    @SuppressWarnings("java:S1452")
    public GenericObjectPoolConfig<StatefulConnection<?, ?>> genericObjectPoolConfig() {
        GenericObjectPoolConfig<StatefulConnection<?, ?>> poolConfig = new GenericObjectPoolConfig<>();
        poolConfig.setMaxIdle(maxIdle);
        poolConfig.setMinIdle(minIdle);
        poolConfig.setMaxWait(Duration.ofMillis(maxWaitMillis));
        poolConfig.setMaxTotal(maxTotal);
        // Validation & eviction for stale connections
        poolConfig.setTestWhileIdle(true);
        poolConfig.setTimeBetweenEvictionRuns(Duration.ofMinutes(5));
        return poolConfig;
    }


    public boolean isClubMetadataCacheEnabled() {
        return isDbCacheEnabled;
    }
}
Input Code
import org.springframework.data.redis.connection.*;

RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration();
clusterConfiguration.addClusterNode(new RedisClusterNode(redisHostName, redisPort));
clusterConfiguration.setPassword(RedisPassword.of(azureRedisPassword));
redisConfiguration = clusterConfiguration;

Expected behavior/code

No MYID Error when using RedisClusterConfiguration

Environment

  • Java 25
  • Spring Boot Version 3.5.7
  • Azure Managed Redis with High Availability Enabled and with OSSCluster policy
  • Lettuce version(s): 6.6.0.RELEASE
  • Redis version: 7.4

Possible Solution

Additional context

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions