로그인한 사용자 정보를 서버 메모리에 저장하는 앱이 있다. 첫 번째 요청에서 서버 A가 세션을 만들었는데, 두 번째 요청이 서버 B로 가면 세션을 찾지 못해 로그인이 풀린다. Sticky Session은 이 문제를 “같은 사용자는 항상 서버 A로 보내자"는 방식으로 해결한다.

동작 방식

ALB의 경우 쿠키를 이용한다. 첫 번째 응답에 AWSALB 쿠키를 심고, 이후 요청은 쿠키값을 보고 같은 타깃으로 라우팅한다.

1. 클라이언트 → ALB → 서버 A (세션 생성)
   응답에 Set-Cookie: AWSALB=xxxxx; Max-Age=86400

2. 클라이언트 → ALB (Cookie: AWSALB=xxxxx) → 서버 A (쿠키 보고 고정)
3. 클라이언트 → ALB (Cookie: AWSALB=xxxxx) → 서버 A
ALB 타깃 그룹 설정:
  Stickiness: 활성화
  Duration: 1일 (쿠키 유효 기간)

왜 문제인가

부하 불균형

특정 서버에 세션이 몰리면 그 서버만 과부하가 걸린다. 새 서버를 추가해도 기존 세션이 거기로 가지 않는다. Auto Scaling으로 서버를 추가했는데 새 서버는 한가하고 기존 서버는 여전히 과부하인 상황이 생긴다.

수평 확장의 어려움

서버를 내려야 할 때 그 서버에 붙어있는 모든 사용자 세션이 끊어진다. Connection Draining은 HTTP 연결 수준의 처리인데, 세션 자체가 사라지는 것은 막지 못한다. 배포나 장애 시 로그인이 풀린다.

테스트와 디버깅 어려움

로드밸런서 뒤에서 특정 서버의 동작을 재현하기 어렵다. 어떤 서버로 요청이 갔는지 확인해야 한다.

근본 해결책 — Stateless 서버

Sticky Session이 필요한 근본 원인은 서버가 상태를 로컬 메모리에 들고 있기 때문이다. 상태를 외부 저장소로 이전하면 어떤 서버가 요청을 처리해도 같은 결과가 나온다.

# 이전: 서버 메모리에 세션 저장
서버 A 메모리: {"user123": {name: "홍길동", cart: [...]}}

# 이후: Redis에 세션 저장
Redis: {"session:abc123": {userId: "user123", name: "홍길동", cart: [...]}}
모든 서버가 Redis에서 읽음 → 어느 서버로 가도 상관없음

JWT를 쓰면 아예 서버 쪽에 세션 저장소가 필요 없다. 클라이언트가 토큰을 들고 있고, 서버는 토큰을 검증만 한다. 완전한 stateless다.

불가피하게 써야 할 때

레거시 앱을 당장 바꾸기 어렵거나, WebSocket처럼 연결 유지가 필요한 경우 단기적으로 쓸 수 있다.

WebSocket은 서버와 클라이언트가 지속 연결을 유지하므로 같은 서버로 고정돼야 한다. 다만 이 경우는 “세션 상태” 문제가 아니라 “프로토콜 특성” 때문이다. Socket.io 같은 라이브러리는 Redis Pub/Sub을 이용해 여러 서버 간 WebSocket 메시지를 브로드캐스트하는 방식으로 Sticky Session 없이 동작하도록 설계할 수도 있다.

트레이드오프

Sticky Session을 쓰더라도 서버가 죽으면 세션이 날아간다는 근본 문제는 해결되지 않는다. 로드밸런서 쿠키는 “이 서버로 보내달라는 힌트"이지, 서버 내 데이터를 보장하지 않는다. 고가용성이 중요한 서비스라면 Sticky Session은 임시방편이고, Redis 같은 외부 세션 저장소가 정답이다.