본문 바로가기

개발

세션 불일치 문제 해결법

 

개발을 평생 단일 서버 환경에서만 할 수 있다면 참 좋겠지만, 사용자가 아무리 늘어나도 트래픽을 무한하게 버틸 수 있는 그런 만능 서버는 존재하지 않기 때문에, 서비스가 성장하는 한 우리는 결국은 서버 대수를 늘리는 scale out을 진행할 수밖에 없을 것이다. 그렇게 단일 서버에서 다중 서버가 되면 개발 시 고려할 사항들이 확 늘어나는데, 오늘은 그 중 서버간 세션 불일치 문제에 대해 다뤄보고자 한다.

 

Session?


세션이란, 클라이언트의 고유 데이터를 저장하기위한 저장소의 개념으로서, 클라이언트 브라우저 종료 시점까지 유효하며 일반적으로 서버 메모리상에 클라이언트의 고유한 세션ID와 함께 저장된다. 여기서 '서버 메모리상에' 라는 부분이 이번 문제의 핵심인데, 두 대의 서버가 존재한다면 세션 정보가 필요한 클라이언트의 요청을 받았을 때 어느 두 대중 어느 서버에 세션 정보를 저장해야할까? 가령 Round-Robin 같은 기법으로 로드 밸런싱 설정만 하고 세션에 대한 아무런 처리도 하지 않는다면, 사용자의 세션 정보는 1번 서버와 2번 서버가 따로 저장되어 데이터가 서로 따로 놀게 될 것이다. 자 그럼 이런 상황을 어떻게 해결하는 게 좋을까? 익히 알려진 방법 몇 가지가 있는데 쉬운 개념부터 하나하나씩 살펴보자.

 

1. Sticky Session


sticky의 뜻이 '끈적끈적한, 달라붙는' 이런 뜻인데, 이런 이름 처럼 이 스티키 세션방식은 클라이언트의 세션 정보가 최초에 어떤 서버에 저장되면, 이후로는 해당 클라이언트 요청은 무조건 그 서버로만 보내는 방식이다. 

 

Sticky Session

그럼 sticky session방식은 어떻게 서버를 지정해서 지속적으로 요청을 주고 받을 수 있는 걸까?

그건 아주 간단한 방법으로 이루어지는데, 요청 처리 순서를 나열하자면 아래와 같다.

 

  1. 클라이언트가 요청을 서버로 보낸다.
  2. 로드밸런서가 요청을 받는데, 요청 쿠키에 서버ID값이 있는지 확인한다.
    • 서버 ID가 있는 경우, 해당 서버로 요청을 보낸다.
      • 해당 서버로 요청을 보냈지만, 서버를 찾을 수 없거나 요청을 받을 수 없는 상태라면, 서버 ID가 없는 경우의 로직을 탄다.
    • 서버 ID가 없는 경우, 일반적 로드밸런싱 알고리즘에 따라 서버로 요청을 보낸다.
  3. 서버가 요청을 받으면, 역시 쿠키에 서버 ID값이 있는지 확인한다.
    • 서버 ID가 있는 경우, 요청을 처리하고 응답을 보낸다.
    • 서버 ID가 없는 경우, 요청을 처리하고 쿠키에 서버ID를 담아 응답을 보낸다.

이와 같이 쿠키를 활용하여 간단하게 서버 식별값을 추가하고, 해당 값을 통해 서버와 클라이언트의 sticky한 연결을 지속시키는 것이다.

 

하지만 이런 Sticky Session 방식에는 몇가지 문제점이 있다.

 

1. 로드 밸런싱의 비효율을 초래

 

만일 서버가 2대인데, 우연하게도 최초요청을 보내는 사용자들이 짝수 요청에만 몰려버렸다고 생각해보자. 그럼 세션 쿠키정보는 두번째 서버에만 계속해서 쌓이게 될 것이고, 사용자들이 이 상태에서 오랜시간 머물며 계속해서 요청을 보내면 트래픽이 2번서버에만 몰리게 된다. 즉, 애써 구성한 로드 밸런서의 실효성이 떨어지는 것이다.

 

2. 장애시 세션 데이터의 유실

 

서버에 세션 데이터가 저장되어있다보니 장애시 다른 서버로 요청을 돌리면 당연히 기존에 저장된 세션데이터를 찾을 수 없어서 쿠키에 세션 ID를 다시 세팅하게 되고, 만일 로그인 세션이었다면 사용자에게 재로그인을 요구하는 상황이 발생하게 된다.

 

 

 

2. Session Clustering


세션 클러스터링 방식은 사용자 세션 정보가 갱신될 때마다 클러스터에 묶인 모든 서버에 세션정보를 복사하여 전파하는 방식으로 세션 불일치 문제를 해결한다. 세션 클러스터링 방법은 WAS마다 일부 차이가 있는데, 가장 대표적인 Tomcat의 클러스터링 방식 두가지를 알아보자.

 

1. All-to-All Session Replication

위에서 설명했듯이, 이 방식은 모든 서버에 세션 데이터를 그대로 복제하는 방법이다.

모든 서버에 저장된 세션 데이터가 동일하기 때문에 로드밸런서에 의해 어느 서버로 요청이 도착하든 같은 처리를 기대할 수 있으며, 특정 서버가 장애가 발생하더라도 즉시 다른 서버가 대응할 수 있기 때문에 장애에도 강하다고 할 수 있다.

 

하지만, 이 방법은 서버가 많아질수록 문제가 커지기 시작한다.

바로 서버간의 세션 복제시에 일어나는 트래픽이 1차적인 문제인데, 서버가 두개 뿐이라면 감수할만한 트래픽이겠지만 만일 서버가 100개라면 한 클라이언트의 세션 데이터 갱신때마다 100번의 서버간의 네트워크 트래픽이 발생해버리기에 서버 성능저하로 이어지게 된다.

그리고 데이터 복제 시, 첫번째 서버에서 마지막 서버까지 복제되는데 시간이 늘어나기 때문에, 아직 복제가 완료되지 않은 상태에서 마지막 서버에 같은 클라이언트 요청이 들어오면 세션 데이터의 불일치가 발생하기도 한다.

또한, 가령 10kb의 세션 데이터를 저장해야 한다면, 모든 서버에 동일한 데이터가 저장되어야 하기 때문에 용량이 x100배가 되어 총 차지 메모리는 1Mb가 돼버려 메모리의 낭비 또한 발생하게 된다.

 

 

 

2. Primary-Secondary Session Replication

위와 같은 All-To-All 클러스터링 방식의 단점을 보완하고자 Primary-Secondary 방식이 고안되었다.

이 방식은 장애 대응을 위한 Seconddary 서버에만 세션 데이터를 그대로 복제하고, 나머지 서버에는 세션ID만 복제하는 방식이다. Primary, Secondary 두 대가 동시에 장애가 나지 않는 이상 세션 데이터의 유실을 막을 수 있고, 서버 수만큼 곱연산으로 증가하던 메모리도 절약할 수 있는 방법이다. 하지만 원본 데이터가 없는 서버로 요청이 전달되면, 다시 Primary, Secondary 서버로 요청을 보내서 정보를 받아오는 트래픽이 추가로 발생한다는 단점도 존재한다.

 

 

 

3. Session Storage


Session Clustering 방식은 Sticky Session의 트래픽 쏠림 현상을 해결할 수 있지만, 서버 대수가 증가할수록 세션 관리의 어려움이 발생한다는 문제가 있다. 이런 문제를 해결하기위한 방법으로 이 세션 스토리지 방식이 있는데, 이 방법은 세션을 관리하는 별도의 스토리지(서버)를 따로 두는 것이다.

 

위와 같이 세션 데이터를 WAS에 그대로 저장하는 게 아닌, 따로 구축해둔 세션 스토리지에 저장하여 요청시마다 세션정보는 세션스토리지에서 받아오는 방식으로 세션 불일치 문제를 해결한다. 이렇게 하면 트래픽 쏠림문제, 메모리 낭비 문제, 서버 대수만큼 증가하는 트래픽 문제들을 모두 해결할 수 있고, 장애 대응의 경우에도 세션 스토리지를 Master-Slave형식으로 나눠서 구성하면 대응이 가능해진다.

 

세션 스토리지로는 Disk방식과 In-Memory방식이 있는데, 세션 데이터는 대부분 영구적인 저장이 필요 없기 때문에 처리 성능이 떨어지는 Disk방식은 거의 사용하지 않고 대부분 In-Memory 방식을 사용한다. Redis나 Memcached를 주로 사용하는데, 각각의 특징에 대해서는 이전에 작성한 Redis게시글을 참고하자.

그리고 혹여나 인메모리 방식을 사용하는데 세션의 영구 저장도 필요한 경우라면 그냥 영속성을 지원하는 Redis를 사용하면 되는 일이기에, Disk방식을 사용하는 것은 웬만해선 생각하지 말자.

 

 

# Reference

1. https://inpa.tistory.com/

'개발' 카테고리의 다른 글

RestTemplate / WebClient, 그리고 Reactive Programming  (0) 2023.03.27
Blocking/Non-Blocking & Sync/Async  (0) 2023.03.15
Mock객체를 활용한 테스트와 유의사항  (0) 2023.03.03
레디스(Redis)란?  (0) 2023.02.20
TDD  (0) 2023.01.12