클라우드 환경과 대규모 서비스의 발전으로 다중 서버 환경은 이제 선택이 아닌 필수가 되었습니다. 그러나 세션 기반 인증을 사용하는 애플리케이션에서는 다중 서버 환경이 새로운 도전 과제를 가져옵니다. 이번 글에서는 다중 서버 환경에서 세션을 관리하는 방법과 각 방식의 장단점을 알아보겠습니다.
다중 서버 환경에서의 세션 불일치 문제
웹 애플리케이션의 트래픽이 증가함에 따라 서버를 수평적으로 확장하는 것은 자연스러운 해결책입니다. 그러나 세션 기반 인증을 사용하는 경우, 서버마다 독립적인 메모리 공간을 가지므로 세션 불일치 문제가 발생합니다.
세션 불일치란?
세션 불일치는 다중 서버 환경에서 사용자의 세션 정보가 특정 서버에만 저장되어, 다른 서버로 요청이 라우팅될 경우 인증 상태가 유지되지 않는 문제를 말합니다.
예를 들어 두 대의 서버(A, B)가 로드 밸런서 뒤에 있다고 가정해보겠습니다:
- 사용자가 로그인 요청을 보내면 로드 밸런서는 서버 A로 요청 전달
- 서버 A는 인증 후 사용자의 세션을 서버 A의 메모리에 저장
- 사용자가 다시 요청을 보내면 로드 밸런서는 서버 B로 요청 전달
- 서버 B에는 해당 사용자의 세션 정보가 없어 인증되지 않은 사용자로 간주
사용자 → 로그인 요청 → 로드 밸런서 → 서버 A (세션 생성 및 저장)
↓
사용자 → 두 번째 요청 → 로드 밸런서 → 서버 B (세션 없음 → 인증 실패)
이 문제를 해결하기 위한 세 가지 주요 방식을 살펴보겠습니다.
1. 스티키 세션 (Sticky Session) 방식
스티키 세션은 사용자의 모든 요청이 항상 동일한 서버로 전달되도록 보장하는 방식입니다.
작동 방식
- 사용자가 처음 로그인하면 로드 밸런서는 해당 사용자와 특정 서버 간의 "고정(affinity)" 관계를 설정
- 이후 해당 사용자의 모든 요청은 동일한 서버로 라우팅
- 고정 관계는 사용자의 쿠키나 IP 주소를 기반으로 유지
구현 예시 (AWS ELB 설정)
# AWS ELB 스티키 세션 설정 예시 (CloudFormation)
Resources:
MyLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
# ... 기타 설정 ...
TargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
# ... 기타 설정 ...
TargetGroupAttributes:
- Key: stickiness.enabled
Value: true
- Key: stickiness.type
Value: lb_cookie
- Key: stickiness.lb_cookie.duration_seconds
Value: 86400 # 쿠키 유효 기간 (1일)
장점
- 구현이 간단함: 대부분의 로드 밸런서가 스티키 세션 기능을 제공
- 성능 오버헤드가 적음: 외부 저장소나 동기화 작업이 필요 없음
- 세션 데이터 변경 추적이 필요 없음: 항상 같은 서버에서 처리되므로
단점
- 서버 장애 시 세션 유실: 특정 서버가 다운되면 해당 서버에 고정된 모든 사용자는 재로그인 필요
- 로드 밸런싱 효율 저하: 특정 서버에 트래픽이 집중될 수 있음
- 확장성 제한: 새 서버 추가 시 기존 사용자의 세션을 마이그레이션하기 어려움
2. 세션 클러스터링 (Session Clustering) 방식
세션 클러스터링은 한 서버에서 생성된 세션 정보를 다른 모든 서버에 복제하여 동기화하는 방식입니다.
작동 방식
- 사용자가 로그인하면 세션이 생성된 서버는 해당 세션 정보를 클러스터 내 다른 모든 서버에 복제
- 모든 서버가 동일한 세션 정보를 가지므로 어떤 서버로 요청이 가더라도 동일한 세션 상태 유지
- 세션 변경 시 모든 서버에 변경 사항 전파
구현 예시 (Tomcat 클러스터링 설정)
<!-- Tomcat server.xml 세션 클러스터링 설정 예시 -->
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
channelSendOptions="8">
<Manager className="org.apache.catalina.ha.session.DeltaManager"
expireSessionsOnShutdown="false"
notifyListenersOnReplication="true"/>
<Channel className="org.apache.catalina.tribes.group.GroupChannel">
<Membership className="org.apache.catalina.tribes.membership.McastService"
address="228.0.0.4"
port="45564"
frequency="500"
dropTime="3000"/>
<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
address="auto"
port="4000"
autoBind="100"
selectorTimeout="5000"
maxThreads="6"/>
<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
</Sender>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpPingInterceptor"/>
<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"/>
<Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
tempDir="/tmp/war-temp/"
deployDir="/tmp/war-deploy/"
watchDir="/tmp/war-listen/"
watchEnabled="false"/>
<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>
장점
- 서버 장애에 강함: 한 서버가 다운되어도 다른 서버에 세션 정보가 존재
- 균등한 로드 밸런싱: 스티키 세션이 필요하지 않아 균등한 부하 분산 가능
- 사용자 경험 향상: 서버 장애 시에도 사용자는 재로그인할 필요가 없음
단점
- 메모리 사용량 증가: 모든 서버가 모든 세션 정보를 복제하므로 메모리 중복 사용
- 네트워크 오버헤드: 세션 복제 과정에서 네트워크 트래픽 발생
- 세션 복제 지연: 복제 과정에서 발생하는 지연으로 일시적인 세션 불일치 가능성
- 구현 복잡성: 적절한 설정과 관리가 필요
3. 스토리지 분리 (External Session Storage) 방식
스토리지 분리 방식은 세션 정보를 모든 서버가 접근할 수 있는 외부 저장소(Redis, Memcached 등)에 저장하는 방식입니다.
작동 방식
- 사용자가 로그인하면 세션 정보를 서버 메모리가 아닌 외부 저장소에 저장
- 모든 서버는 동일한 외부 저장소에 접근하여 세션 정보를 조회/수정
- 세션 ID는 쿠키를 통해 클라이언트에 전달
구현 예시 (Spring Boot + Redis)
// Spring Boot에서 Redis 세션 저장소 구성 예시
@Configuration
@EnableRedisHttpSession
public class SessionConfig {
@Bean
public LettuceConnectionFactory connectionFactory() {
return new LettuceConnectionFactory(
new RedisStandaloneConfiguration("redis-host", 6379));
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory());
return template;
}
}
장점
- 효율적인 메모리 사용: 세션 정보가 한 곳에만 저장되어 중복 없음
- 손쉬운 확장성: 새 서버 추가 시 외부 저장소에 연결만 하면 됨
- 서버 장애에 강함: 서버가 다운되어도 세션 정보는 외부 저장소에 안전하게 보관
- 균등한 로드 밸런싱: 어떤 서버로 요청이 가도 동일한 세션 접근 가능
단점
- 외부 저장소 장애 위험: 저장소가 단일 장애 지점(SPOF)이 될 수 있음
- 지연 시간 추가: 외부 저장소 접근에 따른 네트워크 지연 발생
- 추가 인프라 관리: 외부 저장소 설정 및 유지보수 필요
- 복제 지연 가능성: 저장소 클러스터링 시 복제 지연으로 인한 일시적 불일치 가능
세 가지 방식 비교
각 방식의 특징을 한눈에 비교해 보겠습니다.
비교 항목 | 스티키 세션 | 세션 클러스터링 | 스토리지 분리 |
---|---|---|---|
구현 복잡성 | 낮음 | 높음 | 중간 |
메모리 효율성 | 중간 | 낮음 | 높음 |
서버 장애 대응 | 취약 | 강함 | 강함 |
확장성 | 제한적 | 중간 | 우수 |
성능 오버헤드 | 낮음 | 중간 | 중간 |
로드 밸런싱 효율 | 낮음 | 높음 | 높음 |
추가 인프라 필요 | 없음 | 적음 | 많음 |
적합한 상황 | 소규모 애플리케이션 | 중간 규모, 고가용성 필요 | 대규모, 클라우드 환경 |
실제 구현 시 고려사항
스티키 세션 구현 시 고려사항
- 로드 밸런서 설정을 통해 간단히 구현할 수 있음
- 장애 복구 계획 수립 필요 (서버 장애 시 세션 소실)
- 서버 간 불균형 부하를 모니터링해야 함
세션 클러스터링 구현 시 고려사항
- 애플리케이션 서버의 클러스터링 기능 활용 (Tomcat, JBoss 등)
- 네트워크 대역폭과 세션 복제 주기 최적화
- 서버 추가/제거 시 세션 재분배 전략 필요
스토리지 분리 구현 시 고려사항
- 적절한 외부 저장소 선택 (Redis, Memcached, DB 등)
- 저장소의 고가용성 구성 (클러스터링, 레플리케이션)
- 세션 직렬화/역직렬화 성능 최적화
- 세션 만료 정책 설정
JWT: 세션 관리의 대안
세션 기반 인증의 복잡성을 피하기 위해 JWT(JSON Web Token)와 같은 토큰 기반 인증을 고려할 수도 있습니다. JWT는 상태를 저장하지 않는(stateless) 인증 방식으로, 서버 간 세션 동기화 문제가 발생하지 않습니다.
// JWT 구조 예시
const header = {
"alg": "HS256", // 서명 알고리즘
"typ": "JWT" // 토큰 타입
};
const payload = {
"sub": "1234567890", // 사용자 식별자
"name": "John Doe", // 사용자 정보
"iat": 1516239022, // 발급 시간
"exp": 1516242622 // 만료 시간
};
// header와 payload를 base64로 인코딩한 후 서명
const signature = HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret_key
);
// 최종 JWT
const jwt = base64UrlEncode(header) + "." + base64UrlEncode(payload) + "." + signature;
JWT의 장점
- 서버 상태 저장 불필요: 토큰에 필요한 정보가 모두 포함되어 있음
- 확장성 우수: 서버 간 공유 상태가 없어 수평적 확장이 용이
- 교차 도메인 지원: 다양한 서비스 간에 인증 정보 공유 가능
JWT의 단점
- 토큰 크기: 세션 ID에 비해 크기가 커 네트워크 오버헤드 발생
- 토큰 폐기 어려움: 발급된 토큰은 만료 전까지 무효화하기 어려움
- 민감 정보 저장 제한: 토큰이 클라이언트에 저장되므로 보안 위험 고려 필요
결론
다중 서버 환경에서의 세션 관리는 애플리케이션의 규모, 요구사항, 인프라 환경에 따라 최적의 방식이 달라집니다.
- 스티키 세션: 구현이 간단하지만 서버 장애에 취약하므로 소규모 애플리케이션이나 빠른 구현이 필요한 경우에 적합
- 세션 클러스터링: 서버 장애에 대한 복원력이 뛰어나지만 메모리 사용이 비효율적이므로 중간 규모의 미션 크리티컬한 애플리케이션에 적합
- 스토리지 분리: 확장성과 메모리 효율성이 높지만 외부 의존성이 추가되므로 대규모 애플리케이션이나 클라우드 환경에 적합
- JWT: 세션 관리의 복잡성을 피하고자 하는 경우 고려할 수 있는 대안적 접근법
어떤 방식을 선택하든, 사용자 경험, 시스템 성능, 장애 대응 능력 간의 균형을 고려하여 애플리케이션에 가장 적합한 전략을 선택하는 것이 중요합니다.
참고 자료
'Backend Development' 카테고리의 다른 글
동시성과 병렬성: 현대 백엔드 시스템의 핵심 개념 이해하기 (0) | 2025.05.30 |
---|---|
로드 밸런싱 완전 정복: 백엔드 개발자를 위한 핵심 가이드 (0) | 2025.05.30 |
MySQL Replication 완벽 가이드: 고가용성과 데이터 안정성 확보 (2) | 2025.05.29 |
Java Record 완벽 가이드: DTO와 VO 구현의 새로운 패러다임 (6) | 2025.05.29 |
HTTP 메서드의 멱등성(Idempotency): 안전한 API 설계의 핵심 (2) | 2025.05.29 |