ERR unknown subcommand 'MYID' with Azure Managed Redis (High Availablity)
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
At work we also just run into this error. For us the error was in Azure itself.
We needed do configure in the Azure Managed Redis instance under Settings -> Identity -> System Assigned, the Status to "on"
After this everything worked fine.
Please confirm if this also worked for you
For us we already enabled this System Assigned in Identity Settings. But we get this Intermittent Error / Exception with High Availability enabled. Please let me know if you need any other details on this