대규모 서비스에서 세션 불일치 문제를 해결하는 방법

xeonu·2022년 9월 27일
0

Infra

목록 보기
2/4
post-custom-banner

지난 포스팅에서 Scale up 과 Scale out에 대해 비교한 후 현재 개발하려는 웹서비스에 Scale out을 적용하기로 했다. 이번 포스팅에서는 Scale out으로 서버 확장시 발생하는 세션 불일치 문제에 대해 알아보고 이를 해결하는 방법에 대해 알아보자.

🔎 쿠키와 세션

세션 불일치 문제를 이야기하기 전에 쿠키와 세션에 대해서 간략하게 알아보고 넘어가자. 쿠키와 세션은 클라이언트와 서버가 통신할 때 로그인 정보와 같은 특정 정보를 저장하는 방법이다. 매번 정보를 얻기 위해서 요청을 보내거나 데이터베이스를 참조할 필요 없이 쿠키와 세션을 이용하여 데이터를 유지하면 네트워크 I/O를 줄여 성능 향상을 기대할 수 있다.

쿠키는 클라이언트측 PC에 저장되며 아이디 저장, 팝업창 다시보지 않기와 같은 용도로 사용된다.

세션은 서버측에 저장되며 로그인을 한 클라이언트가 다른 페이지를 방문해도 로그인을 유지할 수 있도록 도와준다.

이 포스팅은 서버에 저장되는 세션에 대해서 알아볼 것이고 구체적으로 어디에 저장해야하는지에 초점을 맞춰서 작성하겠다.

🔎 세션 불일치 문제

단일 서버 환경에서는 서버에 세션 저장소를 저장하면 클라이언트의 요청을 하나의 저장소에서 읽고 쓰기만 하면 됐다. 하지만 다중 서버 환경에서는 이야기가 달라진다. 로드밸런서를 통해 클라이언트의 요청을 받은 서버에 내 세션정보가 과연 정말 존재할까라는 의문이 들 수 있다.

A, B 2개의 서버가 있다고 가정하자. 맨 처음 클라이언트가 로드밸런서를 통해 A에 로그인 요청을 보냈고 A서버에서는 클라이언트에 관한 로그인 유지 세션을 생성하여 자기 서버에 저장했다. 동일한 클라이언트가 다시 요청을 보냈을 때 이번에는 로드밸런서가 B라는 서버에 요청을 보냈다고 하자. 하지만 B에는 클라이언트의 로그인 세션이 없어서 클라이언트는 다시 로그인을 해야할 것이다.

이처럼 각 서버로 전달된 요청들과 관련된 세션들이 불일치하여 발생하는 문제들을 세션 불일치 문제라고하고 이를 해결하기 위한 방법에 대해 알아보자.

🔎 해결방법

✅ Sticy Session

로드밸런서가 클라이언트 요청을 항상 같은 서버로 전달하는 방식이다. 어떤 클라이언트가 최초로 A서버로 요청을 전송했다면 로드밸런서는 계속 A서버로 요청을 보낼것이다. 만약 모든 서버에 클라이언트에 대한 세션 정보가 없다면 로드 밸런서 알고리즘에 의해 적절한 서버에 배치한다.

원리 자체가 단순하여 구현하기 쉽고 확실하게 세션 불일치 문제를 해결할 수 있다.

하지만 특정 서버로 트래픽이 집중될 수 있다.

로드밸런서가 클라이언트의 첫 요청을 ip를 기준으로 서버에 배치한다고 하자. 클라이언트가 패킷에 담아 전송하는 ip는 사설 ip일 것이다. 하지만 사설 ip 대역은 다음과 같이 3종류 뿐이다.

  • 10.0.0.0 ~ 10.255.255.255
  • 172.16.0.0 ~ 172.31.255.255
  • 192.168.0.0 ~ 192.168.255.255

와이파이 공유기를 사용하는 사용자라면 내부분 192.168로 시작하는 내부 ip가 기본으로 설정되어 있을 것이다. 만약 로드밸런서가 192.168로 시작하는 ip에서 출발한 요청들을 A서버로 보내면 와이파이 공유기를 사용하는 대부분의 사용자들의 요청은 A서버로 전달될 것이고 그 결과 A서버로만 트래픽이 집중될 것이다. 따라서 Sticky Session에서는 로드밸런싱 알고리즘이 적절하게 적용되어야 효과를 볼 수 있다.

또 다른 문제점은 특정 서버에 장애가 발생하면 해당 서버와 연결되어있던 클라이언트들의 세션 정보가 모두 사라질 수 있다는 것이다.

✅ Session Clustering

각 서버의 세션 저장소를 클러스터라는 단위로 묶고 묶고 클러스터안의 저장소들이 세션을 공유할 수 있게 한다. All-to-All Session ReplicationPrimary-Secondary Session Replication에 대해 알아보자.

ℹ️ All-to-All Replication

모든 서버들이 서로의 세션 저장소 내용을 복제하여 어떤 서버로 방문해도 세션 정보가 존재하게 하는 방식이다.

특정 서버에 장애가 발생하더라도 세션 정보를 유지할 수 있다. 서버개수만큼 세션정보를 복제해 두었으니 다른 서버에서 세션정보를 다시 복사해오면된다. 또 세션이 서버와 다른 곳에 존재하지 않고 무조건 서버 내부에 존재하므로 세션을 가져오기 위한 불필요한 I/O가 발생하지 않는다.

하지만 세션을 서버 대수만큼 복제하므로 메모리 공간을 많이 사용한다. 세션을 생성하고 수정할 때마다 모든 서버에도 복제해야하므로 세션 정보 유지를 위한 I/O가 발생한다. 하지만 세션은 보통 로그인 유지를 위해 사용되고 첫 로그인시 생성만 하므로 복제를 위한 I/O가 성능에 치명적인 영향을 준다고 보기는 어렵다.

서버끼리 세션 정보를 복사하는 과정에서의 시차로 인해 세션 불일치 문제가 발생할 수 있다.

ℹ️ Primary-Secondary Session Replication

Primary 서버와 Secondary 서버에만 전체 세션정보를 저장하고 나머지 서버에는 JSESSIONID(세션의 고유값)만 복제하여 저장한다.

전체 세션 정보는 딱 2개의 서버에만 저장하므로 All-to-All Session Replication보다 세션 저장을 위한 메모리 공간을 절약할 수 있다.

하지만 서버 세팅과정이 복잡하고 여전히 중복된 세션을 저장하기 위한 추가적인 메모리 공간이 발생한다. All-to-All Session Replication과 마찬가지로 세션 정보 유지를 위한 I/O가 발생하고 시차로 인해 세션 불일치 문제가 발생할 수 있다.

✅ Session Storage

각 서버 내부에 세션저장소를 생성하지 말고 외부의 세션 저장소를 생성하고 서버들이 참조하는 방식이다.

서버에 도달한 요청의 세션정보를 얻기위해 추가적인 세션을 참조하는 I/O가 발생하지만 세션을 항상 복제해 둘 필요가 없어 Session Clustering에 비해 메모리 공간을 절약할 수 있고

매번 세션을 생성할 때마다 세션을 복제할 필요가 없어 세션 유지를 위한 I/O가 발생하지 않는다.

하지만 세션 스토리지에 장애가 발생하면 모든 서버는 세션정보를 제공받을 수 없다.

❓ 대규모 서비스에서는 무엇을 선택해야 할까?

세션은 보통 로그인 유지를 위해서 사용한다. 즉 처음 로그인 할 때만 세션을 생성하고 복제하는 과정이 이루어진다. Session Clustering에서 세션을 유지하기 위한 I/O는 반복적이지 않고 단발성이고 세션이 서버 내부에 있기 때문에 세션을 참조하기 위한 네트워크 I/O는 발생하지 않는다.

하지만 Session Storage의 세션을 참조하는 I/O는 반복적으로 발생하고 이는 세션을 참조할 때마다 반복된다.

물론 서버에서 Session Storage로 세션을 요청할 때마다 3-way-handshake가 발생하지는 않지만 네트워크 I/O는 서버에 큰 부담을 주고 사용자가 많아질 경우 더 심해질 것이다.

또 Session Storage 방식도 복제를 통해 세션 정보가 유실되는 것에 대비하여 추가적인 메모리 공간을 요구한다.

Sticky Session으로 구현한 다중 서버 환경에서 특정 서버가 다운됐다고 가정하자. 로드밸런서는 다운된 서버에 트래픽을 보내지 않을 것이다. 개발자들이 열심히 다운된 서버를 복구하였지만 Sticky Session으로 이미 구현한 다중 서버 환경에서 복구한 서버에는 요청이 전송되지 않을 것이다.

로드밸런서가 복구된 서버에 요청을 보내도록 개발자가 구현했다고 하더라도 멀쩡히 서비스를 사용중이던 클라이언트가 갑자기 다시 로그인해야하는 상황이 발생할 수 있다.

이를 보면 네트워크 I/O에 대한 부담도 크지않고 로드밸런서가 균일하게 서버로 요청을 보내도 세션정보가 유지되는 Session Clustering 방식이 매력적이다. 애석하게도 대규모 서비스에서는 대부분 Session Storage 방식으로 세션을 관리한다.

서버에서는 세션을 Disk에 저장하지 않고 Memory에 저장한다. 즉 속도는 빠르지만 용량이 작다. 세션 하나의 용량을 쿠키의 최대 크기인 4KB라고 가정하고 천만명이 사용한다고 가정하면 약 40GB의 메모리 공간이 필요하다. 서버 내 Memory가 세션 저장을 위해서 사용되는 것도 아닌데, 세션 정보 저장을 위해 서버마다 약 40GB의 메모리 공간을 사용하게된다.

대규모 서비스의 경우 엄청난 트래픽을 감당하기 위해 서버의 개수도 매우 많을 것이다. 각 서버마다 수십GB의 메모리공간을 사용한다고 하면 메모리 구매 비용도 만만치 않을 것이다.

이러한 이유로 대규모 서비스에서는 대부분 Session Storage 방식으로 세션 정보를 저장한다.

🔎 In-memory DB란

대규모 서비스에서 세션을 저장하기 위해 별도의 Session Storage를 두어 관리한다는 것을 알았다. 그렇다면 Session Storage를 어떻게 구축할 것인지 고민해야한다. 현업에서는 데이터를 Disk와 Memory에 저장한다. 가격과 용량이 실제 서버운영 측면에서 타협이 가능하기 때문이다. 엄청난 속도를 가지는 레지스터로 Session Storage를 구성하고 싶지만 미국 로또 1등 당첨자가 와도 너무 비싸다고 할 것이다.

Disk는 용량이 크고 전력이 공급되지 않아도 데이터가 유실되지 않는다는 장점이 있다. 하지만 Memory에 비해 속도가 수천배이상 느리다. SSD로 구현한다고 해도 엄청난 차이로 느리다.

하지만 Memory는 빠른속도를 보여주지만 Disk에 비해 용량이 작고 전력이 공급되지 않으면 내용이 유실된다.

하지만 세션 정보를 항상 저장할 필요가 있을까? 로그인 유지를 위한 세션정보의 유효시간은 보통 30분이다. 전력공급이 끊기면 세션을 다시 생성하면 될 일이다.

Memory는 Disk에 비해 용량이 작지만 세션 정보를 저장하기 어려울만큼 작지도 않다. 또 용량이 작다는 단점이 있지만 속도가 매우 빠르다는 엄청난 장점이 존재한다.

이런 이유로 세션 Session Storage를 구축하기 위해서는 절대로 Disk를 사용하지 말고 Memory를 사용해야한다.

Memory에 데이터를 저장하는 DB를 In-memory DB라고 한다. MySQL과 Oracle DB와 같이 Disk에 저장하는 DB와는 다르다. 대표적으로 RedisMemcached가 있고 각각 장단점이 있지만 현재 운영중인 대부분의 서비스에서는 Redis를 많이 사용하는 추세다.

다음 포스팅에서는 Redis와 Memcached의 특징과 어떤 상황에서 어떤 DB를 사용해야하는지 알아보자.


참고문헌

profile
백엔드 개발자가 되기위한 여정
post-custom-banner

0개의 댓글