본문으로 건너뛰기

세션 관리 실전 고수 팁

세션 전략을 선택하고 배포하는 것까지가 반이라면, 나머지 반은 운영 중 발생하는 문제들을 해결하는 것입니다. 이 페이지에서는 세션 전략 선택 매트릭스, 무중단 배포 중 세션 안전 처리, 세션 관련 장애 대응 노하우를 정리합니다.


세션 전략 선택 매트릭스

조건Sticky SessionTomcat 클러스터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"