서버 성능 개선을 위한 캐시 사용과, 분산 서버 환경에서 세션 인증 정보 유실 문제를 대응하며 Redis를 사용했습니다. Redis가 무엇인지, 어떤 구조이며 왜 사용했는지 간단하게는 설명했지만 조금 더 구조적으로 정리하진 않은 것 같아 새로운 글에 적당히 정리해보도록 하겠습니다.
1. 캐시의 필요성
사용자의 모든 리소스 요청에 대해 DB 쿼리를 날린다면 서버의 성능이 크게 저하됩니다. DB 연결은 IO bound task이기 때문에 웹 서버 안에서 끝날 수 있는 작업에 비해 오래 걸리고, DB 커넥션을 얻기 위해 웹서버 스레드가 대기 상태에 들어가 서버 자원을 온전히 사용하지 못하게 될 수도 있기 때문입니다.(이를 Blocking-IO라고 합니다)
따라서 어플리케이션을 구성할 때, DB 서버로 요청을 날리는 횟수가 적을 수록 좋습니다. 그런데 DB 자원 없이 필요한 리소스를 어떻게 사용자에게 전달할까요? 데이터를 빠르고 간편하게 꺼낼 수 있는 임시 저장소, 즉 캐시에 자주 사용되는 데이터를 보관해놓고 같은 요청에 대해 캐시 저장소에서 캐시 데이터를 불러오는 방법을 생각할 수 있겠습니다.
캐시 저장소에 보관하기 적절한 데이터라고 하면, 상식적으로 다음과 같은 경우를 생각할 수 있겠습니다.
- 기본적으로 많은 트래픽이 몰릴 것으로 예상된다.
- 데이터의 신선도가 크게 중요치 않다. 실시간성이 어느정도 떨어져도(최신 데이터가 아니더라도) 문제 없는 데이터이다.
- 자주 수정되지 않는 데이터이다.
예컨데 페이지 메인에 그 날의 신문 기사를 띄워주는 어플리케이션이 있다고 생각해봅시다. 신문 기사 전문이 DB에 저장되어 있다고 해서, 사용자가 메인 페이지를 요청할 때마다 DB에서 `SELECT * FROM ARTICLE A WHERE A.date=?`쿼리를 날리는게 맞을까요? 당연히 아닙니다. 날짜가 바뀔 때, 캐시 저장소에 오늘의 기사를 옮겨놓고 마치 정적 컨텐츠를 제공하듯 저장소 데이터를 제공하면 될 것입니다.
종합하면 캐시란, 자주 사용되는 데이터를 빠른 접근이 가능한 공간에 임시로 저장하여 사용하는 기법이라고 정리할 수 있겠습니다.
2. 레디스
이러한 캐시 구현을 위해 많이 사용되는 솔루션이 Redis입니다. Redis는 NoSQL 데이터 저장소로, 특징은 다음과 같습니다.
- 인메모리 DB : 실제 하드웨어의 디스크에 접근하는 일반 DB보다 훨씬 빠른 데이터 접근 속도를 자랑한다
- key-value 데이터 구조
- NoSQL이 가지는 일반적인 특성으로, DB 샤딩이 용이하다
메인 메모리에 데이터를 담아두고 매우 빠르게 꺼내오는 만큼 정말 빠른 NoSQL DB 솔루션이라고 생각하면 되겠습니다.
직접 하드디스크를 사용하지 않는 만큼 많은 데이터를 영속적으로 보관하는데는 여타 RDB나 NoSQL에 비해 , 대용량 데이터의 저장이 필요 없으면서도 빠른 접근이 필요하다면 Redis만한 솔루션이 없습니다. 레디스는 다음과 같은 상황에서 사용합니다.
- 캐시 : 앞서 설명했던 상황입니다. DB 접근이나 WAS의 연산 없이 미리 계산된 컨텐츠를 return해야하는 상황에서 사용합니다.
- 분산락 : 해당 게시글을 참고해주세요. 분산 서버 환경에서 동시성 컨트롤을 위해 Redisson을 구현체로 사용하는 분산락이 사용됩니다.
- 세션 서버 : 해당 게시글을 참고해주세요. 분산 서버 환경에서 여러 서버들의 공통 세션 서버로 이용됩니다.
- Redis Pub/Sub MQ : Redis 자체를 MQ로 활용할 수도 있습니다. 이 경우 한 번에 실어나를 수 있는 메세지량은 떨어질지라도 kafka, rabbitMQ 등의 MQ 솔루션보다 레이턴시 면에서 좋은 성능을 보입니다.
3. 레디스를 사용한 이유
캐시 서버로 사용할 수 있는 솔루션은 사실 레디스 이외에도 여러 가지가 있습니다. Memchached, EHCache 등이 그 예시이죠. 저는 프로젝트에 레디스를 사용했습니다. 그 이유는 다음과 같습니다.
- Redis 인스턴스를 따로 띄워야하는 단점이 있지만, 한 번 인스턴스를 만들어 놓으면 앞서 설명했던 캐시, 글로벌 세션, MQ 등 다방면으로 활용하기 좋음
- WAS마다 캐시를 두는 ehcache는 각 서버마다 캐시 데이터의 불일치 가능성이 있음. 레시피 공유 프로젝트의 경우, 추천 레시피의 일치 여부가 그렇게까지 치명적인 부분은 아니지만 엄연한 순위 데이터이므로 모든 WAS가 공유하는 하나의 캐시 데이터가 필요할 것이라고 판단함
- spring boot 측에서 공식적으로 제시하는 Redis 구현체(Lettuce)가 존재하며, 레디스의 경우 value로 다양한 자료구조를 지원하기 때문에 사용이 편리함
요컨데 레디스를 사용하지 않을 이유가 없었습니다(..) 글로벌로 동작, 낮은 레이턴시, 활용의 편리성..
또 다른 각도에서 얘기를 해보자면, 레디스의 인메모리 데이터를 처리하는 메인 스레드는 1개라서 write back등의 캐시 쓰기 전략을 활용했을 때 race condition에 대해 걱정할 필요가 없습니다.
현재는 레디스 서버 하나를 두고 캐시 서버, 세션 서버로 사용하고 있는데 레디스 서버 하나에 이상이 생기면 전체 서비스에 문제가 생길 수도 있겠네요.. 하지만 앞서 언급했듯 NoSQL인 레디스의 경우 replication, clustering같은 복제/다중서버 구성이 가능하고 master-slave 구조로 DB 서버를 설정 가능하며 자체적인 failover 역시 가능합니다.