왜 Redis와 다른 DB를 비교하는 벤치마크를 제공하지 않는가
원문은 Salvatore Sanfilippo님이 에 게재했습니다. 이 블로그 구독하기
Redis의 속도는 신규 사용자에게 어필할 수 있는 장점 중 하나이므로, 비교 ‘광고’의 유행을 따른다면 Redis.io에 몇 가지 비교 자료를 올려두는 것이 논리적으로 맞아 보인다. 하지만 여기에는 두 가지 문제가 있다. 하나는 목표의 문제다. 나는 개발자들에게 Redis 채택을 설득하고 싶지 않다. 우리는 그저 쓸 만한 제품을 제공하기 위해 최선을 다할 뿐이고, 사람들이 이를 통해 일을 잘 처리할 수 있다면 그것으로 만족한다. 나의 마케팅적 바람은 거기서 끝난다. 게다가 더 근본적인 문제가 있다. 서로 다른 시스템을 공정하게 비교한다는 것은 거의 대부분 불가능하다는 점이다.
두 데이터베이스를 비교해 공정한 수치를 얻으려면 두 시스템이 공유해야 할 것이 *매우* 많다. 데이터 모델, 정확한 내구성 보장, 데이터 복제의 안전성, 파티션 상황에서의 가용성 등등. 종종 어떤 시스템은 속도를 희생해 겉으로 잘 드러나지는 않지만 그럼에도 매우 중요한 특성들을 제공하느라 다른 시스템보다 낮은 점수를 받기도 한다. 게다가 서로 다른 데이터베이스 시스템이 정확히 동일한 프로토콜을 사용하지 않는 한 테스트 스위트 자체도 복잡한 문제다. 클라이언트 라이브러리의 차이만으로도 큰 성능 차이가 발생할 수 있다.
하지만 이에 동의하지 않고, 그럼에도 서로 다른 데이터베이스 시스템의 속도를 비교하는 것이 좋은 아이디어라고 믿는 사람들도 있다. 예를 들어 어제 Redis와 AerospikeDB를 비교한 벤치마크가 이곳에 공개됐다: http://lynnlangit.com/2015/01/28/lessons-learned-benchmarking-nosql-on-the-aws-cloud-aerospikedb-and-redis/.
나는 이 벤치마크를 예로 들어 벤치마크가 얼마나 오해를 불러일으키는 존재인지 보여주고자 한다. 해당 벤치마크에서는 어째서인지 244GB라는 어마어마한 메모리를 탑재한 초대형 EC2 인스턴스가 사용됐다(!). R3.8xlarge 인스턴스다. 내 테스트에서는 좀 더 현실적인 m3.medium 인스턴스를 사용하겠다.
그런 괴물 같은 인스턴스를 사용했을 때 Redis는 단일 노드 기준으로 초당 12만 8천 건의 연산을 처리했다. 내 EC2 인스턴스는 훨씬 사양이 낮다. 다른 EC2 인스턴스에서 redis-benchmark로 파이플라이닝을 사용하지 않고 동일한 100바이트 데이터 크기로 테스트했을 때 나는 초당 3만 2천 건을 얻었다. 그러니 단일 프로세스 기준으로 내 인스턴스는 대략 4배 느리다고 할 수 있다.
Redis INFO 명령어로 이 벤치마크 동안 시스템이 CPU를 어떻게 사용하고 있는지 살펴보자:
# CPU
used_cpu_sys:181.78
used_cpu_user:205.05
used_cpu_sys_children:0.12
used_cpu_user_children:0.87
127.0.0.1:6379> info cpu… 10초간의 테스트 후 …
# CPU
used_cpu_sys:184.52
used_cpu_user:206.42
used_cpu_sys_children:0.12
used_cpu_user_children:0.87Redis는 약 3초를 시스템 시간으로, 약 1.5초만을 유저 공간에서 사용했다. 여기서 일어나는 일은 각 요청마다 작업의 대부분이 read()와 write() 호출을 수행하는 데 쓰인다는 것이다. 또 각 클라이언트마다 하나의 질의에 하나의 응답을 받는 워크로드이기 때문에, 각 클라이언트의 각 요청마다 전체 RTT를 그대로 부담한다.
이제 파이플라이닝을 사용하면 어떻게 되는지 살펴보자. 파이플라이닝은 Redis 사용자들에게 매우 잘 알려져 있고 널리 활용되는 기능이다. 서버 활용도를 극대화할 수 있는 유일한 방법이기도 하고, 애플리케이션 안에서도 한 번에 여러 연산을 수행할 수 있는 지점이 보통 여러 곳 존재하기 때문이다.
파이프라인을 32개 연산으로 설정하자 수치가 극적으로 달라졌다. 나의 작은 인스턴스는 단일 코어만으로 초당 25만 건을 처리할 수 있었는데, 이는 앞서 언급한 벤치마크에서 (각각 더 빠른) 32개 코어를 사용해 얻은 *최고* 결과의 25%에 해당한다.
CPU 시간을 살펴보자:
# CPU
used_cpu_sys:189.16
used_cpu_user:216.46
used_cpu_sys_children:0.12
used_cpu_user_children:0.87
127.0.0.1:6379> info cpu… 10초간의 테스트 후 …
# CPU
used_cpu_sys:190.60
used_cpu_user:224.92
used_cpu_sys_children:0.12
used_cpu_user_children:0.87이번에는 CPU를 실제로 데이터베이스 엔진이 쿼리를 처리하는 데 사용하고 있다. 컨텍스트 스위칭 때문에 시간을 낭비하고 있는 것이 아니다. 약 1.5초는 시스템 시간으로, 8.46초는 Redis 프로세스 자체에서 사용됐다.
파이프라인 숫자를 더 낮게 설정하면 그 중간 어딘가의 결과가 나온다. 파이프라인 4일 때는 초당 10만 건(벤치마크에 사용된 더 큰 인스턴스라면 약 초당 40만 건에 해당한다), 파이프라인 8일 때는 초당 18만 건, 이런 식이다.
그러므로 이런 식으로 Redis와 AerospikeDB를 벤치마킹했을 때 놀랍도록 비슷한 결과가 나오는 것은 우연이 아니다. 어느 정도는 데이터베이스를 테스트하는 것이 아니라 네트워크 스택과 커널을 테스트하고 있는 셈이다. DB가 별다른 큰 낭비 없이 read와 write 시스템 콜 한 번씩으로 쿼리를 처리할 수 있다면 결과는 이렇게 나오게 된다. 그리고 여기서는 메모리에 들어가는 데이터를 다루고 있기 때문에 실제 쿼리를 처리하는 시간 자체는 짧다(참고로 Redis에서 100k 크기의 키 10M개는 해당 인스턴스들에 할당된 메모리의 일부만을 사용한다).
하지만 이야기는 여기서 끝나지 않는다. 우리가 수행할 수 있는 연산들은 어떨까? GET/SET만으로 Redis를 테스트하는 것은 비가 올 때 페라리의 백미러 닦임 성능이 얼마나 좋은지를 테스트하는 것과 같다.
Redis 아키텍처의 핵심 중 하나는 매우 다른 연산들도 비용이 비슷하다는 점이다. 그렇다면 종료된 게임의 점수를 게시해 리더보드를 만드는 대규모 페이스북 게임 사례는 어떨까?
동일한 단일 프로세스가 ZADD myscores <random-int> <random-int> 쿼리를 수행할 때는 초당 11만 건을 처리할 수 있다.
좀 더 요구해보자. HyperLogLog로 카디널리티를 추정하면서 동시에 두 개의 redis-benchmark 프로세스로 새로운 요소를 추가하고 현재 추정치를 읽어온다면 어떨까? Set 크기는 이번에도 1천만 개다. 그래서 이 테스트에서는 해당 set의 무작위 요소들로 PFADD를 실행하는 벤치마크 하나와, 같은 HyperLogLog에 대해 동시에 PFCOUNT를 수행하는 벤치마크 하나를 실행했다. 두 프로세스 모두 동시에 초당 25만 건을 기록해, 단일 Redis 프로세스로 초당 총 50만 건을 처리한 셈이다.
Redis에서 복잡한 연산을 수행하는 것은 파이플라이닝과 비슷하다. 각 read/write마다 *더 많은* 일을 해야 한다. 그렇지 않으면 성능은 I/O에 의해 좌우된다.
자, 몇 가지 유용한 정리를 해보자. 1) GET/SET 벤치마크는 서로 다른 데이터베이스 시스템을 비교하는 좋은 방법이 아니다. 2) 더 나은 성능 비교는 사용 사례별로 하는 것이다. 특정한 사용 사례가 주어졌을 때, 서로 다른 데이터 모델, 스키마, 쿼리, 전략을 사용해 두 데이터베이스 시스템으로 동일한 앱의 동일한 트래픽을 처리하려면 인스턴스가 얼마나 필요한지를 묻는 방식이다. 3) 대부분의 사람들이 실제로 사용할 인스턴스 타입으로 테스트하라. 초대형 인스턴스 타입은 특정 데이터베이스 시스템의 비효율성을 가릴 수 있으며, 어차피 대부분의 사람들이 사용할 환경도 아니다.
우리는 앞으로도 Redis를 더 빠르게 최적화해 나갈 것이며, 비교 벤치마크 게시를 계속 지양할 것이다.
[Amazon AWS가 EC2 무료 액세스를 제공해 준 것에 감사드립니다]
글을 무작위로 읽기
댓글
로그인하고 댓글 남기기