분산 환경에서 세션 불일치 문제 해결방법

김태현·2023년 10월 19일
0
post-thumbnail

개요

아래와 같이 여러 대의 서버 인스턴스를 활용하는 서버에서, 세션 데이터를 각 서버마다 별도로 관리하도록 설계한다면 세션 불일치 문제가 발생할 수 있습니다. 예를들어, 사용자가 로그인한 후 다른 서버로 요청을 보내면 해당 서버의 세션 저장소에 사용자가 로그인한 데이터가 없을 수 있습니다. 이러한 문제를 세션 불일치 문제라고 하며, 이 문제를 해결하기 위해 웹 서버는 세션 데이터를 서버 메모리가 아닌 외부 저장소에 저장해야 합니다.

서버 아키텍처


세션 데이터를 내부 메모리에 저장하는 경우


외부 저장소를 사용하는 경우

Spring의 세션 클러스터링은 기본적으로 Redis를 사용합니다. 세션 정보를 외부 서버에 저장하는 작업을 수행하면서 스프링은 왜 Redis를 사용하는지 궁금했습니다. DBMS를 사용할 수도 있고, EhCache, MongoDB, Memcached 등 여러가지 캐시엔진을 사용할 수 있기 때문입니다.

DBMS vs NoSQL

세션 데이터를 저장할 때 NoSQL 데이터베이스를 사용하는 이유는 다음과 같습니다.

확장성

NoSQL 데이터베이스는 수평 확장에 유리합니다. 즉 시스템에 더 많은 트래픽이나 사용자가 추가되었을때 세션 데이터를 저장/검색하기에 더 효율적입니다.

속도

NoSQL 데이터베이스는 DBMS에 비해 빠른 응답시간을 제공합니다. 즉 사용자가 로그인했을때 세션 데이터를 검색하는 작업을 빠르게 처리할 수 있습니다.

세션 객체의 형태

세션 객체는 Key에 해단하는 SESSION ID와 이에 대응되는 Value로 구성됩니다. Value에는 세션 생성시간, 마지막 접근 시간, 사용자 등의 정보가 Map 형태로 저장됩니다. 즉 세션 객체는 Key-Value 형식으로 데이터를 저장합니다.

NoSQL 또한 Key-Value 형식으로 데이터를 저장하기 때문에 세션 객체를 저장하기 위한 데이터베이스는 DBMS에 비해 NoSQL이 적합합니다.

NoSQL 저장구조

이렇게 NoSQL 데이터베이스의 특징은 저장구조가 Key-Value 형태로 단순하다는 것입니다. 결과적으로 DBMS와 같이 복잡한 조회 연산이 지원되지 않지만, Key 값을 사용해서 데이터를 조회하기 때문에 고속 읽기와 쓰기에 최적화된 경우가 많습니다.

특히 세션에 저장되는 데이터의 경우 대부분 간단한 연산을 통해 처리할 수 있습니다. 이러한 맥락에서 Key-Value 형태의 데이터베이스가 세션 저장소로 적합하다고 볼 수 있습니다.

세션 불일치 문제 해결방법

Stick Session

Sticy Session은 웹 애플리케이션의 로드 밸런싱에서 사용되는 기술입니다. 이 기술은 클라이언트의 요청이 향상 특정서버로 라우팅되도록 보장합니다. 클라이언트가 웹 애플리케이션에 처음 요청을 보내면 로드밸런서는 요청을 수신하고 이 요청을 처리할 서버를 선택하는 알고리즘을 사용합니다. 서버는 클라이언트에 대한 세션을 생성하고 세션 정보를 유지합니다. 세션은 일반적으로 클라이언트 식별자 또는 SessionID와 관련이 있습니다. 이후 클라이언트가 추가 요청을 보낼 때 로드 밸런서는 클라이언트의 세션 정보를 사용하여 이 클라이언트의 모든 후속 요청을 동일한 서버로 보내도록 지정합니다.

Stcky Session 방식은 다음과 같은 문제가 있습니다.

  1. 서버 부하 불균형: 만약 몇몇 사용자가 다른 사용자들보다 더 많은 요청을 보내는 경우, 해당 서버에 부하가 집중될 수 있습니다. 이로 인해 다른 서버는 상대적으로 가벼운 부하를 갖게 되고, 전체 시스템의 부하 분산이 불균형해질 수 있습니다.

  2. 서버 장애 시 문제: 특정 서버가 고장이 난 경우, 해당 서버에 연결된 모든 클라이언트의 세션 데이터는 손실됩니다. 사용자들은 다른 서버로 리디렉션되어야 하며, 이 과정에서 사용자 경험에 영향을 미칠 수 있습니다.

  3. 유연성 부족: 서버를 확장하거나 추가하는 경우 Sticky Session 방식은 관리가 어려울 수 있습니다. 새로운 서버가 추가되면 세션 데이터의 동기화와 관리가 복잡해질 수 있습니다.

Session Clustering

Session Clustering은 웹 애플리케이션 환경에서 세션 데이터를 여러 서버 간에 공유하고 동기화 하는 기술입니다. 이를 통해 웹 애플리케이션은 여러 서버에서 동일한 세션 데이터에 접근할 수 있으며 사용자 요청이 다른 서버로 라우팅될 때에도 일관성 있는 세션 정보를 제공할 수 있습니다.

Session Clustering은 다음과 같은 문제점이 있습니다.

  1. 비효율적인 메모리 관리: 세션 클러스터링은 모든 서버 간에 세션 데이터를 복제합니다. 따라서 모든 서버는 동일한 세션 데이터를 가지고 있어야 합니다. 이는 메모리 사용량을 증가시키고 메모리 관리에 어려움을 초래할 수 있습니다. 특히 세션 데이터가 크거나 많은 사용자 세션이 활성화된 경우, 서버의 메모리 부담이 증가할 수 있습니다.

  2. 높은 네트워크 트래픽: 세션 클러스터링은 세션 데이터를 서버 간에 복제하고 동기화하기 위해 네트워크를 사용합니다. 세션 데이터의 변경이 발생할 때마다 데이터를 다른 서버로 전송해야 하므로 많은 네트워크 트래픽이 발생할 수 있습니다. 이로 인해 대역폭을 소비하고 네트워크 병목 현상을 초래할 수 있습니다.

Session Storge

세션 스토리지 방식은 세션 저장소를 외부 서버로 분리하는 방식입니다.

해당 프로젝트에서는 Session Storge 방식을 도입했습니다. 이 방식을 선택한 이유와 얻은 이점은 아래에 이어서 설명하겠습니다.

캐시엔진 비교

Redis - Memcached

NoSQL 데이터베이스 중 세션 저장소로 가장 많이 쓰이는 것이 Redis와 Memcached 입니다.
Memcached는 컴퓨터(서버)의 전원이 꺼지게 되면 데이터가 사라지기 때문에 영속성을 보장할수 없습니다. 이 때문에 엄밀히 말하면 데이터베이스라고 할 수 없습니다. 반면 Redis의 경우 AOFRDB 기능을 통해 디스크에 데이터를 저장하여 영속성이 보장됩니다. 여기서 세션은 영속성이 보장될 필요가 없기 때문에 두 가지 캐시엔진 모두 사용할 수 있습니다.

Memcached와 Redis는 모두 Key-Value 형식으로 데이터를 저장하고, 인메모리 기반으로 동작한다는 공통점이 있습니다. 즉 두가지 캐시엔진 모두 데이터에 빠르게 엑세스할 수 있다는 장점으로 이어집니다.

Redis > Memcached

첫 번째 고려사항은 장애복구(failover)입니다.

Redis의 경우 Replication을 지원하기 때문에 서버 하나에 장에가 발생하더라도 복제된 Slave 서버를 사용하여 중단없이 서비스를 운영할 수 있습니다.

반대로 Memcached는 Replication을 지원하지 않습니다. 대신 Memcahced 서버에 데이터를 분산시켜 저장/관리하기 때문에 서버 1대에 장애가 발생하더라도 나머지 노드에 있는 데이터들은 재배치할 필요 없이 장애가 발생한 노드의 데이터에만 손실이 일어납니다. 이러한 방식의 경우 Memcached 서버의 숫자를 늘리게 되면 그 피해를 감소시킬 수 있습니다.

Memcached < Redis

대규모 트래픽을 받는 상황에서 Redis는 Memcached에 비해 응답속도가 떨어질 수 있습니다.

Redis - jemalloc 메모리 할당구조(mollac, free)

예를들어 1Byte 데이터를 저장하기 위해서, 운영체제는 페이지 단위로 용량을 사용해야합니다. 약 페이지 단위가 4000 Byte 라고 가정하면, 1Byte만 할당되어도 운영체제는 4000 Byte를 할당 하는 것입니다. Page1 에 1Byte가 저장되어있는 상황에서 4000Byte 짜리 데이터가 들어온다면 Page1에 남은 용량인 3999Byte를 사용하지 못하고 Page2에 데이터가 할당됩니다. 이는 메모리 단편화 문제를 발생시키고, 이러한 단편화 문제로 인해 응답속도가 느려질 수 있습니다.

Memcached - slab 메모리 할당구조
반면 Memcached의 경우 slab allocator 알고리즘을 사용하기 때문에 메모리 단편화가 발생하지 않고, 결과적으로 메모리 할당을 다시 하지 않아도 된다는 장점이 있습니다.

응답속도 면에서 Memcached는 Redis에 비해 성능이 잘 나옵니다. Redis가 jemalloc 메모리 할당 구조를 사용하기 때문입니다. malloc과 free를 사용하여 메모리 파편화가 발생하고 재할당 비용으로 인해 응답속도가 느려지는 이슈가 발생합니다. Memcached는 slab 메모리 할당 구조를 사용하기 때문에 이런 재할당 비용이 발생하지 않습니다.

결과적으로 Memcached가 Redis에 비해 응답속도에서 더 좋은 성능을 보여주지만 Redis의 응답속도가 떨어지는 것은 아닙니다.

해당 프로젝트에서 Redis를 결정한 이유는?


AWS 공식문서에 따르면 두 가지 솔루션 모두 각각의 장단점을 가지고 있다고 합니다. 또한 Redis는 String 이외에도 다양한 자료구조(List, Set, Sorted Set)등 다양한 자료구조를 지원하지만 세션 스토리지에서 큰 메리트로 다가오지 않았습니다.

해당 프로젝트에서 Redis를 선택한 가장 큰 이유는 스프링에서 Redis API를 지원하기 때문이었습니다. 즉 Spring-data-redis를 의존성에 추가한다면, 스프링의 추상화 기능을 사용하여 쉽게 Redis를 사용할 수 있다는 장점이 존재했습니다.

이런 이유로 Redis를 세션 스토리지로 선택하였습니다.

참고

https://stackoverflow.com/questions/17143268/using-a-nosql-system-to-store-session-data
https://stackoverflow.com/questions/10558465/memcached-vs-redis
https://aws.amazon.com/ko/elasticache/redis-vs-memcached/

profile
안녕하세요. Java&Spring 기반 백엔드 개발자 김태현입니다.

0개의 댓글