인생을 코딩하다.

[Spring] 세션 불일치 문제가 발생하는 이유와 세션 불일치를 해결하는 법 본문

Spring

[Spring] 세션 불일치 문제가 발생하는 이유와 세션 불일치를 해결하는 법

Hyung1 2021. 6. 16. 21:58
728x90
반응형

프로젝트에서 세션 로그인 기능을 만들었습니다. 그리고 추후 저는 대용량 트레픽을 감당할 수 있는 서버를 만들 계획이 있었습니다.

 

대용량 트레픽을 감당할 수 있는 서버를 만들기 위해 scale up을 고려해 서버를 한 대만 놓는다면, 서버 한 대에 모든 부하가 집중되므로 장애 시 서버가 복구될 때까지 서비스를 중단해야 하는 상황이 발생합니다.

  • 사용하려던 서비스가 중단된다면, 그에 안좋은 기억이 생기고 타 서비스로 이용을 바꾸거나, 서비스를 사용하지 않을 수 있는데, 이것은 엄청난 비즈니스 손실(수익 손실)이 생길 수 있습니다.

그러기 위해선 scale out을 고려해 서버를 확장해야 했습니다.

 

하지만 서버 확장시 제가 만든 세션 로그인 기능에 문제가 생길 우려가 있었습니다. 이 문제는 아래에서 그림과 함께 설명해놓았습니다,

 

저는 이 문제를 해결하기 위해 여러가지 고민을 해보았습니다.

 

우리는 한 서버가 모든 트레픽을 감당하게 되서 발생하는 문제점들을 해결하기위해 서버를 로드밸런싱합니다.

 

로드밸런싱 : 대용량 트레픽을 장애없이 처리하기 위해 여러 대의 서버에 적절히 트래픽을 분배하는 것

 

로드밸런싱을 하면 세션의 불일치 문제가 발생합니다. 왜 그럴까요??

세션 불일치 문제가 발생하는 이유

세션은 key와 value로 이루어진 서버의 저장소입니다.

 

Scale out을 고려해 서버가 두 대 이상인 경우 다른 서버에서는 해당 세션이 없기 때문에 문제가 발생할 수 있습니다.

 

예를 들어 서버 세 대가 있다고 가정해봅시다.

session db가 각 서버의 램 공간에서 돌아가고 있습니다.

 

로그인해서 

 

1 . 클라이언트가 로드밸런서를 통해서 1번 서버에서 session Id를 발급받았다고 해보죠.

2. 그럼 클라이언트는 1번 서버의 session ID를 쿠키에 담아서 가지고 있습니다.

3. 그 다음 클라이언트가 또 요청을 보내서 로드밸런서가 2번 서버로 보냅니다.

4. 하지만 2번 서버에서는 session ID를 찾을 수가 없습니다.

 

그럼 2번 서버에서는 로그아웃된 페이지를 보게되고, 다시 로그인이 필요하게 됩니다.

 

로드밸런싱 된 서비스에서 이런 문제가 발생한다면 누가 그 서비스를 이용할까요? 이 문제들로 인해 서비스를 이용하는 유저들이 줄어든다면, 수익과 관련된 큰 문제까지 발생할 것이라고 생각합니다.

그럼 어떻게 해결해야 할까요?

Sticky Session

  • 첫 request에 대한 응답을 준 서버에만 세션이 관리됩니다.
  • 특정 세션의 요청을 처음 처리한 서버로만 보내는 것이라고 할 수 있습니다.

1. 클라이언트가 첫 1번 서버로 request를 합니다.

2. 1번 서버는 session Id를 클라이언트에게 response합니다.

3. 클라이언트는 1번 서버를 통해 세션을 생성합니다.

1번 서버에서 세션을 생성했다면 그 다음 요청들은 모두 1번서버가 담당하게 됩니다. 

 

다시 클라이언트는 로그인 요청을 합니다. 따라서 요청은 WAS에 session이 저장되어있는 1번 서버로만 갑니다.

 

이렇게 첫 request에 대한 응답을 준 서버에만 세션이 갈 수 있는 이유는 바로 로드밸런서 때문인데요, 

 

로드 밸런서는 요청에 따라 보내야 할 인스턴스를 찾기 위해서 쿠키정보를 활용합니다. 로드 밸런서가 한 요청을 받게 되면 우선적으로 요청에 쿠키정보가 있는지 부터 확인하고 쿠키의 정보를 확인했다면, 해당 요청은 로드 밸런서에 의해 해당 쿠키가 생성되어 있는 인스턴스로 보내지게 됩니다. 만약 존재하지 않는 쿠키라면 로드 밸런서의 알고리즘에 의해 선택된 다른 인스턴스에 쿠키가 생성되어 다음에 똑같은 요청이 오면 같은 경로로 맵핑시켜 줄 수 있도록 합니다.

 

즉, 항상 동일한 서버에 요청을 하는것이 아닌 클라이언트의 요청에 쿠키가 존재하는지 확인 후 요청 작업이 이루어집니다. 만약 쿠키가 존재하지 않는다면 기존 로드밸런싱 방법에 의해 요청이 이루어집니다.

 

이것이 sticky session의 개념이라고 할 수 있습니다.

 

하지만 이것은 큰 단점이 있습니다.

1. 세션을 고정해놓기 때문에 그 해당 WAS가 과부하가 올 수 있습니다. 그래서 로드밸런싱 기능이 잘 작동하지 않을 수 있습니다.

  • 이것은 OSI 7계층 중 L4와 관련이 있는데요, L4 스위치에는 Sticky라는 옵션이 있습니다. 해당 옵션을 활성화 시키면 로드밸런서든 timeout 시간 내에 재접속한 동일 사용자에 대해서 같은 서버로 접속할 수 있도록 할당해 줍니다. 이 경우에 session을 유지할 수 있지만 각 접속마다 적절한 서버로 할당해주는 것이 아닌 특정 서버로만 할당을 강제하기 때문에 상황에 따라 한 서버로 접속이 몰리는 현상이 발생할 수 있습니다.
  • 대표적인 사례들로 OP 주소에 따른 맵핑을 예로 들 수 있습니다.
    • 프록시 서버를 이용해 접속할 경우, 사용자 ip는 동일하게 나타나므로 다수의 접속을 한대의 서버에서 모두 소화해야하는 현상이 발생합니다.
    • 또한 와이파이 공유기로 예를 들자면, 와이파이 공유기에는 218.~~~.~~~ 의 패턴을 가진 외부 IP 주소가 할당이 됩니다. 그리고 해당 공유기를 사용하는 사용자들은 연결하는 순간 가상의 192.~~~ 또는 172.~~~ 패턴을 가진 내부 아이피 주소를 할당 받습니다. 즉, 하나의 외부 IP는 여러개의 내부 IP주소를 가질 수 있다는 뜻입니다. 만약 하나의 외부 IP를 수천 명이 사용하고 있다는 가정 하에, Sticky-Session 이 IP 주소의 패턴에 따라 맵핑을 시키면 어떻게 될까요? 같은 내부망에 있는 사람들이 한꺼번에 하나의 서버에만 요청이 들어오기 때문에 다른 서버에 비해 트래픽이 과하게 몰릴 수 있습니다.

2. 위와 같은 이유로 최악의 경우 서버가 다운될 수 있습니다. 특정 서버에 Fail나면 특정 서버에 붙어있는 세션이 소실될 수 있습니다. 그럼 Fail이 발생하지 않은 서버에 다시 로드밸런싱 하게 됩니다. 결국 다시 로그인 해야하는 똑같은 문제가 발생하겠죠?

 

이런 문제를 해결하기 위해서 L7을 사용해서 사용해서 Cookie에 있는 sessionID 기반의 switching을 수행합니다.

Session Clustering

여러 WAS 세션을 동일한 세션으로 관리하는 것입니다.

새로운 서버가 하나 뜰 때마다 기존 WAS에 새로운 서버의 IP/Port를 입력해서 클러스터링 해줘야합니다.

이것은 2번 서버가 Fail이 나더라도 기존 1번 서버의 세션은 남아 있기때문에 다시 로그인 할 필요가 없어집니다.

 

클러스터링이란 DB 분산 기법 중 하나로 DB 서버를 여러 개 두어 서버 한 대가 죽었을 때 대비할 수 있는 기법입니다.

 

좀 더 쉽게 구체적으로 설명해드리자면, 

 

1번 서버에서 login session이 저장되었다면, 2번 서버와 3번 서버에도 서버1에 저장되어있는 세션을 전파(복사)하는 것이라고 할 수 있습니다.. 따라서 1번 방법의 문제점인 특정 서버에만 트래픽이 몰리는 문제를 해결할 수 있게 됩니다.

 

하지만 이 방법도 단점이 있습니다.

 

  • 하지만 이 방식은 scale out 관점에서 새로운 서버가 하나 뜰 때마다 기존에 존재하던 WAS에 새로운 서버의 IP/Port를 입력해서 클러스터링 해줘야 하는 단점이 있습닏.
  • 조금더 쉽게 말씀드리자면 세션을 전파(복사)하는 작업을 진행하기 때문에 모든 서버에 동일한 세션 정보가 저장됩니다. 즉, 효율적인 메모리 관리가 이루어지지 않습니다. 
  •  Tomcat을 예로 들었을 때, 모든 데이터를 각각의 Tomcat 노드에게 전달해야 해야 하고 배포하는 노드가 아닐 경우에도 복사를 진행하기 때문에 불필요하게 메모리를 차지하게 됩니다. 이로인해 오히려 성능이 크게 떨어질 수 있습니다.
  • 데이터 변경이 발생할때 마다 세션을 전파(복사)하는 작업이 일어나기 때문에 네트워크 요청 트래픽이 증가하게 됩니다. 서버가 늘어날수록 이 트레픽은 더욱 심하게 증가될 것입니다.
  • 세션 전파 작업 중 모든 서버에 세션이 전파되기까지의 시간차로 인한 세션 불일치 문제와 같은 예상치 못한 문제가 발생할 가능성이 존재합니다.

별도의 Session을 관리하는 저장소(In-memory DB)를 두는 법

서버에 세션 정보를 저장하는 것이 아닌 외부에 저장소를 만들고 이 서버에 모든 데이터를 저장하는 것입니다. 즉 WAS서버들이 외부의 저장소에서 데이터를 읽어오는 것입니다.

1. 1번 서버에서 로그인에 성공하면 별도의 Session 저장소에 session Id가 생깁니다.

2. 그리고 유저는 session Id를 받아옵니다.

3. 그 후 유저는 어떤 서버에 접속하든 별도의 독립된 Session DB에서 읽어오기 때문에 올바른 response를 줄 수 있습니다.

 

해당 방법을 사용하면

  • Sticky session의 문제점인 특정 서버로 트래픽이 몰리는 문제가 발생하지 않고
  • 위와 같은 방식은 새로운 서버를 띄우더라도 해당 서버에만 세션 서버의 정보를 적어주고 연결 해주면 되기 때문에 scale out 할 때 기존 서버의 수정이 발생하지 않는 장점이 있습니다.

해당 세션 서버가 죽는 순간 모든 세션이 사라지기 때문에 이 Redis 서버의 다중화(마스터-슬레이브 복제)도 고려해볼 수 있습니다.

어떤 In-memory DB를 사용하는 것이 좋을까?

별도의 독립적인 session 저장소로 세션의 정합성 문제를 해결하기 위해서는 대표적인 redismemcached 데이터 베이스를 고려해볼 수 있습니다. 

 

redis와 memcached의 차이점에 관한 자세한 글은 이 글을 읽어보시면 됩니다.

 

간단하게 redis와 memcached의 대표적인 차이를 생각해보자면, 저는 memcached에 비해 redis가 다양한 자료구조를 지원하기 때문에 개발의 편의성과 생산성이 증가할 것이라고 생각합니다.

 

예를 들어 실시간 랭킹 서버를 구현할 때 관계형 DBMS를 이용한다면 DB에 데이터를 저장하고, 저장된 SCORE 값으로 정렬하여 다시 읽어오는 과정이 필요합니다. Sorted-Set을 이용하면 더 빠르고 간단할 것 입니다.

 

하지만 memcached는 Sorted-Set을 지원해주지 않죠. 하지만 레디스는 다양한 자료구조를 지원해주기 때문에 Sorted-Set을 비롯한 여러 자료구조를 지원해줍니다. 즉 위의 문제의 결론은 Sorted-Set 이용하면 더 빠르고 간단하게 개발을 할 수 있겠죠.

 

개발을 하다보면 sorted_Set 이외에도 많은 자료구조들이 필요할텐데 다양한 자료구조를 지원하는 점이 다른 in-memory 데이터베이스들에 비해 redis의 장점이라고 할 수 있습니다.

 

또한 레디스는 트랜잭션의 문제도 해결해 줄 수 있습니다. 싱글 스레드로 동작하는 서버의 모든 자료구조는 atomic 하기 때문에, race condition을 피해 데이터의 정합성을 보장하기 쉽죠.

 

즉, 외부의 Collections을 잘 이용하는 것만으로 개발 시간 단축이 가능하고, 생각하지 못한 여러가지 문제를 줄여줄 수 있으므로 개발자는 비즈니스 로직에 집중할 수 있다는 큰 장점이 존재합니다.

 

위에 장점들은 다 알겠는데 사실 Login 기능 구현에서는 위에서 말한 다양한 자료구조를 쓸 일이 없을텐데 왜 레디스를 사용하셨나요?

memcached에 비해 redis는 스프링에서 API를 지원해주기 때문입니다. 이로인해 훨씬 빠르고 간결하고 개발을 할 수 있겠죠.

 

따라서 전 redis를 이용하기로 결정했습니다.

 

그럼 redis에 관해 자세히 알아보기 위해  다음 글을 참고해보도록 할까요?

 

참고하면 좋은 글

* Redis와 memcached 비교

 

 

 

 

 

728x90
반응형
Comments