세션 관리 실전 고수 팁
세션 전략을 선택하고 배포하는 것까지가 반이라면, 나머지 반은 운영 중 발생하는 문제들을 해결하는 것입니다. 이 페이지에서는 세션 전략 선택 매트릭스, 무중단 배포 중 세션 안전 처리, 세션 관련 장애 대응 노하우를 정리합니다.
세션 전략 선택 매트릭스
| 조건 | Sticky Session | Tomcat 클러스터 | Redis 세션 |
|---|---|---|---|
| 서버 2대 이하 | 최적 | 가능 | 과투자 |
| 서버 3~5대 | 가능 | 최적 | 권장 |
| 서버 5대 이상 | 비추천 | 비추천 | 필수 |
| 클라우드/K8s 환경 | 비추천 | 어려움 | 필수 |
| 레거시 앱 (코드 수정 불가) | 최적 | 가능 | 어려움 |
| 고가용성 최우선 | 비추천 | 가능 | 최적 |
| 세션 데이터 많음 (1KB+) | 가능 | 비추천 | 최적 |
| 무중단 배포 필요 | 주의 | 주의 | 최적 |
결론: 새 프로젝트라면 처음부터 Redis를 사용하세요. 기존 시스템은 규모에 따라 단계적으로 전환하세요.
무중단 배포 중 세션 안전 처리
무중단 배포 시 세션이 유실되지 않도록 순서를 지켜야 합니다.
Rolling Update 절차
#!/bin/bash
# rolling-deploy.sh
SERVERS=("10.0.0.1" "10.0.0.2" "10.0.0.3")
LB_API="http://lb.internal/api"
NEW_VERSION=$1
for server in "${SERVERS[@]}"; do
echo "=== Deploying to $server ==="
# 1. 로드밸런서에서 해당 서버 제거
curl -X PUT "$LB_API/servers/$server/status" -d '{"status": "draining"}'
echo "Server $server removed from load balancer"
# 2. 기존 연결이 완료될 때까지 대기 (graceful drain)
sleep 30
# 3. 새 버전 배포
ssh "$server" "sudo systemctl stop tomcat && \
cp /releases/$NEW_VERSION.war /opt/tomcat/webapps/ROOT.war && \
sudo systemctl start tomcat"
# 4. 헬스체크 통과 대기
until curl -s "http://$server:8080/health" | grep -q '"status":"UP"'; do
echo "Waiting for $server to become healthy..."
sleep 5
done
# 5. 로드밸런서에 복귀
curl -X PUT "$LB_API/servers/$server/status" -d '{"status": "active"}'
echo "Server $server back in rotation"
# 6. 다음 서버로 넘어가기 전 안정화 대기
sleep 15
done
echo "Rolling deploy complete!"
Sticky Session + 무중단 배포 시 주의
# Sticky Session 환경에서 서버를 내리기 전
# 로드밸런서에서 신규 세션 차단 (기존 세션만 처리)
# Apache balancer-manager에서 서버 상태를 DRAINING으로 변경
curl "http://localhost/balancer-manager?cmd=update&w=loadbalancer&sw=server1&ws=d"
# d=Draining: 기존 연결은 계속 처리하고, 새 연결은 받지 않음
# 세션이 자연스럽게 소멸될 때까지 대기 (세션 만료 시간에 따라)
sleep 1800 # 세션 만료 30분 대기 (실제로는 모니터링 후 판단)
# 이후 서버 중단
sudo systemctl stop tomcat1
세션 관련 장애 유형별 대응
장애 1: 로그인이 자꾸 풀린다
증상: 사용자가 로그인 후 곧 다시 로그인 페이지로 이동
원인 가능성:
1. Sticky Session 설정 누락 (다른 서버로 요청이 분산됨)
2. 세션 만료 시간이 너무 짧음
3. Redis 연결 실패로 세션 조회 불가
4. 로드밸런서의 jvmRoute 설정 불일치
디버깅 방법:
# 1. 로그에서 세션 ID와 서버 확인
grep "JSESSIONID" /var/log/apache2/access.log | tail -20
# JSESSIONID=ABC.server1 → JSESSIONID=ABC.server2로 바뀌면 Sticky Session 문제
# 2. Redis 연결 확인
redis-cli -h redis-host ping
redis-cli -h redis-host KEYS "session:*" | wc -l # 세션 수 확인
# 3. Tomcat 로그에서 세션 관련 오류 확인
grep -i "session\|serializ\|redis" /opt/tomcat/logs/catalina.out | tail -50
장애 2: Redis 장애 → 전체 세션 유실
증상: Redis 서버 다운 후 모든 사용자 로그인 해제
대응:
1. Redis Sentinel 또는 Cluster로 이중화 (예방)
2. 장애 발생 시 사용자에게 재로그인 안내 메시지
3. 중요 작업 데이터는 DB에도 저장 (주문, 결제 진행 중 등)
Spring Session에서 Redis 장애 시 graceful 처리:
@Configuration
public class SessionConfig {
@Bean
public HttpSessionIdResolver sessionIdResolver() {
// Redis 장애 시에도 쿠키 기반 세션 ID는 유지
return CookieHttpSessionIdResolver.withCookieSerializer(
new DefaultCookieSerializer()
);
}
}
장애 3: 세션 폭발 (Memory Leak)
증상: Redis 메모리가 급격히 증가, OOM 발생
원인: 세션 만료 설정 누락, 세션에 대용량 데이터 저장
# Redis 메모리 사용량 확인
redis-cli INFO memory | grep used_memory_human
# 세션 수 확인
redis-cli KEYS "session:*" | wc -l
# 세션별 크기 확인 (상위 10개)
redis-cli KEYS "session:*" | xargs -I{} redis-cli OBJECT ENCODING {} | sort -n | tail -10
# 수동으로 만료된 세션 정리
redis-cli --scan --pattern "session:*" | while read key; do
ttl=$(redis-cli TTL "$key")
if [ "$ttl" -eq -1 ]; then
echo "No TTL: $key"
# redis-cli DEL "$key" # 신중하게 삭제
fi
done
세션 보안 강화
세션 고정 공격(Session Fixation) 방어
로그인 성공 후 새 세션 ID를 발급합니다.
// Spring Security에서 자동 처리 (기본값: MIGRATE)
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.sessionManagement(session -> session
.sessionFixation().migrateSession() // 로그인 후 세션 ID 재발급
.maximumSessions(1) // 동시 로그인 1개로 제한
.maxSessionsPreventsLogin(false) // 새 로그인이 이전 세션 만료
);
return http.build();
}
}
HttpOnly + Secure 쿠키 설정
# application.yml
server:
servlet:
session:
cookie:
http-only: true # JavaScript에서 쿠키 접근 차단 (XSS 방어)
secure: true # HTTPS에서만 쿠키 전송 (HTTP 도청 방어)
same-site: strict # CSRF 방어
name: SID # JSESSIONID 대신 의미 없는 이름 사용 (정보 은닉)
max-age: 1800
운영 모니터링 설정
# Redis 세션 모니터링 스크립트
#!/bin/bash
redis-cli -h redis-host -a password INFO stats | grep -E "keyspace_hits|keyspace_misses"
# 캐시 히트율 계산
hits=$(redis-cli INFO stats | grep keyspace_hits | cut -d: -f2)
misses=$(redis-cli INFO stats | grep keyspace_misses | cut -d: -f2)
total=$((hits + misses))
rate=$(echo "scale=2; $hits * 100 / $total" | bc)
echo "Session cache hit rate: $rate%"
# 세션 수 모니터링
session_count=$(redis-cli KEYS "session:*" | wc -l)
echo "Active sessions: $session_count"
Prometheus + Grafana로 자동 수집하려면 redis_exporter를 사용합니다:
# docker-compose.yml
services:
redis-exporter:
image: oliver006/redis_exporter:latest
environment:
- REDIS_ADDR=redis://redis-host:6379
- REDIS_PASSWORD=your-password
ports:
- "9121:9121"