Clarifications about Redis and Memcached

Salvatore Sanfilippo

Redis와 Memcached에 대한 해명

원문은 Salvatore Sanfilippo님이 에 게재했습니다. 이 블로그 구독하기

나를 아는 사람이라면 내가 경쟁 제품을 나쁘게 보는 사람이 아니라는 걸 알 것이다. 오히려 나는 사용자에게 선택지가 많은 것을 좋아해서, Redis를 다른 기술과 비교하는 일은 거의 하지 않는다.

하지만 올바른 솔루션을 고르려면 사용자가 정확히 알고 있어야 한다는 것도 사실이다.

이 글은 Mike Perham이 쓴 블로그 포스트를 읽고 쓰게 되었다. Mike는 Sidekiq이라는 인기 라이브러리의作者로, 백엔드로 Redis를 사용한다는 점에서 알고 있는 사람도 있을 것이다. 그래서 나는 Mike를 결코 “Redis에 반대하는” 사람이라고 생각하지 않는다. 그럼에도 그는 URL http://www.mikeperham.com/2015/09/24/storing-data-with-redis/에서 확인할 수 있는 블로그 포스트에서 캐싱에 관해 “아마도 [Redis 대신] Memcached를 쓰는 편이 좋을 것이다”라고 말한다. 즉 Mike는 정말로 Redis가 캐싱에 적합하지 않다고 믿고 있으며, 다음과 같은 근거로 자신의 주장을 펼친다:

  1. Memcached는 캐싱을 위해 설계되었다.
  2. 디스크 I/O를 전혀 수행하지 않는다.
  3. 멀티 스레드로 동작하며 멀티 코어를 활용해 수십만 건의 요청을 처리할 수 있다.

위 주장들을 하나씩 짚어본 뒤, 위 문장들에는 담기지 않았지만 내 생각에는 대부분의 캐싱 사용자와 사용 사례에 더 중요한 정보들을 추가로 다루겠다.

Memcached는 캐싱을 위해 설계되었다: 이건 논거라고 할 수 없으니 넘어가겠다. 나도 “Redis는 캐싱을 위해 설계되었다”라고 말할 수 있다. 그런 점에서는 둘은 완전히 같다. 다음으로 넘어가자.

디스크 I/O를 전혀 수행하지 않는다: Redis에서도 원한다면 디스크 I/O를 완전히 비활성화해 순수하게 인메모리로 동작하게 할 수 있다. 다만 정말 필요하다면 재부팅할 때만 데이터베이스를 저장하는 식으로, 예를 들어 “SHUTDOWN SAVE”로 영속성을 활용할 수도 있다. 요점은 Redis의 영속성은 전혀 사용하지 않더라도 그 자체로 부가가치가 된다는 것이다.

멀티 스레드로 동작한다: 이는 사실이다. 내 목표 중 하나도 Redis의 I/O를 스레드로 처리하는 것이다(memcached도 사실상 데이터 접근 자체는 스레드로 처리하지 않는다). 하지만 Redis는 특히 파이프라이닝을 사용하면 스레드당 초당 처리할 수 있는 요청 수가 매우 인상적이다(파이프라이닝을 집중적으로 사용하면 50만 건이 흔한 수치이며, 파이프라이닝 없이는 초당 약 100,000건 정도다). 각 Redis 인스턴스가 동일하고 모두 마스터로 동작하며 디스크 연산이 비활성화되고 샤딩이 “memcached 샤딩 모델”처럼 클라이언트에 위임되는 일반적인 캐싱 시나리오에서는, 시스템당 여러 Redis 프로세스를 띄우는 것도 나쁘지 않다. 이렇게 하면 shared-nothing 멀티 스레드 구성이 되므로 중요한 것은 단일 스레드당 처리할 수 있는 연산량이다. 마지막으로 확인했을 때 Redis는 스레드당 성능이 적어도 memcached만큼은 빨랐다. 구현은 시간이 지나며 바뀌므로 오늘날 어느 쪽이 우세할지는 달라질 수 있지만, 둘 다 주어진 자원을 최대한 활용하려는 만큼 성능은 비슷할 것이라고 본다. Memcached의 멀티 스레딩은 사용과 운영을 더 단순하게 만든다는 점에서 여전히 장점이지만, 결정적인 요소는 아니라고 생각한다.

더 짚을 점이 있다. Mike는 초당 연산 횟수만 이야기하면서 연산의 *질*에 대해서는 언급하지 않는다. Redis나 Memcached 같은 시스템에서는 실제로 인메모리 자료구조를 건드리는 비용보다 명령어 디스패치와 I/O 비용이 훨씬 더 지배적이다. 그래서 기본적으로 Redis에서는 단순한 GET이나 SET을 실행하는 비용이나 ZRANK 같은 복잡한 연산을 실행하는 비용이나 거의 비슷하다. 하지만 복잡한 연산 하나로 애플리케이션 관점에서는 훨씬 더 많은 일을 해낼 수 있다. 예를 들어 캐시된 값 다섯 개를 일일이 가져오는 대신 작은 Lua 스크립트 하나만 보내면 될 수도 있다. 따라서 두 시스템의 실제 “확장성”은 여러 차원을 가지고 있으며, 무엇을 달성할 수 있는가도 그중 하나다.

Mike가 우려한 지점 중 내가 보기에 유효한 것은 멀티 스레딩뿐이며, 그것도 Redis를 memcached 대체라는 특수한 경우로만 본다면 여러 프로세스를 실행하거나, 혹은 그냥 하나만 실행하는 것으로도 해결할 수 있다. memcached와 유사한 연산만으로는 단일 스레드를 포화시키기가 매우 매우 어렵기 때문이다.

진짜 차이점

이제 두 시스템 간의 *진짜* 차이점에 대해 이야기할 차례다.

메모리 효율성

이 부분에서는 Memcached가 예전에 Redis보다 나았다. 단순히 문자열 대 문자열 딕셔너리를 표현하도록 설계된 시스템에서는 메모리를 더 효율적으로 쓰기가 더 쉽다. 이 차이는 극적이지 않고, 내가 확인하지 않은 지 5년 정도 되었지만 예전에는 눈에 띄는 수준이었다.

하지만 장시간 실행되는 프로세스의 메모리 효율성을 고려하면 이야기는 조금 달라진다. 다음 섹션을 읽어보라.

다만 메모리 효율성을 제대로 평가하려면 Redis에서 작게 집계된 값들이 특별히 인코딩되어 매우 메모리 효율적으로 저장된다는 점도 고려해야 한다. 예를 들어 작은 정수들로 이루어진 집합은 내부적으로 8, 16, 32 또는 64비트 정수 배열로 표현되며, 정렬되어 있기 때문에 특정 값의 존재 여부를 확인할 때 이진 탐색을 이용해 로그 시간에 접근할 수 있다.

JSON에 의존하는 대신 해시를 사용해 객체를 표현할 때도 마찬가지다. 따라서 실제 메모리 효율성은 구체적인 사용 사례를 두고 평가해야 한다.

Redis LRU vs Slab allocator

Memcached는 메모리 활용 관점에서 완벽하지 않다. 시간이 지나며 캐시된 값의 크기가 크게 바뀌는 애플리케이션의 경우 심각한 단편화를 겪을 가능성이 높고, 유일한 해결책은 재시작뿐이다. 이 관점에서 Redis는 훨씬 더 결정적이다.

게다가 Redis LRU는 최근 크게 개선되어 이제 실제 LRU에 매우 가깝게 동작한다. 자세한 정보는 여기에서 확인할 수 있다: http://redis.io/topics/lru-cache. 내가 이해한 바로는 memcached의 LRU는 여전히 slab 할당자에 따라 만료가 이루어지므로 때로는 실제 LRU와는 거리가 먼 동작을 보일 수 있지만, 이에 대해서는 전문가들의 의견을 듣고 싶다. Redis LRU를 테스트해보고 싶다면 최근 버전의 Redis에서 제공하는 redis-cli LRU 테스트 모드를 사용하면 된다.

스마트 캐싱

Redis를 캐싱 용도로 쓰면서 memcached처럼만 사용한다면 정말 중요한 무언가를 놓치고 있는 것이다. 내 생각에 이것이 Mike의 블로그 포스트에서 가장 큰 오해다. 사람들이 점점 더 Redis로 갈아타는 이유는 캐시된 데이터를 더 유용한 방식으로 표현할 수 있다는 걸 발견했기 때문이다. 무언가의 최신 N개 항목을 유지하고 싶은가? capped list를 사용하면 된다. 캐시된 인기 지수를 관리하고 싶은가? sorted set을 사용하면 된다. 그 외에도 방법은 많다.

영속성과 복제

이런 기능이 필요하다면 매우 중요한 자산이 된다. 예를 들어 이 모델을 이용하면 막대한 읽기 부하를 확장하는 것이 매우 간단해진다. 영속성을 이용한 재시작이나 시간 경과에 따른 캐시 스냅샷 생성 등도 마찬가지다. 물론 두 기능이 전혀 중요하지 않은 사용 사례가 있는 것도 당연하다. 내가 여기서 말하고 싶은 것은 “순수 캐싱” 사용 사례 중에서도 영속성과 복제가 중요한 경우가 있다는 것이다.

관찰 가능성

Redis는 관찰 가능성이 매우 매우 뛰어나다. 수많은 내부 지표에 대한 상세한 리포트를 제공하고, SCAN으로 데이터셋을 훑어보거나 객체의 만료를 관찰할 수 있다. LRU 알고리즘을 튜닝할 수도 있고, 클라이언트에 이름을 붙여 CLIENT LIST에서 확인할 수도 있다. “MONITOR”를 이용해 애플리케이션을 디버깅하는 등 여러 고급 기능도 제공한다. 나는 이것이 장점이라고 생각한다.

Lua 스크립팅

Lua 스크립팅은 많은 캐싱 사용 사례에서 매우 큰 도움이 된다고 생각한다. 예를 들어 캐시된 JSON blob이 있을 때 Lua 명령을 이용하면 전체를 전송하는 대신 특정 필드 하나만 추출해 클라이언트에 반환할 수 있다(개념적으로는 Redis 해시를 이용해 객체를 직접 표현해도 같은 효과를 낼 수 있다).

결론

Memcached는 훌륭한 소프트웨어다. 나는 소스 코드를 여러 번 읽어봤고, 우리 업계에 혁명을 가져온 작품이며, 여러분에게 Redis보다 더 나은 선택인지 직접 확인해 볼 필요가 있다. 하지만 사물은 있는 그대로 평가되어야 하며, 결국 나는 Mike의 글과 지난 수년간의 비슷한 글들을 읽으면서 다소 짜증이 났다. 그래서 내 관점을 보여주기로 했다. 사실관계가 틀린 부분이 있다면 알려 달라. “EDIT” 섹션을 추가해 블로그 포스트를 업데이트하겠다.

이 글은 muse-spark-1.2-contributor 모델을 사용해 번역했습니다.

댓글