AWS ElastiCache Redis Troubleshooting

이번에는 ElastiCache Redis를 운영하면서 발생했던 이슈와 해결했던 경험들을 공유해보려고 합니다.

ElastiCache Clustered Redis 다중 키 작업 문제

클러스터로 구성하면서 다중 키를 작업하면 아래와 같은 에러가 발생할 수 있습니다.

CROSSSLOT Keys in request don't hash to the same slot

해당 에러는 다중 키 작업시 키가 동일한 해시 슬롯에 있지 않아서 발생하는 문제입니다. 해결 방법은 2가지가 있습니다.

첫번째 방법은 Redis 클러스터를 지원하는 Redis 클라이언트 라이브러리를 사용하는 것입니다. 라이브러리를 변경하면 구현 소스를 일부 변경해야 할 수 있습니다.

두번째 방법은 키에 “{..}” 패턴을 포함하여 강제로 동일한 해시슬롯에 넣는 방법입니다. 아래의 와 같이 value 값에는 "{CACHE_KEYS}"로 공통 패턴을 주었고, key 값에는 "{AWS_CACHE}"로 공통 패턴을 주어서 나중에 한번에 찾아 지우기 쉽도록 구현했습니다.

@Cacheable(value = "{CACHE_KEYS}.TestService.listForAwsData", key = "{'{AWS_CACHE}', #root.targetClass, #root.methodName, #model.value}") ResultModel listForAwsData(CommonSearchModel model) { ... }
Code language: CSS (css)

Redis OOM

캐싱 중 메모리가 꽉 찬 경우에 아래와 같은 에러가 발생합니다.

OOM command not allowed when used memory > 'maxmemory'
Code language: JavaScript (javascript)

캐싱 정책을 살펴보고, 더 이상 캐싱 데이터를 줄일 여지가 없다면 스케일업을 진행합니다. 스케일업 작업 중에도 이미 캐싱된 데이터는 정상적으로 조회됩니다.

Error executing cache operation

가능성은 현저히 적지만, 서비스 중 ElastiCache Redis에 문제가 생길 수 있습니다. 자동 장애조치를 통해서도 해결할 수 없는 장애를 대비해서 아래와 같이 캐시 데이터를 가져올 때 에러가 나더라도 null을 리턴하여 정상적으로 db에서 데이터를 조회하도록 CustomRedisTemplate을 구현합니다.

public class CustomRedisTemplate<K, V> extends RedisTemplate<K, V> { private static final Logger logger = LoggerFactory.getLogger(CustomRedisTemplate.class); @Override public <T> T execute(final RedisCallback<T> action, final boolean exposeConnection, final boolean pipeline) { try { return super.execute(action, exposeConnection, pipeline); } catch(final Throwable t) { logger.warn("Error with cache : {}", t.getMessage()); return null; } } @Override public <T> T execute(final RedisScript<T> script, final List<K> keys, final Object... args) { try { return super.execute(script, keys, args); } catch(final Throwable t) { logger.warn("Error with cache : {}", t.getMessage()); return null; } } @Override public <T> T execute(final RedisScript<T> script, final RedisSerializer<?> argsSerializer, final RedisSerializer<T> resultSerializer, final List<K> keys, final Object... args) { try { return super.execute(script, argsSerializer, resultSerializer, keys, args); } catch(final Throwable t) { logger.warn("Error with cache : {}", t.getMessage()); return null; } } @Override public <T> T execute(final SessionCallback<T> session) { try { return super.execute(session); } catch(final Throwable t) { logger.warn("Error with cache : {}", t.getMessage()); return null; } } }
Code language: JavaScript (javascript)

Leave a Reply

Your email address will not be published. Required fields are marked *