[Huge Traffic Handling] HTTP Session과 Session Clustering

Raha·2026년 4월 16일

Huge Traffic Handling

목록 보기
1/9

들어가며

지금까지 Spring MVC 흐름, JPA, 트랜잭션을 공부하면서 서버가 요청을 처리하는 방식을 살펴봤다. 그런데 한 가지 의문이 생긴다. 우리가 매일 쓰는 쇼핑몰에서는 로그인이 유지되고 장바구니도 사라지지 않는다. HTTP는 Stateless라고 했는데, 이게 어떻게 가능한 걸까?

이번 글에서는 다음 질문들을 따라가며 그 답을 찾아본다.

  • HTTP가 Stateless라면, 서버는 어떻게 나를 기억하는가?
  • 서버가 여러 대일 때 세션은 어떻게 공유되는가?
  • Redis는 왜 세션 저장소로 적합한가?

1. HTTP는 기억력이 없다

HTTP는 각 요청을 완전히 독립적으로 처리한다. 첫 번째 요청이 끝나면 서버는 그 요청이 있었다는 사실 자체를 잊어버린다.

요청 1: "로그인 해줘" → 서버: "OK"
요청 2: "내 장바구니 보여줘" → 서버: "누구세요?"

이것이 Stateless의 실체다. 매 요청이 새로운 낯선 사람처럼 취급된다.

장점은 명확하다. 서버가 이전 상태를 전혀 기억하지 않아도 되니 가볍고 빠르다. 하지만 우리가 원하는 서비스를 만들려면 상태 유지가 필요하다.


2. 세션과 쿠키: 기억력을 만드는 방법

이 문제를 해결하기 위해 세션과 쿠키가 등장했다. 둘은 역할이 다르다.

구분저장 위치역할
쿠키클라이언트(브라우저)세션 ID 보관
세션서버실제 사용자 데이터 보관

작동 방식은 다음과 같다.

1. 사용자가 로그인
         ↓
2. 서버가 세션 생성 → 고유한 세션 ID 발급
         ↓
3. 세션 ID를 쿠키에 담아 클라이언트에 전달
   Set-Cookie: JSESSIONID=abc123; HttpOnly
         ↓
4. 이후 요청마다 브라우저가 쿠키를 헤더에 담아 자동 전송
   Cookie: JSESSIONID=abc123
         ↓
5. 서버가 세션 ID로 사용자 데이터 조회

세션 자체가 쿠키에 담기는 게 아니다. 세션을 찾는 열쇠(ID)만 쿠키에 담긴다. 실제 데이터는 서버에 있다.

Spring에서의 세션 사용

Spring에서는 HttpSession을 통해 세션을 다룬다.

@PostMapping("/login")
public ApiResponse<LoginResponse> login(HttpSession httpSession,
        @RequestBody LoginRequest request) {
    LoginResponse loginResponse = authService.login(request);

    // 세션에 사용자 정보 저장
    httpSession.setAttribute("userId", loginResponse.getUserId());
    httpSession.setAttribute("email", loginResponse.getEmail());

    return ApiResponse.success(loginResponse);
}

@GetMapping("/status")
public ApiResponse<LoginResponse> checkStatus(HttpSession httpSession) {
    Long userId = (Long) httpSession.getAttribute("userId");
    String email = (String) httpSession.getAttribute("email");

    if (userId == null && email == null) {
        throw new DomainException(DomainExceptionCode.NOT_FOUND_USER);
    }

    return ApiResponse.success(authService.getLoginInfo(userId, email));
}

@GetMapping("/logout")
public ApiResponse<Void> logout(HttpSession httpSession) {
    httpSession.invalidate(); // 세션 무효화
    return ApiResponse.success();
}

로그인 후 브라우저 개발자 도구의 Application → Cookies 탭을 보면 JSESSIONID 쿠키가 생성된 것을 확인할 수 있다. 로그아웃 후에는 이 쿠키가 삭제된다.


3. 단일 서버 세션의 한계

단일 서버에서는 세션이 잘 동작한다. 하지만 실제 서비스는 서버가 한 대가 아니다.

트래픽이 늘어나면 서버를 여러 대 두고 로드 밸런서가 요청을 분산시킨다. 이때 문제가 생긴다.

사용자 → 로드밸런서 → 서버A (로그인, 세션 생성)
사용자 → 로드밸런서 → 서버B (세션 없음 → 로그인 풀림)

서버A에 저장된 세션을 서버B는 모른다. 사용자는 매번 다른 서버에 붙을 때마다 다시 로그인해야 한다. 이렇게 되면 로드 밸런서를 두는 의미가 없어진다.


4. 세션 클러스터링: 세 가지 해결책

이 문제를 해결하기 위한 방법이 세션 클러스터링이다. 크게 세 가지 방식이 있다.

Sticky Session

로드 밸런서가 특정 사용자의 요청을 항상 같은 서버로 보내는 방식이다.

사용자A → 항상 서버A
사용자B → 항상 서버B

구현이 간단하지만 치명적인 단점이 있다. 서버A에 장애가 생기면 서버A에 묶인 모든 사용자의 세션이 사라진다. 또한 특정 서버에 요청이 몰릴 수 있어 로드 밸런싱의 효과가 떨어진다.

Session Replication

모든 서버가 동일한 세션 데이터를 복제해서 가지고 있는 방식이다.

서버A: {userId: 1, email: "test@test.com"}
서버B: {userId: 1, email: "test@test.com"}  ← 복제
서버C: {userId: 1, email: "test@test.com"}  ← 복제

어느 서버로 요청이 가도 세션이 유지된다. 하지만 서버가 늘어날수록 복제 비용이 커지고 네트워크 부하가 증가한다.

Centralized Session Store

세션 데이터를 별도의 중앙 저장소에 보관하고, 모든 서버가 이 저장소를 바라보는 방식이다.

서버A ─┐
서버B ─┼─→ Redis (세션 저장소)
서버C ─┘

어떤 서버로 요청이 가도 Redis에서 세션을 조회하기 때문에 항상 일관된 데이터를 사용할 수 있다. 현업에서 가장 많이 쓰이는 방식이다.

비교 요약

방식핵심 단점적합한 환경
Sticky Session장애 시 세션 유실, 부하 불균형소규모 시스템
Session Replication복제 비용, 서버 증가 시 부하고가용성 요구 환경
Centralized Store중앙 저장소 장애 시 전체 영향대규모 서비스

5. Redis를 세션 저장소로 쓰는 이유

Centralized Session Store 방식에서 Redis가 표준처럼 쓰이는 이유가 있다.

세션은 매 요청마다 조회된다. 사용자가 페이지를 이동할 때마다, API를 호출할 때마다 서버는 세션을 확인한다. 이 조회가 느리면 전체 응답 속도가 느려진다.

Redis는 In-Memory 저장소다. 데이터를 디스크가 아닌 메모리(RAM)에 저장하기 때문에 조회 속도가 극도로 빠르다.

일반 DB (디스크): 수 ms ~ 수십 ms
Redis (메모리): 수십 μs

Spring에서 Redis 세션 연동

build.gradle에 의존성을 추가한다.

implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.springframework.session:spring-session-data-redis'

application.yml에 Redis 설정을 추가한다.

spring:
  data:
    redis:
      host: localhost
      port: 6379
  session:
    store-type: redis

이게 전부다. 기존 코드(HttpSession)는 한 줄도 바꾸지 않아도 된다. Spring Session이 내부적으로 세션 저장소를 서버 메모리에서 Redis로 교체해준다.

동작 확인

Redis CLI에서 실제로 세션이 저장됐는지 확인할 수 있다.

docker exec -it redis-server redis-cli
> keys spring:session*
1) "spring:session:sessions:b1ccba81-a7f1-4601-9626-df84218037ca"

> hgetall spring:session:sessions:b1ccba81-...
sessionAttr:userId → 1
sessionAttr:email  → test@test.com

서버 메모리가 아닌 Redis에 세션 데이터가 저장된 것을 확인할 수 있다.

Redis 연동 후 가장 눈에 띄는 차이는 서버를 재시작해도 세션이 유지된다는 점이다. 기존에는 서버 메모리에 세션이 있었기 때문에 재시작하면 세션이 사라졌다. Redis에 저장된 세션은 서버와 독립적으로 존재한다.


마치며

HTTP의 Stateless 특성을 세션과 쿠키로 보완하고, 다중 서버 환경에서 Redis를 이용해 세션을 중앙에서 관리하는 흐름을 살펴봤다. 핵심은 세션 데이터를 서버 바깥으로 꺼내는 것이다.

profile
Backend Developer | Aspiring Full-Stack Enthusiast

0개의 댓글