글쓰는 개발자

[#1] 분산 서버 환경의 세션 정합성 관리 본문

Project/Sell-everything

[#1] 분산 서버 환경의 세션 정합성 관리

개발하자 2021. 6. 28. 02:44
우리가 개발하고 있는 서비스는 어느 규모까지 성장할까요?

우리가 개발하고 있는 서비스는 얼마나 많은 사람들이 사용하게 될까요?

 

 

IT 서비스를 개발한 경험이 있는 사람이라면, 위와 같은 질문을 한 번쯤은 던져볼 수 있을 것이라 생각합니다.

 

그럼, 질문에 대한 답은 무엇일까요? 조금 허무하시겠지만, 저는 "아무도 모른다"라고 생각합니다.

 

당연한 거 아닌가요?

 

네 맞습니다..

 

 

하지만, 그렇다고 해서 아무런 대책 없이 일단 만들어보자!! 하고 서비스를 개발했다가

 

좋은 일이 생겨 서비스의 규모가 커지고 많은 사람들이 이용하게 된다면, 담당 개발자는 마냥 웃을 수는 없을 겁니다.

 

어쩌면 퇴사를 진지하게 고민할지도...

 

동일한 서비스를 제공하는 시스템이라 할 지라도, 규모가 큰 서비스와 규모가 작은 서비스는

 

다방면에서 발생하는 이슈의 관심사가 매우 다르다 보니, 작은 규모만 생각하고 만들었던 서비스의 많은 부분을

 

변경해야 하는 처지에 놓이게 될 테니까요.

 

미래에 대비하는 개발자

 

 

그래서 저는, 어떻게 될지 모를 미래에 대해 미리 생각하여

 

발생할 수 있는 많은 변수들을 고려하여 시스템을 구성하고,

 

기술에 대한 깊은 지식을 기반으로 여러 가지 trade off를 고려하여

 

우리 상황에 맞는 기술을 사용하는 것이 개발자에게 꼭 필요한 자질 중 하나라고 생각합니다.

 

그래야 앞서 말씀드린 것처럼 다양한 변수 상황에도 유연하게 대처할 수 있고,

 

더 견고한 소프트웨어를 제작할 수 있을 테니까요.

 

 

오늘은, 그러한 관점에서 생각해 본 이슈 중 하나였던 '다중 서버 환경에서의 세션 관리'에 대한 이야기를 해보고자 합니다.

 

IT 서비스의 규모가 커지면서, 여러 대의 서버가 들어서게 되면 서버 간 세션 데이터의 정합성, 즉 동기화의 관점에서

 

문제가 발생할 수 있고, 이러한 동기화 문제의 해결 방법 중에 현재의 시스템 상황을 고려하여 성능 저하를 최소화하는

 

방법을 선택해야 합니다.

 

세션 데이터의 정합성을 보장하는 기술에는 어떤 것들이 있는지, 그러한 기술들이 지금의 환경에 적용하기에

 

적합한지, 기술 간의 trade-off를 고려하여 어떠한 기술을 선택했는지 등에 대한 이야기를 나눠보겠습니다.

 

 


목차

  1. Scale Up, Scale Out
  2. 세션 정합성 이슈
  3. Sticky Session
  4. Session Clustering
  5. In-memory Database

 

Scale Up, Scale Out

Scale Up과 Scale Out은 컴퓨팅 자원의 성능 향상이 필요한 경우 적용할 수 있는 대표적인 두 가지 방법입니다.

 

서버의 규모가 커지고, 사용자 트래픽이 증가하여 현재 보유한 자원으로는 처리하는 것이 불가능하거나 비효율적일 때

 

Scale Up이나 Scale Out을 통해 컴퓨팅 자원을 확장하여, 더 나은 서버 퍼포먼스를 기대할 수 있습니다.

 

Scale Up, 수직 확장
Scale-up is done by adding more resources to an existing system to reach a desired state of performance. For example, a database or web server needs additional resources to continue performance at a certain level to meet SLAs. More compute, memory, storage, or network can be added to that system to keep the performance at desired levels. When this is done in the cloud, applications often get moved onto more powerful instances and ...

 

Scale Out, 수평 확장
Scaling up makes sense when you have an application that needs to sit on a single machine. If you have an application that has a loosely coupled architecture, it becomes possible to easily scale out by replicating resources. Scaling out a microservices application can be as simple as spinning up a new container running a webserver app and adding it to the load balancer pool. When scaling out the idea is ....

 

위의 문서를 간단하게 요약하면, Scale up은 기존에 존재하던 컴퓨팅 성능을 향상하는 것을 말합니다.

 

CPU의 처리량을 향상한다거나, RAM 용량을 키우는 방식 등이 있겠네요.

 

Scale up을 서버에 적용하면, 동일한 시간에 더 많은 일을 처리하는 고성능 서버 환경을 구성할 수 있습니다.

 

부하가 많이 들어오는 미디어 작업이나 배치 처리, 딥러닝 모델 학습 등의 고성능 컴퓨팅 자원을 요구하는 곳에 알맞은 방식이 되겠죠.

 

 

Scale up

 

 

Scale out은 동일한 성능의 컴퓨팅 자원을 복제하여 여러 대 설치하는 것을 의미합니다.

 

Scale out의 방식을 서버에 적용하면, 많은 사용자의 요청이 발생하더라도 네트워크 환경 및 서버 부하를 고려하여

 

트래픽을 분산해줄 수 있습니다. 또한, RTT(Round-trip-time)를 고려하여 지리적으로 먼 지역에 위치한 사용자들에게

 

가까운 서버를 제공하여 더 쾌적하고 빠른 서비스 환경을 제공할 수 있는 등의 유연한 처리도 가능해집니다.

 

 

Scale out

 

 

그럼 Scale Up과 Scale Out, 둘 중 어느 것을 적용하면 좋을까요?


위에 대한 명확한 정답은 없습니다. 자금이 넉넉하게 받쳐준다면, Scale up된 고성능 컴퓨팅 자원을 수백대로 Scale out 해주면 가장 좋겠죠?

 

하지만 우리의 자금은 늘 한정적이고, 따라서 현상황을 고려하여 더 효율적인 퍼포먼스를 발휘하는 선택을 해야 합니다.

 

제가 진행하는 프로젝트는 B2C, 즉 다수의 고객을 상대하는 웹 서버 기반의 IT 서비스이기 때문에 Scale out이 더 적합한 선택이라고 생각했습니다.

 

근거는, 첫째로 Scale Out이 "확장에 유리하다"는 이점이 있다고 생각했기 때문입니다.

 

우리가 서버를 확장하기 위해서, Scale Out은 단순히 추가적인 서버를 설치하여 서버 프로그램을 실행시킨 후,

 

해당 PC가 클라이언트 요청을 받아 처리할 수 있도록 연결해주기만 하면 됩니다.

 

만약 Scale Up을 확장 전략으로 택했다면, 컴퓨팅 자원의 성능 향상을 위해 운용 중인 서버 PC를 조작해 주어야 하고

 

그러한 과정에서 운영 중인 서비스를 중단해야 할 가능성을 배제할 수 없습니다.

 

IT 서비스를 운영하는 시간은 곧 기업의 수익으로 환산될 것이고, 서비스의 중단은 중단 시간 동안 벌어들일 수 있는

 

수익을 포기하는 것과 다름이 없습니다. 또한, 서버 확장마다 서비스가 중단된다면 서비스 사용자 경험에도 악영향을

 

끼칠 것입니다. 서비스가 확장되어 글로벌 시장에 진입하고자 할 때에도 앞서 말씀드린 RTT로 인해 기존에 사용하던

 

국내 서버를 사용하면 속도가 저하될 것이고, 이는 사용자 경험에 악영향을 주어 경쟁력이 떨어지는 제품이 될 것입니다.

 

그러한 경우에도 Scale Out의 경우에는 해외 서버를 설치하고, 해외 사용자의 요청을 처리할 수 있도록 한다면

 

기존의 국내 서버를 통한 운영과 비슷한 퍼포먼스를 보일 수 있겠지만, Scale Up은 이러한 대처가 불가능합니다.

 

그래서 확장과 관련하여 발생할 수 있는 다양한 상황에 대해 Scale Out이 더 유연하게 대처할 수 있다고 판단했습니다.

 

 

두 번째는, 시스템 장애가 발생하더라도 더 빠른 대처 및 복구가 가능하다고 생각했기 때문입니다.

 

시스템 설계를 할 때에 '단일 장애점(Single Point of Failure)'을 갖는 구조로 설계하면 시스템에 장애가 일어날 때

 

관련한 모든 서비스가 중단되기 때문에, 단일 장애점을 갖는 것은 매우 위험합니다.

 

이런 문제도 Scale Out은 대처하기 좋겠죠. 한 서버에 장애가 발생하면, 다른 서버로 요청을 이관하면 될 테니까요.

 

하지만 Scale Up을 했을 때는, 특히 서버가 한 대인 경우에 장애가 발생하면 그 시간 동안 서비스 운영이 불가능한 큰 위험성을 가지고 있습니다.

 

따라서 이와 같은 이유들로 하여금, Scale Up을 적용하는 것보다는 Scale Out을 적용하는 것이 더 좋다고 판단하였고

 

서버 확장이 있을 때 Scale Out 할 것을 염두에 두고 프로젝트를 진행하였습니다.

 

 

세션 정합성 이슈

병렬 처리 환경에서 발생하는 동기화 이슈

 

 

위에서 한 얘기만 살펴보면 Scale Out에는 장점밖에 없는 것 같습니다. 하지만 늘 병렬 처리 환경에서는 동기화 이슈가 따라오기 마련이죠.

 

주로 발생하는 이슈 중 하나가, 여러 서버에서 사용자 세션을 나눠 갖기 때문에 발생하는 세션 정합성 문제입니다.

 

사용자가 로그인을 통해 세션을 얻었다면, 당연히 이후의 프로세스는 로그인이 되어있는 상태이기를 기대할 것입니다.

 

로그인을 했는데 또 로그인을 하라고 하는 서비스가 있다면 별로 사용하고 싶지 않겠죠?

 

그런데, 이러한 상황이 멀티 서버 환경에서는 발생할 수 있습니다.

 

세션 정합성 이슈로 인한 로그인 문제 상황

 

그림처럼, 서버 1에 요청을 해서 기껏 세션 인증을 받아뒀더니, 이후의 요청을 서버 2로 전송해서 세션이 없다고

 

생각해 사용자에게 또 로그인 요구를 하게 되는 올바르지 않은 결과를 낳을 수 있습니다.

 

사용자의 서비스 경험에 직결되기 때문에 굉장히 큰 문제이고, 반드시 해결하고 가야 할 문제입니다.

 

 

이러한 문제에 대한 해결 방안으로, 세 가지 방식을 추려볼 수 있었습니다.

 

Sticky Session

먼저, 첫 번째 방식은 Sticky Session 방식입니다. 그런데 Sticky Session을 소개하기 전에 먼저 짚고 넘어가야 하는 부분이 있습니다.

 

컴퓨터 네트워크에서 OSI 7 Layer에 대해 한 번쯤 들어보셨을 거예요.

 

OSI 7 Layer

 

네트워크 통신을 하기 위해서는 네트워크가 요구하는 통신 프로토콜을 준수해야 합니다.

 

사용자가 애플리케이션을 통해 데이터를 전송하면, 그 데이터는 OSI 7 Layer의 각 계층을 내려가면서 네트워크 표준에 맞는 형태의 데이터로 모습을 바꿔가고, 최종적으로 Physical Layer에서의 작업이 끝나면 네트워크 회선을 따라 데이터의 도착지를 향해갑니다.

 

한 마디로, 데이터를 네트워크 상에서 교환 가능한 형태로 만들어 주는 일련의 공정 작업을 OSI 7 Layer의 각 계층에서 분담해서 하고 있는 거죠.

 

그러한 공정 작업 중, 4번째 계층인 Transport Layer에서는 IP, Port 정보를 패킷에 입혀, 패킷이 한 지점에서 출발해서

 

네트워크의 다양한 경로를 지나 최종적인 목적지까지 도착하게 하는 역할을 수행합니다.

 

또한, 네트워크 상에 존재하는 네트워크 구성 기기(Switch, Router 등)들이 패킷의 IP/Port 정보를 이용해 네트워크 상황을 분석하고, 트래픽 분산 기능을 수행하기도 하죠.

 

스위치는 네트워크의 특정 노드를 연결하는 역할을 수행하는데, 이 연결을 수행할 때 있어 수신된 패킷이 가진 정보를

 

분석할 수 있는 능력을 어디까지 가졌느냐에 따라 L2, L3, L4, L7 등으로 분류됩니다. L 뒤에 붙은 숫자는, OSI 7 Layer의

 

계층 번호를 의미합니다. 예를 들어 L4 스위치는 Transport Layer에서 분석할 수 있는 요소인 IP, Port 정보 및 하위

 

Layer(Session, DataLink 등)의 정보들을 분석하여 스위칭할 수 있다는 의미가 됩니다.

 

L4 Switch가 제공하는 기능 중에는, 특정 timeout이 만료되기 전까지 계속해서 동일한 서버에 요청을 전송하는 방식인

 

Sticky라는 옵션이 존재합니다. Sticky 옵션을 켜면 사용자가 세션을 특정 서버에서 인증받았을 때, 그다음 요청도

 

계속해서 같은 서버로 전송하게 됩니다.

 

이러한 방식을 활용하면 위에서 얘기했던 여러 서버에서 세션이 동기화되지 않는 문제가 해결되겠죠?

 

세션을 가진 서버로만 요청을 전송할 테니까요.

 

Sticky Session 흐름도

 

Sticky Session의 단점

 

 

하지만, Sticky Session에는 특정 서버에 부하가 집중될 수 있는 치명적인 단점이 존재합니다.

 

부하를 분산해서 처리하려고 서버를 Scale out 했더니 특정 서버에만 요청이 집중적으로 전달된다면, Scale out이 주는 이점은 사라지고 괜히 비용만 더 쓴 셈이 되는 거죠.

 

그럼, 왜 Sticky Session 방식을 이용하면 특정 서버에 부하가 집중적으로 몰릴 가능성이 있는 걸까요?

 

이유는 L4 switch가 패킷 데이터를 분석할 수 있는 정보의 수준이 Transport Layer에서 제공하는 IP 까지라는 것에 있습니다.

 

다시 말해, L4 switch는 패킷을 열어서 어떤 타입의 메시지인지, 메시지의 내용은 무엇인지 등을 분석할 수가 없습니다.

 

그런 정보는 OSI model의 Application Layer에서 제공하기 때문에, 애초에 L4 Switch가 접근조차 불가능한 정보인 것이죠.

 

그래서 L4 Switch는 IP 정보만을 기반으로 하여 사용자 요청에 대한 로드 밸런싱을 수행합니다.

 

이것이 문제가 되는 순간은, 공인 IP라는 개념이 등장하면서부터입니다.

 

보통 네트워크에 연결할 때, 네트워크 회선을 직접적으로 컴퓨터에 연결하기보다는 공유기 같은 장치를 거쳐 연결하는

 

경우가 대부분이죠. 와이파이와 같은 무선랜을 사용할 때에도 당연히 공유기를 통해 연결합니다.

 

그런데 공유기를 거쳐 네트워크에 전송된 패킷에 기록된 IP는, 우리가 사용하는 PC의 IP(사설 IP)가 아닌 공유기의 IP(공인 IP)가 기록되어 있습니다.

 

즉, L4 Switch가 수신하는 데이터에 적혀있는 IP는 우리가 사용하고 있는 공유기의 IP가 되는 거죠.

 

그럼 L4 Switch에서는 IP 정보만 확인할 수 있기 때문에, 동일한 공유기에 연결된 PC를 모두 동일한 호스트로 인식하고,

 

Sticky Session이 적용된 경우에 공유기에 연결된 모든 호스트의 요청을 하나의 서버로 보내버리게 됩니다.

 

우리가 집에서 사용하는 가정용 공유기 정도는 서버에 큰 부하를 줄 만큼의 요청을 발생시키진 못 하겠지만,

 

대학교나 회사와 같은 전용 회선을 사용하는 수준의 대규모 단체의 요청이라면 얘기가 달라지겠죠.

 

많으면 수만 명까지도 사용할 수 있는데, 그 사용자들의 요청이 모두 하나의 서버로 집중될 테니까요.

 

그래서, Sticky Session을 사용하는 경우에 특정 서버에 부하가 집중되는 문제가 발생할 수 있다고 판단되어 다른

 

방법을 찾아야 했습니다.

 

Session Clustering

Sticky Session이 가진 단점을 극복하고자, Session Clustering이라는 방식의 도입을 고려해보았습니다.

 

Session Clustering이란, 각 서버가 자신이 가진 세션을 다른 서버에서도 가지도록 동기화하는 방식을 말합니다.

 

Session Clustering

 

그림처럼 서버끼리 자신의 세션을 공유해준다면, 사용자가 세션 인증을 받았던 서버가 어떤 서버였는지에 관계없이

 

모든 서버가 사용자 세션에 대한 정보를 가지고 있기 때문에 세션이 없는 서버에 대한 서비스 요청으로 걱정할 일도 없고, Sticky Session과 같은 방식을 사용해 인위적으로 요청 경로를 조작해서 특정 서버에만 부하가 몰릴 일도 없습니다.

 

또한, 각 서버가 각자의 자원(메모리, ..)에 직접 세션을 갖고 있기 때문에 중간에 네트워크 I/O와 같은 부가적인 작업을 통해 세션을 가져올 필요도 없어 더 빠르고 효율적인 세션 활용이 가능합니다.

 

그럼, 세션 클러스터링을 적용하면 항상 최적의 성능을 발휘하는 서버 구성이 가능한 걸까요?

 

특정 경우에는 세션 클러스터링을 사용하는 것이 효율적이었지만, 또 그렇지 않은 경우도 존재한다는 것을 알 수 있었습니다.

 

Scale-out과 세션 클러스터링 비용의 상관관계

 

 

Apache Tomcat 문서를 조금 읽어보면, 다음과 같은 설명을 볼 수 있습니다.

Using the above configuration will enable all-to-all session replication using the DeltaManager to replicate session deltas. By all-to-all, we mean that every session gets replicated to all the other nodes in the cluster. This works great for smaller clusters, but we don't recommend it for larger clusters — more than 4 nodes or so. 

 

문서에 의하면, 세션 클러스터링을 4개보다 더 많은 노드를 가지는 클러스터에 적용하는 것은 권장하지 않는다고 합니다.

 

이유가 직접적으로 나와있지는 않지만, 세션 클러스터링을 수행하는 방식으로부터 왜 5개 이상의 노드를 가지는 클러스터에서는 세션 클러스터링을 권장하지 않는지에 대해 추론해볼 수 있었습니다.

 

위의 문서 앞부분을 보면, 세션 클러스터링이 동작하는 방식은, "클러스터에 존재하는 모든 노드들에게 세션을 복제"하는 방식이라고 합니다.

 

서버가 자신이 가지고 있는 세션을 다른 서버에도 동일하게 갖도록 하기 위해 다른 모든 서버와 1:1 통신을 하면서 세션을 복제하는 것이죠.

 

서버의 수가 적으면 적은 통신 횟수로도 서버 간 세션 공유가 가능하고, 공유된 세션을 메모리 등 각자의 컴퓨팅 자원에

저장함으로써 세션에 접근하는 비용을 줄일 수도 있지만,

 

서버의 개수가 늘어날수록 통신의 횟수가 서버 수의 제곱에 비례하여 기하급수적으로 증가할 것이고, 이는 서버가 자체적으로 세션을 소유함으로 인해 얻는 이득보다 더 큰 비용으로 환산될 것임을 예측할 수 있습니다.

 

그래서, 세션 클러스터링 또한 대규모 트래픽이 발생하는 IT 서비스에 적용하기 적합하지 않은 구조라고 판단하였습니다.

 

이외에도 세션의 동기화에 걸리는 시간차로 인해 세션 불일치가 발생할 수 있는 점, 동일한 세션의 복제본을 많은 서버에서 공유하기 때문에 효율적인 메모리 관리가 되지 않는 점 등의 단점들이 많다는 점 또한 세션 클러스터링을 적용하기

어려웠던 이유로 작용했습니다.

 

In-memory Database

마지막으로 찾아본 방식은 별도의 데이터베이스에 세션을 저장하는 방식이었습니다.

 

물론, 디스크 기반의 데이터베이스를 사용하면 I/O 작업에서 병목이 있을 수 있기 때문에, 그보다 더 나은 성능을 보여주는 메모리 기반의 in-memory DB의 도입을 고려하였습니다.

 

각 서버가 세션을 소유하는 것이 아니라, in-memory DB에 모두 저장하고 필요할 때 DB에서 조회하는 형태로 세션을 사용한다면 더 이상 세션의 동기화는 문제가 되지 않겠죠. 애초에 하나의 원본 세션만 존재하고, 나머지는 이를 참조하는 형태이니까요.

 

in-memory DB 기반 세션 인증

 

또한 세션 클러스터링처럼 서버의 개수가 많아진다 하더라도 서버 간 세션을 공유할 필요가 없기 때문에, 서버의 규모가 클 때에도 성능 저하로 걱정할 필요가 없습니다. 물론, 네트워크 I/O를 통해 세션을 들고 오는 작업에 대한 비용이 발생하겠지만, 서버의 개수가 증가할수록 세션 클러스터링이나 Sticky Session에서 발생하는 성능 저하에 비교하면 훨씬 효율적이죠.

 

따라서, 여러 가지 상황을 고려했을 때 가장 효율적인 성능을 보여줄 수 있는 것은 in-memory DB라고 생각하여 해당 방식을 도입하였습니다.

 

 

In-memory DB, 어떤 것을 사용하면 좋을까요?

 

AWS에서는 대표적으로 Redis와 Memcached 두 가지 In-memory DB를 소개하고 있습니다.

 

Redis vs Memcached

 

 

현재 상황에 더 적합한 기술을 선택하기 위해, 두 가지를 여러 관점에서 분석하고 어떠한 장단점이 있는지 정리해보았습니다.

 

Redis

 

Redis는 다양한 자료구조를 지원하고, in-memory DB의 사용 편의성을 향상하는 다양한 서비스를 제공합니다.

 

또한, 서버에 장애가 발생해도 빠르게 복구할 수 있게 하는 기능들을 지원하죠.

 

 

다양한 자료구조 지원

 

Redis는 저장 및 복구에 사용되는 데이터에 다양한 자료구조를 사용할 수 있습니다.

 

Redis의 Data Structures 페이지를 참고하면, String / Set / List / Hash 등 10가지 이상의 자료구조를 지원하고 있네요.

 

Redis 자료구조

 

데이터를 저장하려는데, 프로그램에서 정의된 데이터에 대한 자료구조를 지원하지 않는 시스템이라면 우리는 데이터를

 

동일한 의미를 가진 다른 데이터로 변환하는 복잡한 작업을 중간에 추가해주어야 합니다. 이러한 작업을 개발자가

 

일일이 구현한다면, 개발 편의성이 떨어지고 직접적인 구현 대상인 비즈니스 로직에 집중하기 어려운 결과를 낳겠죠.

 

따라서 다양한 자료구조를 지원한다는 것은, 개발자의 편의성을 향상해 더 빠르고 효율적인 작업을 가능하게 합니다.

 

Redis는 이러한 부분까지 고려하여 여러 가지 자료구조로 구성된 데이터를 저장하고, 조회할 수 있게 합니다.

 

 

데이터 백업 및 복구

 

Redis Persistence 문서를 참고하면, Redis는 RDB(Redis Database)AOF(Append Only File)라는 두 가지의

 

데이터 영구 저장 방식을 제공하여서 종료된 서버에 대한 런타임 데이터를 복구할 수 있게 합니다.

 

in-memory DB는 Persistency를 가지는 저장장치, 이를테면 HDD와 같은 장치에 데이터를 저장하는 것이 아니라

 

말 그대로 메모리에 데이터를 저장함으로써 데이터에 접근하는 시간을 단축해 성능을 향상하는 전략을 취합니다.

 

하지만, 메모리의 데이터는 휘발성 데이터이기 때문에 프로그램이 종료되면 프로그램의 모든 데이터는 사라집니다.

 

Redis 서버에도 장애가 일어나 종료되지 않으리란 보장은 없고, 그런 경우에는 저장된 세션 데이터가 모두 유실되겠죠.

 

그렇기 때문에 예외 상황에 대한 복구 준비도 되어 있어야 하는데, Redis는 위의 두 가지 방식을 통해 특정 시점에

 

데이터를 디스크에 저장하고, 저장된 데이터를 통해 다시 메모리에 데이터를 복구할 수 있게 합니다.

 

 

다양한 Eviction Algorithm

 

때로는 메모리의 데이터가 가득 차는 경우도 발생하기 마련입니다. 이러한 경우 메모리에 조금이라도 더 남아있어야 할

 

가치가 큰 데이터는 그대로 남겨두고 나머지 데이터는 Data Eviction을 해서 새로운 데이터가 들어올 자리를 마련합니다.

 

이때, Redis는 다양한 Eviction 결정 방식을 통해 Eviction 대상을 결정하는 반면, Memcached는 LRU 한 가지 방식만을

 

통해 Data Eviction을 수행합니다. Eviction 알고리즘이 다양하다면 우리가 사용하는 데이터의 성격에 맞추어 효율적인

 

Eviction 대상을 결정할 수 있겠죠. 이러한 부분에서도, Redis가 유리한 측면이 있다고 생각합니다.

 

 

Spring의 공식 지원

 

Spring docs에서는 공식적으로 Redis를 지원하고 있습니다. 개인적인 기준이 되겠지만, 프로젝트를 Spring Boot

 

기반으로 진행하고 있기 때문에, 기술 간의 호환성을 고려했을 때 프레임워크 수준에서 기술을 공식적으로 지원한다는

 

것은 꽤나 든든한 장점으로 다가왔습니다. 반면 Memcached는 Spring이 공식적으로 지원하는 기술이 아니기 때문에,

 

SSM(Simple Spring Memcached)이나 Memcache Provider와 같은 의존성을 따로 설치하여 사용해야 합니다.

 

Memcached

 

Memcached는 Redis처럼 화려한 자료구조를 지원하거나 복구 전략을 제공하지는 않지만, Memached만의 장점이 존재합니다.

 

 

Multi-Thread

 

Alibaba Cloud의 Redis vs Memcached 문서에서 다음과 같은 내용을 확인할 수 있었습니다.

Redis only uses single cores while Memcached utilizes multiple cores. So on average, Redis boasts a higher performance than Memcached in small data storage when measured in terms of cores. Memcached outperforms Redis for storing data of 100k or above. Although Redis has also made some optimizations for storing big data, it is still inferior to Memcached.

 

Redis는 Single core를 사용하는 반면, Memcached는 Multi cores를 활용하기 때문에 data storage의 크기가 작은 경우에는 Redis가 코어당 성능이 더 좋지만, data storage의 크기가 큰 경우에는 Multi-core를 활용하는 Memcached가 더 유리합니다.

 

 

효율적인 메모리 할당 방식

 

Memcached는 데이터를 저장할 때, 메모리 할당 방식으로 slab memory allocation 방식으로 데이터를 저장하는 반면,

 

Redis는 데이터 저장 시 매번 malloc/free를 통해 데이터를 저장합니다. 이러한 이유 때문에, Redis가 Memcached에 비해

 

상대적으로 메모리 파편화가 더 많이 발생합니다. 또한, Redis는 메모리 할당을 해주기 때문에 부하가 많아지는 경우에

 

느려질 가능성이 더 큽니다. 실사용에서도 Redis -> Memcached로 전환하는 경우에는 이러한 문제가 없었지만,

 

Memcached -> Redis로 전환하는 경우에 문제가 발생할 수 있다고 합니다.

 

 

Redis vs Memcached

 

여태까지의 의사 결정 과정 중 가장 어려운 부분이지 않을까 생각합니다. Sticky Session이나 Session Clustering처럼

 

크리티컬 한 단점이 있는 것이 아니고, 상황에 따라 Redis를 사용하던 환경에서 Memcached로 전환해야 될 가능성도,

 

반대로 Memcached를 사용하던 환경에서 Redis로 전환해야 될 가능성도 충분합니다. 그래서, 몇 가지 기준을 세우고

 

상황에 맞추어 어떤 in-memory DB를 사용해야 할지 결정하였습니다.

 

 

어떤 환경에 in-memory DB를 적용하려 하는가?

 

시스템의 버전, 테스트 환경, 데이터의 자료구조 등 정말 많은 요소에 의해 성능이 다르게 측정되겠지만,

 

일반적으로 Redis는 데이터 Read, Memcached는 데이터 Write에서 더 좋은 성능을 보인다고 합니다.

 

앞서 언급한 메모리 할당 방식으로 인해 이러한 차이가 발생하지 않았나 생각합니다.

 

현재 웹 서비스에서 세션 데이터를 관리할 용도로 in-memory DB를 사용하려 하는데, 웹 서비스에서 세션을

 

새로 생성하거나 수정하는 경우보다는, 기존에 저장된 세션을 읽어 세션 기반의 서비스를 요청하는 일이 훨씬 많겠죠? 

 

보통 웹 서비스에서는 로그인을 한 번만 하고 이후에는 로그인 정보를 기반으로 서비스를 사용하니까요.

 

즉, Redis나 Memcached를 도입하려는 곳이 데이터 읽기를 훨씬 많이 수행하는 곳이고, 그렇다면 읽기 성능이 더 뛰어난

 

Redis가 적합할 것이라고 판단했습니다.

 

 

장애 복구 능력

 

서버를 안정적으로 운영하는 것만큼 중요한 것이 서버 장애가 발생했을 때 그것을 신속히 복구하는 것이죠.

 

Redis는 앞서 말씀드린 RDB, AOF를 활용하여 데이터를 영구 저장하고 데이터가 유실되는 경우에 저장된 데이터를 다시

 

메모리에 로드하는 방식으로 복구할 수 있습니다. 하지만 Memcached는 이러한 기능을 지원하지 않기 때문에,

 

장애 복구 측면에서도 Redis의 손을 들어줄 수 있었습니다.

 

 

대규모 트래픽 환경에서의 응답속도

 

Memcached는 Multi-core, Redis는 Single-core 기반으로 작동합니다. 그래서, 데이터 저장 규모가 적을 때는 core 대비

 

성능이 Redis가 뛰어나고 저장 규모가 클 때는 core 대비 성능이 Memcached가 더 뛰어납니다.

 

대규모 트래픽이 발생하는 서비스를 바라보고 개발을 진행 중이고, 그렇기 때문에 해당 부분에서는 Memcached가 더

 

유리하다고 판단되었습니다.

 

 

개발 편의성 및 시스템 호환성

 

Redis는 String, Set, List, Hash 등 다양한 자료구조를 지원합니다. 이 말인즉슨, 프로그램상에서 사용하는 데이터를

 

그대로 Redis에 저장하고, 저장된 데이터를 다시 프로그램 위에 가져올 수 있다는 의미이죠. 이것은 개발 시에 가져가는

 

이점이 꽤나 크다고 생각합니다. 단순히 개발하기 편하다는 기준을 넘어, 자료구조상 비효율이 발생할 수 있는

 

문제를 어느 정도 방지할 수 있으니까요. List나 Hash의 형태로 정의된 데이터를 String으로 변환해서 저장한다고

 

생각하시면 그 비효율에 대해 이해하기 편할 것 같습니다.

 

또한, 기본적으로 Spring은 Redis를 공식적으로 지원하고 있습니다. 저는 이 말의 의미가, 단순히 Spring에서 편리하게

 

Redis를 사용할 수 있다는 것의 의미만 가지고 있는 것이 아니라 앞으로 Spring에서 Redis를 사용할 때에 있어 환경을

 

구성할 책임이 Spring Framework에 있다는 뜻으로 생각합니다. 본인들이 공식 지원한다고 얘기했으니, 당연히 Spring

 

버전업이 있을 때에도 자신들이 사용하는 디펜던시를 고려할 것이고, 그 고려 대상에 Redis도 포함되어 있을 테니까요.

 

반면, Memcached를 사용하기 위해서는 Simple Spring Memcached와 같은 서드파티 라이브러리를 사용해야 하며, 이는

 

Spring이 Memcached를 고려하지 않은 방식으로 프레임워크를 변경해서 문제가 생길지언정, 우리는 그것에 대해 딱히

 

무언가 요구할 권리는 없는 상황인 거죠. 프레임워크가 아닌 서드파티 라이브러리에 의존적입니다.

 

그래서 저는, 이러한 부분에서도 Redis를 사용하는 것이 유리하다고 생각했습니다.

 

 

의사 결정

 

위와 같은 판단들로 하여금, 프로젝트에는 Redis를 사용하는 것으로 결정하였습니다.

 

대규모 트래픽에 대해서 응답 속도가 조금 떨어지는 단점이 있겠지만, 신속한 장애 복구 / 다양한 자료구조 / 스프링의

 

공식 지원 등 그 단점을 커버할 수 있는 장점들이 더 많다고 생각했기 때문입니다. 이것이 정말로 유효한 의사

 

결정이었는지는 앞으로 성능 테스트 등을 거쳐보며 확인할 생각입니다.

 

 

마치며

 

이상으로, 대규모 시스템에서 세션 관리를 위한 여러 가지 방식을 살펴보고 현재의 시스템 사정에 맞는 것을 도입하는

 

과정을 포스팅하였습니다. No Silver Bullet이란 말이 있죠. 제가 위의 결정 과정을 통해 Redis를 선택했다고 할 지라도,

 

또 다른 환경에서는 Redis를 사용하는 것이 Over-Engineering일 수도 있고 성능 저하를 야기할 수도 있습니다.

 

그렇기 때문에 항상 우리가 개발하는 시스템에 대해 깊이 이해하고, 이것이 더 좋은 성능을 발휘하기 위한 방법을

 

고민하는 것이 개발자로서 올바른 자세가 아닐까 생각합니다.

 

앞으로 프로젝트를 진행하면서도 이러한 주제로 계속 고민하면서, 특정 성과를 발견하는 시점에 또 공유하도록 하겠습니다.

 

 

프로젝트 URL : https://github.com/f-lab-edu/sell-everything

 

GitHub - f-lab-edu/sell-everything: Spring Framework 기반 중고 거래 서비스 플랫폼

Spring Framework 기반 중고 거래 서비스 플랫폼. Contribute to f-lab-edu/sell-everything development by creating an account on GitHub.

github.com

 

반응형
Comments