Published on

redis 세션 적용기

Authors
  • avatar
    Name
    zkrp
    Twitter

저번 글에서 세션의 개념, 세션을 사용하는 방법, 그리고 세션의 단점(서버 재기동, 대량 트래픽, 스케일 아웃 환경 등에서 발생하는 문제점)에 대해 설명했습니다.

이번 글에서는 이러한 세션의 단점을 어떻게 해결할 수 있는지, 즉 외부 세션 저장소(예: Redis)의 도입이 왜 필요한지와 실제 적용 방법을 중점적으로 다뤄보려고 합니다.

https://wngud.tistory.com/25 

저는 이번 프로젝트에서 외부 세션 저장소로 Redis를 사용하여 서버 확장성과 일관성을 확보하고, 장애 상황에서도 세션 유실을 최소화하려 합니다.

레디스를 적용하기 전에 레디스에 대해 간략하게 설명하겠습니다.

1. Redis

레디스는  Nosql DBMS(비관계형 데이터베이스)로 분류되며 In memory 기반의 Key -Value 구조를 가진 데이터 관리 시스템입니다.

  • 관계형 데이터베이스에서 테이블의 데이터를 쿼리하기 위한 SQL을 사용하지 않는 데이터 저장소입니다
  • TTL 설정 → 일정 시간이 지나면 데이터 삭제, 용량이 작은 메모리의 효율적 관리할 수 있습니다.
  • 메모리 기반이라 모든 데이터들을 메모리에 저장하고 조회가 매우 빠릅니다 - Mysql의 10배
  • 다양한 자료구조
    • key ,hash ,set
  • I/O단에서 멀티플렉싱을 지원합니다.
  • 분산 환경에서 뛰어난 성능이 발휘됩니다

이러한 성능과 다양한 기능 덕분에 세션 관리, 캐시, 메시지 브로커 등 실시간 처리가 중요한 시스템에서 널리 활용됩니다.

이제 Redis 를 이용해 어떻게 세션 관리를 하는지 설명하겠습니다.

2. Redis를 이용한 세션 관리 구조

2-1) 기존  세션관리 플로우차트 

기존 서버 내장 세션 관리 방식부터 설명하겠습니다.

설명

  플로우차트를 간단하게  설명하자면

  1. 클라이언트가 최초 요청(로그인 등) 시, 세션 생성·갱신을 서버에 요청
  2. 서버는 세션을 메모리 등에 생성하고, 세션 정보(세션ID)를 클라이언트에 전달
  3. 클라이언트는 이후 모든 요청마다 세션ID를 포함해서 서버에 전달
  4. 서버는 세션ID가 있으면 정상 응답, 세션이 없거나 만료되면 오류 반환(로그인 풀림 등)
  5. 서버가 여러 대인 경우(로드밸런서 환경)
    1. 각 서버 메모리에 독립적으로 세션이 저장됨(서버별로 따로)
    2. 클라이언트가 항상 같은 서버로만 연결되어야 세션이 유지됨
      1. 그래서 Sticky Session이라는 방식으로 세션을 특정 서버에 고정시켜 계속 같은 서버가 응답하게 만듭니다.

이 구조에서 만약 서버가 재시작되면 ,세션 정보(메모리)가 사라지면서 모든 정보가 사라질수 있습니다

또한 Sticky Session 방식은 서버 확장성/운영 에서는 불편하다는 단점이있습니다.

다음은 외부 저장소(Redis)를 사용한 플로우 차트를 설명하겠습니다.

2-2) 외부 저장소(Redis) 플로우차트 

설명 플로우차트를 간단하게  설명하자면
  1. 클라이언트가 최초 요청(로그인 등) 시, 세션 생성·갱신을 서버에 요청
  2. 서버는 세션을 생성한뒤 레디스에 저장하고 , 세션 정보(세션ID)를 클라이언트에 전달
  3. 클라이언트는 이후 모든 요청마다 세션ID를 포함해서 서버에 전달
  4. 서버는 레디스에  세션ID가 있으면 정상 응답, 세션이 없거나 만료되면 오류 반환
  5. 서버가 여러 대인 경우 (로드밸런서 환경)
    1. 서버가 여러 대여도 모든 서버가 Redis를 통해 동일한 세션 정보를 실시간으로 공유합니다.
    2. 클라이언트가 어느 서버에 접속하든 Redis에서 인증 및 세션 검증이 이뤄지므로 세션의 일관성이 유지됩니다.

모든 서버가 Redis에 세션 정보를 저장, 조회하므로, 서버가 여러 대이거나 재시작되더라도 세션이 안전하게 유지됩니다.

또한 스케일아웃 환경, 인증 처리가 효율적으로 처리 된다는 장점이 있습니다.

하지만 외부 저장소도 상황에 따라서 사용해야합니다.

모든 기술에는 트레이드오프가 존재하므로  프로젝트에 적용시 아래와 같은 단점도 충분히 적용해야합니다.

3.  외부 저장소 단점

첫 번째는 네트워크 비용입니다.
서버와 Redis가 별도의 네트워크로 통신하기 때문에, 네트워크 병목이나 레이턴시가 발생할 수 있습니다.
특히 데이터나 트래픽이 많거나, Redis 서버가 멀리 떨어진 리전에 위치한 경우 응답 속도가 느려질 수 있습니다.

두 번째는 장애 위험성입니다.
Redis 서버 자체가 장애를 겪을 경우 전체 서비스의 세션 관리에 문제가 발생할 수 있습니다.
이런 상황을 대비해 Sentinel(장애 감지 및 자동 failover), Cluster(수평 확장 및 장애 내성) 등 고가용성 구성을 반드시 고려해야 합니다.

세 번째는 속도입니다.
내부(서버 메모리)에 세션을 저장하면 프로세스 내에서 바로 접근할 수 있기 때문에 속도가 매우 빠릅니다.
한편, 외부 저장소(Redis)를 사용하면 세션 조회/저장 시 네트워크 왕복 비용이 추가되어 내부 저장보다 수십 배 느릴 수 있습니다.
외부 저장소도 매우 빠르지만, 데이터가 많거나 트래픽이 폭주할 때에는 속도 저하가 발생할 수 있습니다.

이해하기 쉽도록 직접 코드를 작성해서 테스트를 해보겠습니다.

  • 로컬 환경 (Local)
    • 기기: MacBook Pro M2, 16GB RAM
    • OS: macOS
  • 서버 환경 (Redis 서버 측)
    • CPU: AMD Ryzen 7 7840HS
    • RAM: 64GB
    • OS: Ubnutu 22.04
    • Redis Version: 7.4.2
  • 네트워크 환경
    • 같은 대역(LAN)

네트워크 환경: 같은 대역 

3 -1) 테스트

로컬 로직 : Java HashMap에서 데이터 조회, 1,000,000회 반복 평균 측정

 @Test
    void localSpeed() {
        Map<String, String> map = new HashMap<>();
        map.put("testKey", "테스트입니다");

        // Warm-up
        map.get("testKey");

        long totalTime = 0;
        int iterations = 1_000_000;

        for (int i = 0; i < iterations; i++) {
            long start = System.nanoTime();
            String value = map.get("testKey");
            Assertions.assertThat(value).isEqualTo("테스트입니다");
            long end = System.nanoTime();
            totalTime += (end - start);
        }

        log.info("Local 평균 응답 시간: {} ns", totalTime / iterations);
    }

레디스 로직: Redis 서버에서 데이터 조회, 10,000회 반복 평균 측정

@Test
    void redisSpeed() {
        redisTemplate.opsForValue().set("testKey", "테스트입니다");

        // Warm-up
        redisTemplate.opsForValue().get("testKey");

        long totalTime = 0;
        int iterations = 10_000;

        for (int i = 0; i < iterations; i++) {
            long start = System.nanoTime();
            String value = redisTemplate.opsForValue().get("testKey");
            Assertions.assertThat(value).isEqualTo("테스트입니다");
            long end = System.nanoTime();
            totalTime += (end - start);
        }

        log.info("Redis 평균 응답 시간: {} ns", totalTime / iterations);
    }

로컬 로직 측정 결과:  : 93 ns

Redis 로직 측정 결과:  3598517 ns

로컬과 Redis의 속도 차이는 약 38,715배 차이가 납니다.

4.  장점

이렇게 큰 차이가 나는데로 왜 레디스를 쓰는 이유를 설명하겠습니다.

  • 로컬 메모리는 CPU가 직접 접근하므로 압도적으로 빠릅니다. 하지만 각 서버마다 데이터가 분산 저장되어 여러 대 서버(쿠버네티스 파드 등)에서 데이터 불일치, 세션 유실, 인증 오류 등 운영상 치명적 문제가 발생합니다.
  • Redis는 네트워크 왕복, 직렬화 과정 등으로 인해 접근 속도는 느려도, 모든 서버가 단일 저장소에 접근해 데이터 일관성(정합성)을 강력하게 보장합니다. 서버가 몇 대든, 재시작·스케일 아웃·장애 복구 상황에서도 세션 유실 없이 동일 결과를 얻을 수 있습니다.

저 역시 이번 프로젝트에서 쿠버네티스(Kubernetes) 파드를 여러 대 운영하는 구조를 사용하고 있습니다.
이런 환경에서는 각 서버(파드)가 각각 요청을 처리하기 때문에, 만약 로컬 메모리나 개별 캐시만을 사용하면 클라이언트가 다른 파드에 연결될 때마다 이전 상태(세션 정보)가 사라지거나 일관성이 깨질 수 있습니다.

특히, 로그인 세션이나 사용자 상태 정보(면접 정보, 사용자 대화내역)처럼 데이터의 정합성이 매우 중요한 상황에서는 중앙 집중형 저장소인 Redis를 도입해야 모든 파드에서 동일한 세션 상태 정보를 안전하게 공유하고, 세션 유실 없이 서비스 신뢰성을 확보할 수 있습니다.

그래서 저도 로컬 속도 테스트 결과만 보는 것이 아니라, 실전 운영환경에서 데이터 일관성, 확장성, 장애 복구 측면에서 Redis를 선택할 수밖에 없었습니다.

이상으로 글 마치겠습니다 읽어주셔서 감사합니다!!