서버를 2대로 늘렸더니 로그인이 자꾸 풀립니다. 서버 A에서 로그인했는데 다음 요청이 서버 B로 가면 세션이 없다고 합니다. 어떻게 해결하나요?

개념 정의

세션 클러스터링 은 여러 서버가 세션 저장소를 공유해서, 어떤 서버로 요청이 가든 동일한 세션을 사용할 수 있게 하는 것입니다. Spring Session + Redis 조합이 가장 많이 쓰입니다.

왜 문제가 생기나

톰캣은 기본적으로 세션을 JVM 힙 메모리 에 저장합니다.

  1. 사용자가 서버 A에서 로그인 → 세션이 서버 A 메모리에 저장
  2. 다음 요청이 로드밸런서에 의해 서버 B로 전달
  3. 서버 B에는 해당 세션이 없음 → 인증 실패 (401/403)

해결: Spring Session + Redis

세션을 서버 메모리 대신 Redis 에 저장하면, 모든 서버가 같은 세션을 공유합니다.

설정

XML
<!-- build.gradle 또는 pom.xml -->
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
YAML
# application.yml
spring:
  session:
    store-type: redis
    redis:
      namespace: myapp:session
  data:
    redis:
      host: redis.example.com
      port: 6379

이 설정만으로 톰캣의 HttpSession이 Redis 기반으로 교체됩니다. 스프링 시큐리티의 SecurityContext도 자동으로 Redis 세션에 저장됩니다.

동작 흐름

단계기존 (메모리)Redis 세션
로그인서버 A 메모리에 세션 저장Redis에 세션 저장
다음 요청서버 B에 세션 없음 → 실패Redis에서 세션 조회 → 성공
세션 만료서버 재시작하면 소멸Redis TTL로 관리

Redis에 저장되는 구조

BASH
# Redis에서 세션 확인
127.0.0.1:6379> KEYS myapp:session:*
1) "myapp:session:sessions:expires:abc123"
2) "myapp:session:sessions:abc123"

127.0.0.1:6379> HGETALL myapp:session:sessions:abc123
# sessionAttr:SPRING_SECURITY_CONTEXT → 직렬화된 SecurityContext
# creationTime → 1711440000000
# maxInactiveInterval → 1800
# lastAccessedTime → 1711441800000

SecurityContext(Authentication 포함)가 Redis에 직렬화되어 저장됩니다.

함정 — 이걸 모르면 터진다

1. 세션에 저장하는 객체가 Serializable이 아니면 에러

Redis에 세션을 저장하려면 세션에 담기는 모든 객체가 직렬화 가능 해야 합니다. UserDetails 구현체가 Serializable을 구현하지 않으면 SerializationException이 발생합니다.

JAVA
// ❌ Serializable 빠짐 → Redis 저장 시 에러
public class CustomUser implements UserDetails { ... }

// ✅ Serializable 구현
public class CustomUser implements UserDetails, Serializable {
    private static final long serialVersionUID = 1L;
    // ...
}

2. Redis 다운되면 전체 서비스 인증 불가

세션 저장소가 Redis 하나에 의존하면, Redis가 다운되는 순간 모든 서버의 인증이 실패 합니다. Redis Sentinel 또는 Cluster로 고가용성을 확보해야 합니다.

3. 세션 크기가 커지면 Redis 메모리 폭발

세션에 큰 객체(장바구니 전체, 파일 데이터 등)를 넣으면 Redis 메모리가 빠르게 고갈됩니다. 세션에는 인증 정보(UserDetails)만 저장하고, 비즈니스 데이터는 별도 키로 관리합니다.

세션에 넣는 데이터는 최소한으로. 인증 정보 외의 데이터는 Redis의 별도 키로 분리합니다.

Sticky Session은 대안이 될까?

로드밸런서의 Sticky Session(세션 고정)으로 같은 사용자를 항상 같은 서버로 보내는 방법도 있습니다.

비교Sticky SessionRedis 세션
서버 장애 시세션 유실Redis에 남아있음
스케일 아웃트래픽 불균형균등 분산
구현 복잡도로드밸런서 설정만Redis 인프라 필요
배포 시세션 유실 가능영향 없음

Sticky Session은 임시방편입니다. 서버 장애나 배포 시 세션이 유실되므로, 프로덕션에서는 Redis 세션을 권장합니다.

정리

항목설명
문제다중 서버에서 세션이 서버 메모리에만 저장되어 인증 불일치
해결Spring Session + Redis로 세션 저장소 통합
설정spring-session-data-redis 의존성 + store-type: redis
주의Serializable 필수, Redis 고가용성, 세션 크기 최소화
댓글 로딩 중...