본문 바로가기
ERROR FIX

REDIS 7 update error (feat. lettuce, refactor-core)

by ppirae 2025. 12. 5.

AI Generated Image

회사에서 redis 6 을 사용중이었는데

검증계를 redis 7 로 버전업 하였더니

검증 서비스에서 redis 커넥션이 되지않는 문제가 발생하였다.

 

에러를 2개 발견하였다.

1. ERROR Exception:20 - io.lettuce.core.RedisConnectionException: Unable to connect

2. ERROR HashedWheelTimer:452 - You are creating too many HashedWheelTimer instances. HashedWheelTimer is a shared resource that must be reused across the JVM,so that only a few instances are created.


1번 에러

  • 현상: redis 7 부터 ACL 도입으로 업그레이드되면서 인증 방식이 변경되었는데, 클라이언트(Java 애플리케이션) 설정이 이를 따라가지 못해 연결 실패가 발생하고 있었다.
  • 원인: Lettuce 라이브러리를 사용중이었는데 Lettuce 5.2.0.RELEASE 버전은 Redis 7(ACL) 환경에서 사용하기에 매우 불안정하며 권장하지 않고 있었고, 해당 버전은 2019년 말에 출시된 버전으로, Redis 6 이상의 ACL 기능을 완벽하게 지원하지 않거나 버그가 있을 가능성이 높았다.
    Lettuce 6.2.6 버전으로 올려도 연결이 안되었는데 내부에서 사용되는 reactor-core의 버전이 낮아서 호환이 안되어 같이 올려주어 해결하였다. Lettuce 6.x 버전은 내부적으로 Project Reactor 3.4 이상 버전을 사용한다고 한다.
  • 해결: 라이브러리 버전업으로 해결
    • lettuce-core 5.2.0 -> 6.2.6
    • reactor-core 3.3.0 -> 3.4.34

2번 에러

위 연결이 안되는 에러보다 더 심각한 메모리 누수가 발생하고 있는 에러였다.

이 에러 때문에 서버 리소스를 잡아먹어 연결이 매우 지연되고 있었다.

  • 현상: 연결 실패 -> 재시도 로직에서 클라이언트 객체 계속 생성 -> 쓰레드 폭증 -> 서버 다운 위험
  • 원인: 현재 코드가 Redis 연결이 안 될 때마다 매 요청마다 새로운 RedisClient 객체를 생성(new)하고 있는 것으로 작성되어 있었다. (싱글톤으로 관리되지 않음)
  • 해결: 클라이언트와 커넥션을 static(싱글톤)으로 관리 하도록 코드를 수정하였다.
private static RedisClusterClient redisClient;
private static StatefulRedisClusterConnection<String, String> connection;

/**
     * 초기화 메서드: 최초 1회만 실행됨 (동기화 처리)
     */
    private static synchronized void init() {
        if (connection != null && connection.isOpen()) {
            return;
        }

        try {
            // 1. Redis URI 생성 (ACL 적용)
            String redisUrl = settingUrl();
            logger.info("Connecting to Redis Cluster: {}", redisUrl.replaceAll(":.*@", ":****@"));

            // 2. Client 생성
            redisClient = RedisClusterClient.create(redisUrl);

            // 토폴로지 자동 갱신 옵션 추가
            ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder()
                    .enableAllAdaptiveRefreshTriggers() // 이벤트(MOVED, RECONNECT 등) 발생 시 즉시 갱신
                    .enablePeriodicRefresh(Duration.ofMinutes(10)) // 10분마다 주기적 갱신
                    .build();

            redisClient.setOptions(ClusterClientOptions.builder()
                    .topologyRefreshOptions(topologyRefreshOptions)
                    .build());

            // 3. 커넥션 맺기
            connection = redisClient.connect();
            logger.info("Redis Cluster Connected Successfully.");

        } catch (Exception e) {
            logger.error("Redis Connection Failed!", e);
        }
    }

 


추가 궁금증

레디스는 커넥션 풀처럼 사용하는 건가 ?

결론: Lettuce는 일반적인 DB 커넥션 풀처럼 사용하지 않는다.

전통적인 JDBC(오라클, MySQL 등)나 예전 Redis 라이브러리(Jedis)와는 동작 방식이 완전히 다르다.

이해를 돕기 위해 은행 창구고속도로로 비유

  • 전통적인 방식 (JDBC, Jedis) = "은행 창구 (Connection Pool)"
    • 커넥션 하나당 한 번에 하나의 요청만 처리
    • 요청이 100개 들어오면 커넥션(창구)도 100개가 필요
    • 그래서 미리 50~100개를 만들어두고 빌려 쓰고 반납하는 "Connection Pool"이 필수
  • Lettuce 방식 (Netty 기반) = "고속도로 (Multiplexing)"
    • 커넥션(도로)은 딱 하나만 뚫어놓음
    • 이 하나의 도로 위로 수천 개의 자동차(요청)가 동시에 달림 (비동기 처리)
    • 데이터가 섞이지 않게 내부적으로 잘 관리하므로(Thread-Safe),
      풀(Pool)을 만들 필요 없이 커넥션 하나를 모든 쓰레드가 공유해서 쓰는 것이 훨씬 빠르다.

댓글