1. 서론: 다중 WAS 환경에서의 세션 불일치 문제
현대의 상용 웹 애플리케이션은 단일 인스턴스로 운영되지 않습니다. 고가용성(High Availability, HA) 확보와 트래픽 분산을 위해 L4/L7 스위치, AWS ALB, Nginx 등의 로드 밸런서 뒤에 최소 2대 이상의 Tomcat(WAS) 인스턴스를 두는 액티브-액티브(Active-Active) 클러스터링 구조가 기본입니다.
하지만 로드 밸런서가 라운드로빈(Round-Robin) 방식으로 클라이언트의 HTTP 요청을 여러 서버로 번갈아 전달하는 순간, ‘세션 불일치(Session Inconsistency)’ 문제가 발생합니다.
[클라이언트 #1 로그인] ──▶ [로드 밸런서] ──▶ [Tomcat #1] (JSESSIONID_A 발급, 메모리 저장)
[클라이언트 #1 다음 요청] ──▶ [로드 밸런서] ──▶ [Tomcat #2] (세션 정보 없음! 로그인 풀림 현상 발생)
Tomcat #1에서 로그인에 성공하여 메모리에 세션을 생성했더라도, 다음 요청이 Tomcat #2로 인입되면 사용자는 순식간에 로그인 풀림 현상이나 장바구니 초기화 같은 치명적인 장애를 겪게 됩니다.
이번 포스팅에서는 Tomcat 자체의 세션 클러스터링(Replication) 메커니즘과 설정 방법, 실무 운영 시 마주치는 치명적인 한계, 그리고 현대 클라우드 환경의 표준 대안(Redis/Spring Session)까지 완벽히 정리합니다.
2. 세션 공유 전략 3가지 비교
다중 서버 환경에서 세션을 유지하는 방법은 크게 세 가지가 있으며, 아키텍처적 특성이 극명하게 갈립니다.
| 방식 | 동작 원리 | 장점 | 단점 / 한계 | 실무 적합도 |
|---|---|---|---|---|
| Sticky Session | 로드 밸런서가 쿠키/IP를 기반으로 동일 사용자를 특정 서버에 고정 | 설정이 매우 단순함, 서버 간 통신 오버헤드 없음 | 특정 서버 장애 시 해당 서버 세션 전체 유실, 트래픽 불균형 발생 | 임시 조치 / 소규모 |
| Tomcat Session Replication | Tomcat 엔진끼리 멀티캐스트/유니캐스트로 세션 객체를 상호 복제 | 서버 장애 시 즉각 페일오버(Failover) 보장 | 노드 증가 시 네트워크 I/O 폭증, 클라우드 멀티캐스트 미지원 | 온프레미스 소규모 (2~4대) |
| 분산 캐시 (Redis / DB) | 외부 세션 저장소(Redis)에 세션을 위임하고 WAS는 무상태화 | 완벽한 수평 확장(Scale-out), 서버 재부팅 시 세션 유지 | 별도 Redis 클러스터 인프라 관리 필요 | 현대 엔터프라이즈 표준 |
3. Tomcat 네이티브 세션 클러스터링 구축 (server.xml)
온프레미스 인프라에서 Tomcat 자체 기능을 사용해 노드 간 세션을 복제하려면 conf/server.xml의 <Host> 태그 내부에 <Cluster> 요소를 정의해야 합니다.
server.xml 상세 설정
<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
<!-- Tomcat 세션 클러스터링 설정 시작 -->
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
channelSendOptions="8">
<!-- 세션 매니저: 전체 복제(DeltaManager) 또는 백업 복제(BackupManager) -->
<Manager className="org.apache.catalina.ha.session.DeltaManager"
expireSessionsOnShutdown="false"
notifyListenersOnReplication="true"/>
<!-- 클러스터 통신 채널 정의 -->
<Channel className="org.apache.catalina.tribes.group.GroupChannel">
<!-- 1. 멤버십 서비스: 노드 탐색 (Multicast) -->
<Membership className="org.apache.catalina.tribes.membership.McastService"
address="228.0.0.4"
port="45564"
frequency="500"
dropTime="3000"/>
<!-- 2. 수신 리시버: 다른 노드로부터 세션 데이터를 받는 TCP 포트 -->
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
address="auto"
port="4000"
autoBind="100"
selectorTimeout="5000"
maxThreads="6"/>
<!-- 3. 발신 센더: 변경된 세션을 다른 노드로 전송 -->
<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
</Sender>
<!-- 4. 클러스터 메시지 인터셉터 -->
<Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor"/>
</Channel>
<Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
filter=""/>
<Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>
<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>
<!-- Tomcat 세션 클러스터링 설정 끝 -->
</Host>
핵심 설정 파라미터 해설
channelSendOptions="8": 세션 데이터 전송을 비동기(Asynchronous)로 처리합니다. 동기 모드(2)는 데이터 일치성은 완벽하지만 클라이언트 응답 속도가 크게 떨어집니다. 실무에서는 비동기 전송인 8을 권장합니다.DeltaManagervsBackupManager:- DeltaManager: 클러스터 내의 **모든 노드(All-to-All)**에 세션 변경분을 전체 복제합니다. 2~4대의 소규모 서버에 적합하며, 노드가 늘어나면 네트워크 브로드캐스트 부하가 감당하기 어려워집니다.
- BackupManager: 세션을 소유한 Primary 노드와 1개의 Backup 노드에만 복제하는 방식입니다. 노드가 5대 이상일 때 고려할 수 있습니다.
McastService(멀티캐스트 IP: 228.0.0.4, Port: 45564): 동일 네트워크 대역의 노드들이 서로 살아있는지 하트비트(Heartbeat)를 주고받는 주소입니다.
4. 애플리케이션 레벨 필수 요건 (web.xml & Serializable)
Tomcat 설정을 완벽하게 마쳤더라도, 애플리케이션 레벨에서 다음 두 가지 조건을 만족하지 않으면 세션 복제는 완전히 무용지물이 됩니다.
1) web.xml에 <distributable/> 태그 선언
배포 대상 애플리케이션의 WEB-INF/web.xml 최상단에 <distributable/> 태그를 반드시 추가해야 합니다. 이 태그가 누락되면 Tomcat은 해당 앱을 단일 서버용으로 간주하고 세션 복제 대상에서 제외합니다.
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<display-name>MyClusteredApp</display-name>
<!-- 세션 클러스터링 활성화 필수 태그 -->
<distributable/>
</web-app>
2) 세션 저장 객체의 java.io.Serializable 구현
세션에 바인딩되는 모든 자바 객체(User DTO, 권한 정보, 장바구니 등)는 네트워크 패킷으로 직렬화(Serialization)되어 다른 서버로 전송됩니다. 만약 Serializable 인터페이스를 구현하지 않은 일반 객체를 session.setAttribute()에 넣으면 런타임에 즉각 IllegalArgumentException 또는 NotSerializableException이 발생합니다.
// 올바른 예시
public class UserSessionDto implements Serializable {
private static final long serialVersionUID = 1L;
private Long userId;
private String username;
private List<String> roles;
// getter, setter...
}
5. 클라우드 환경의 현실: 왜 Redis인가?
AWS, Azure, GCP에서의 멀티캐스트 차단
AWS VPC나 대부분의 퍼블릭 클라우드 가상 네트워크 환경은 브로드캐스트 스톰(Broadcast Storm) 방지를 위해 IP 멀티캐스트(Multicast)를 하드웨어 수준에서 차단합니다. 따라서 위에서 설정한 McastService 방식의 Tomcat 네이티브 클러스터링은 클라우드에서 동작하지 않습니다.
Tomcat에서 정적 유니캐스트(StaticMembershipInterceptor)를 쓸 수도 있지만, 오토스케일링(Auto-scaling)으로 서버가 동적으로 늘어나고 줄어드는 클라우드 환경에서는 IP를 일일이 정적으로 등록하는 것이 불가능합니다.
현대 아키텍처의 정답: Spring Session + Redis
T1 -. Session CRUD .-> Redis[(Redis Cluster / AWS ElastiCache)]
T2 -. Session CRUD .-> Redis
T3 -. Session CRUD .-> Redis
style Redis fill:#ffcdd2,stroke:#b71c1c,stroke-width:2px
Spring Boot 환경이라면 spring-session-data-redis 라이브러리를 추가하여 세션을 인메모리 Redis에 저장하는 것이 압도적으로 우수합니다.
// build.gradle
implementation 'org.springframework.session:spring-session-data-redis'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
# application.yml
spring:
session:
store-type: redis
timeout: 30m
data:
redis:
host: redis-cluster.internal
port: 6379
이렇게 하면 Tomcat 자체는 완전한 무상태(Stateless) 서버가 되어, 서버 1대가 다운되거나 100대로 오토스케일링되어도 세션이 단 1건도 유실되지 않습니다.
6. 트러블슈팅 및 운영 체크리스트
- 방화벽 확인: Tomcat Receiver 포트(기본 4000/TCP)와 멀티캐스트 포트(45564/UDP)가 내부망 서버 간에 열려있는지
telnet또는nc명령어로 확인하세요. - 세션 복제 주기: 세션의 속성이 변경될 때만 복제되도록
org.apache.catalina.ha.session.DeltaManager의notifyListenersOnReplication을 적절히 제어하세요. - 네트워크 대역폭 모니터링: 세션 객체가 비대해지면 노드 간 통신 트래픽이 기가비트 대역폭을 잠식할 수 있으므로, 세션에는 최소한의 식별자(ID)만 보관하고 세부 정보는 DB/캐시에서 조회하는 것이 좋습니다.
7. 결론
Tomcat 세션 클러스터링은 고가용성 인프라를 구축할 때 반드시 이해해야 하는 핵심 메커니즘입니다. 레거시 온프레미스 환경이라면 server.xml과 web.xml의 <distributable/>을 활용한 네이티브 세션 복제를, 클라우드/컨테이너 환경이라면 Redis 기반의 분산 세션 스토리지를 채택하는 것이 안정적인 서비스를 위한 최선의 선택입니다.