Sticky Session: 세션 고정 완전 정복
Sticky Session(세션 고정 또는 세션 어피니티)은 같은 클라이언트의 요청이 항상 동일한 서버로 전달되도록 로드밸런서가 보장하는 기법입니다. 애플리케이션 코드 변경 없이 로드밸런서 설정만으로 적용할 수 있어 레거시 시스템에서 특히 유용합니다.
Sticky Session의 동작 원리
최초 요청:
클라이언트 → LB → App2 (세션 생성, JSESSIONID=XYZ.server2)
이후 요청:
클라이언트 (Cookie: JSESSIONID=XYZ.server2)
→ LB: ".server2" 읽어 → App2로 라우팅
→ App2: 세션 XYZ 조회 성공
로드밸런서는 세션 쿠키의 라우팅 접미사(.server2)를 읽거나, 자체 쿠키를 삽입해 어느 서버로 보낼지 결정합니다.
Nginx: 쿠키 기반 Sticky Session
오픈소스 Nginx는 쿠키 기반 Sticky Session을 지원하지 않습니다. 대신 두 가지 대안이 있습니다.
방법 1: ip_hash (IP 기반 고정)
클라이언트 IP를 해시해서 항상 같은 서버로 라우팅합니다.
upstream backend {
ip_hash;
server 10.0.0.1:8080;
server 10.0.0.2:8080;
server 10.0.0.3:8080;
}
장점: 설정이 단순. 단점: 같은 NAT 뒤의 여러 사용자가 같은 서버에 집중될 수 있음.
방법 2: hash $cookie_JSESSIONID (쿠키 값 해시)
Tomcat이 반환한 JSESSIONID 쿠키 값을 해시해서 라우팅합니다.
upstream backend {
hash $cookie_JSESSIONID consistent;
server 10.0.0.1:8080;
server 10.0.0.2:8080;
server 10.0.0.3:8080;
}
세션 쿠키가 없는 최초 요청에는 Random과 유사하게 동작하고, 이후 요청부터 동일 서버로 고정됩니다.
방법 3: Nginx Plus sticky cookie (상용)
Nginx Plus에서는 완전한 쿠키 기반 Sticky Session을 지원합니다.
upstream backend {
server 10.0.0.1:8080;
server 10.0.0.2:8080;
sticky cookie srv_id expires=1h domain=.example.com path=/;
# 로드밸런서가 srv_id 쿠키를 삽입해 라우팅에 사용
}
Apache mod_proxy_balancer: 쿠키 기반 Sticky Session
Apache는 오픈소스에서도 쿠키 기반 Sticky Session을 완벽하게 지원합니다.
Tomcat jvmRoute 설정 (필수)
각 Tomcat 서버의 server.xml에서 고유한 jvmRoute를 설정합니다:
<!-- Tomcat 1: /opt/tomcat1/conf/server.xml -->
<Engine name="Catalina" defaultHost="localhost" jvmRoute="server1">
<!-- Tomcat 2: /opt/tomcat2/conf/server.xml -->
<Engine name="Catalina" defaultHost="localhost" jvmRoute="server2">
이렇게 하면 Tomcat이 세션 ID에 서버 식별자를 자동 추가합니다:
Set-Cookie: JSESSIONID=1A2B3C4D.server1; Path=/; HttpOnly
Apache 설정
# /etc/apache2/sites-available/sticky.conf
<VirtualHost *:80>
ServerName example.com
<Proxy "balancer://mycluster">
BalancerMember "http://10.0.0.1:8080" route=server1
BalancerMember "http://10.0.0.2:8080" route=server2
BalancerMember "http://10.0.0.3:8080" route=server3
ProxySet lbmethod=byrequests
ProxySet stickysession=JSESSIONID # 세션 쿠키명과 일치해야 함
</Proxy>
ProxyPass "/" "balancer://mycluster/"
ProxyPassReverse "/" "balancer://mycluster/"
</VirtualHost>
Apache는 JSESSIONID=1A2B3C4D.server1 쿠키에서 .server1 부분을 읽어 route=server1인 BalancerMember로 라우팅합니다.
URL 기반 Sticky Session (쿠키 불가 환경)
쿠키를 사용할 수 없는 환경에서 URL 파라미터에 세션 정보를 포함합니다:
ProxySet stickysession=jsessionid|JSESSIONID
# URL 예시
http://example.com/app/index.jsp;jsessionid=1A2B3C4D.server1
mod_jk: jvmRoute 자동 연동
mod_jk는 jvmRoute를 workers.properties의 worker 이름과 자동으로 매핑합니다.
# workers.properties
worker.loadbalancer.sticky_session=True
worker.loadbalancer.sticky_session_force=False # 세션 서버 다운 시 다른 서버 허용
sticky_session_force=True로 설정하면 세션 서버가 다운됐을 때 다른 서버로 페일오버하지 않고 오류를 반환합니다. 세션 유실을 막으려면 False(기본값)가 안전합니다.
Sticky Session의 한계와 주의사항
한계 1: 서버 장애 시 세션 유실
App1이 다운되면 App1에 고정된 사용자들의 세션이 사라집니다. sticky_session_force=False 또는 nofailover=Off로 다른 서버로 페일오버하면, 세션 데이터가 없어 로그인이 풀립니다.
대응: 세션 유실 시 로그인 페이지로 안내하거나, 중요 데이터는 DB에도 저장합니다.
한계 2: 부하 불균형
사용량이 많은 사용자가 App1에 집중되고 App2, App3에는 가벼운 사용자들만 있다면, App1이 과부하를 받습니다. Sticky Session은 부하를 완전히 균등하게 분배하지 못합니다.
한계 3: 스케일 아웃 시 기존 사용자 영향
새 서버를 추가해도 기존 사용자들은 이미 고정된 서버에 계속 접속합니다. 새 서버는 처음에 거의 트래픽을 받지 못합니다.
실전 구성: Apache + Tomcat Sticky Session
[클라이언트]
│
[Apache] ← stickysession=JSESSIONID
├── Tomcat1 (jvmRoute=server1) ← port 8080
├── Tomcat2 (jvmRoute=server2) ← port 8081
└── Tomcat3 (jvmRoute=server3) ← port 8082
1. 각 Tomcat server.xml 설정
<!-- Tomcat 1 -->
<Engine name="Catalina" defaultHost="localhost" jvmRoute="server1">
<!-- Tomcat 2 -->
<Engine name="Catalina" defaultHost="localhost" jvmRoute="server2">
<!-- Tomcat 3 -->
<Engine name="Catalina" defaultHost="localhost" jvmRoute="server3">
2. Apache 설정
<Proxy "balancer://appcluster">
BalancerMember "http://127.0.0.1:8080" route=server1 max_fails=3 retry=30
BalancerMember "http://127.0.0.1:8081" route=server2 max_fails=3 retry=30
BalancerMember "http://127.0.0.1:8082" route=server3 max_fails=3 retry=30
ProxySet lbmethod=bybusyness
ProxySet stickysession=JSESSIONID
ProxySet nofailover=Off # 세션 서버 다운 시 페일오버 허용
</Proxy>
ProxyPass "/" "balancer://appcluster/"
ProxyPassReverse "/" "balancer://appcluster/"
3. 동작 확인
# 첫 요청: 세션 생성 확인
curl -c cookies.txt -v http://example.com/app/login \
-d "username=admin&password=pass" 2>&1 | grep "JSESSIONID"
# Set-Cookie: JSESSIONID=1A2B3C4D.server2
# 이후 요청: 항상 server2로 가는지 확인
for i in {1..5}; do
curl -b cookies.txt http://example.com/app/current-server
done
# 모든 응답이 server2에서 와야 함
언제 Sticky Session을 선택해야 하나?
적합한 상황:
- 세션 공유 인프라(Redis) 구축이 어려운 레거시 환경
- 서버 2~3대의 소규모 구성
- 세션 유실이 크리티컬하지 않은 서비스 (세션 만료 시 재로그인 허용)
피해야 할 상황:
- 고가용성이 최우선인 서비스 (금융, 의료)
- 서버를 자주 추가·제거하는 클라우드/컨테이너 환경
- 트래픽 패턴이 불균일한 서비스 (일부 사용자가 매우 많은 요청 발생)
다음 페이지에서는 Tomcat 인메모리 클러스터링으로 세션을 모든 서버에 복제하는 방법을 알아봅니다.