Why we don’t have benchmarks comparing Redis with other DBs

Salvatore Sanfilippo

Redisと他のデータベースを比較するベンチマークがない理由

Redisの速度は、新しく使い始める人にとって売りの一つになり得ます。そのため、比較による「広告」が広がっている流れに倣えば、Redis.ioにもいくつか比較結果を掲載するのが自然でしょう。しかし、これには二つ問題があります。一つは目的の問題です。開発者にRedisの採用を説得したいわけではありません。私たちは適切な製品を提供するために最善を尽くしているだけで、それによって人々が仕事を片付けられるならうれしい。それが私のマーケティングに対する望みのすべてです。もう一つあります。異なるシステムを公平に比較することは、ほとんどの場合不可能なのです。

二つのデータベースを比較して公平な数値を得るには、データモデル、正確な耐久性の保証、データレプリケーションの安全性、パーティション発生時の可用性など、多くの点で同じ条件にする必要があります。実際には、あるシステムが別のシステムより低いスコアになることがよくあります。それは、速度を犠牲にして、あまり「見栄えのする」ものではないものの、非常に重要な特性を提供しているからかもしれません。さらに、異なるデータベースシステムがまったく同じプロトコルで通信するのでない限り、テストスイートも複雑な問題になります。クライアントライブラリの違いだけでも、大きな差が生じる可能性があります。

それでも異論を唱える人はいます。異なるデータベースシステムの速度を比較するのは、やはりよい考えだと考える人たちです。たとえば昨日、RedisとAerospikeDBのベンチマークがこちらで公開されました:http://lynnlangit.com/2015/01/28/lessons-learned-benchmarking-nosql-on-the-aws-cloud-aerospikedb-and-redis/

ベンチマークがいかに人を惑わせるものか、という私の主張を示すために、このベンチマークを使います。このベンチマークでは、なぜか非常に巨大なEC2インスタンスが使われています。なんと244GBのRAMを搭載したインスタンスです。使われているのはR3.8xlargeインスタンスです。私のテストでは、より現実的なm3.mediumインスタンスを使います。

このような巨大なインスタンスを使うと、Redisは単一ノードの場合で毎秒128kオペレーションを処理できました。私のEC2インスタンスはそれよりはるかに制限されています。別のEC2インスタンスからRedis benchmarkを使い、パイプライン処理は使わず、データサイズを同じ100バイトにしてテストしたところ、毎秒32kオペレーションでした。つまり、単一プロセスの場合、私のインスタンスはおよそ4倍遅いということです。

このベンチマーク中に、システムがCPUをどのように使っているかをRedisのINFOコマンドで見てみましょう。

# 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.87

Redisはシステム時間を約3秒使い、ユーザー空間ではわずか約1.5秒しか使っていません。ここで起きているのは、各リクエストについて、処理の大部分がread()とwrite()の呼び出しに費やされているということです。また、各クライアントが1クエリにつき1レスポンスを返すワークロードなので、各クライアントの各リクエストで、RTT全体の時間を負担することになります。

では、代わりにパイプライン処理を使うとどうなるか確認しましょう。これはRedisユーザーには非常によく知られ、盛んに利用されている機能です。サーバーを最大限に活用する唯一の方法であり、アプリケーション内には通常、一度に複数の操作を実行できる箇所がいくつもあるからです。

32個の操作をパイプラインにまとめると、数値は劇的に変わりました。私の小さなインスタンスは、単一コアを使って毎秒250kオペレーションを処理できました。これは、先ほどのベンチマークで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秒、Redisプロセス自体では8.46秒使いました。

パイプラインに含める操作数を減らすと、中間的な結果になります。4個のパイプラインでは毎秒100kオペレーション(ベンチマークで使われたより大きなインスタンスなら毎秒約400kオペレーションに相当するはずです)、8個では毎秒180kオペレーション、といった具合です。

つまり、RedisとAerospikeDBをこの方法でベンチマークすると、非常によく似た結果になるのは偶然ではありません。多かれ少なかれ、テストしているのはデータベースではなく、ネットワークスタックとカーネルです。データベースが、他に大きな無駄を生じさせず、readとwriteのシステムコールだけでクエリを処理できるなら、得られる結果はこうなります。そしてここでは、メモリに収まるデータを扱っているため、実際のクエリを処理する時間は短いのです(ちなみに、Redisで100バイトのキーを1,000万個保存しても、これらのインスタンスに割り当てられたメモリのほんの一部しか使いません)。

しかし、これだけではありません。実行できる操作についてはどうでしょうか。GET/SETを実行してRedisをテストするのは、雨の日にミラーをきれいにする性能を調べてフェラーリを評価するようなものです。

Redisのアーキテクチャーの基本的な部分は、大きく異なる操作でもコストが同程度になるという点です。では、終了したゲームのスコアを投稿してランキングを作る、巨大なFacebookゲームについてはどうでしょうか。

クエリがZADD myscores <random-int> <random-int>の場合、同じ単一プロセスで毎秒110kオペレーションを処理できます。

では、さらに要求してみましょう。新しい要素を追加しながら、HyperLogLogでカーディナリティを推定し、二つのredis-benchmarkプロセスで現在の推定値を同時に読み取る場合はどうでしょうか。セットのサイズはまた1,000万です。このテストでは、一方のベンチマークでセット内のランダムな要素を使ってPFADDを実行し、もう一方で同じHyperLogLogに対して同時にPFCOUNTを実行しました。どちらも同時に毎秒250kオペレーションを記録し、単一のRedisプロセスで合計毎秒50万オペレーションとなりました。

Redisでは、複雑な操作を実行することはパイプライン処理に似ています。各read/writeで*より多く*の処理をしたいのです。そうでなければ、性能はI/Oに支配されてしまいます。

では、いくつか有用な点をまとめます。1)GET/SETベンチマークは、異なるデータベースシステムを比較する方法としては優れていません。2)よりよい性能比較は、ユースケースごとに行うことです。つまり、特定のユースケースについて、異なるデータモデル、スキーマ、クエリ、戦略を使った場合、二つの異なるデータベースシステムで同じアプリケーションの同じトラフィックを処理するには、それぞれ何台のインスタンスが必要か、という比較です。3)実際に多くの人が使うことになるインスタンスタイプでテストしてください。巨大なインスタンスタイプでは、特定のデータベースシステムの非効率さが隠れてしまう可能性がありますし、そもそも多くの人が使うものではありません。

これからもRedisの速度を最適化し続けます。そして、比較ベンチマークの公開はこれからも避けます。

[EC2への無料アクセスを提供してくれたAmazon AWSに感謝します]

原文は Salvatore Sanfilippo により に公開されました。

この記事は「gpt-5.6-luna」を使用して翻訳されました。