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

Salvatore Sanfilippo

為什麼我們不提供 Redis 與其他資料庫的比較基準測試

Redis 的速度可能是吸引新使用者的一大賣點,因此順應比較式「廣告」的風潮,在 Redis.io 上放幾個比較數據似乎很合理。然而這麼做有兩個問題。其一是目標:我並不想說服開發者採用 Redis,我們只是盡力提供一個合適的產品,如果大家能用它順利完成工作,我們就很開心了,我的行銷企圖也就到此為止。更重要的是:要以公平的方式比較不同的系統,幾乎總是不可能的。

要比較兩個資料庫並取得公平的數據,它們必須在*很多方面*保持一致:資料模型、確切的耐久性保證、資料複寫安全性、分割期間的可用性等等:通常某個系統的得分會比另一個系統低,原因在於它為了提供那些較不「吸睛」卻依然非常重要的特性而犧牲了速度。此外,除非不同的資料庫系統使用完全相同的協定,否則測試套件本身也是一個複雜的問題:光是客戶端函式庫的差異,就可能造成巨大的落差。

然而,仍有人持不同看法,認為比較不同資料庫系統的速度無論如何都是個好主意。舉例來說,昨天有一份 Redis 與 AerospikeDB 的 benchmark 發表於此:http://lynnlangit.com/2015/01/28/lessons-learned-benchmarking-nosql-on-the-aws-cloud-aerospikedb-and-redis/

我將利用這份 benchmark 來說明 benchmark 為何是容易誤導人的東西。在該 benchmark 中,出於某種奇怪的原因,使用了配備 244 GB RAM(!)的超大型 EC2 執行個體。那是 R3.8xlarge 執行個體。而我的測試將使用更貼近真實世界的 m3.medium 執行個體。

使用這種怪獸級的執行個體,Redis 在單節點的情況下,每秒可提供 128k ops 的處理能力。我的 EC2 執行個體規格要小得多,從另一個 EC2 執行個體使用 Redis benchmark 進行測試,在不使用 pipelining(管線化)且同樣使用 100 bytes 資料大小的情況下,我得到 32k ops/sec,因此以單一行程來看,我的執行個體速度大約慢了 4 倍。

讓我們透過 Redis INFO 指令來看看在此 benchmark 期間系統如何使用 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.87

Redis 花了約 3 秒的 system time,且僅約 1.5 秒在 user space。這裡發生的情況是,對於每個請求,最大的工作量是在執行 read() 與 write() 呼叫。此外,由於每個客戶端都是一個查詢對應一個回覆的工作負載,我們必須為每個客戶端的每個請求付出完整的 RTT。

現在來看看如果改用 pipelining 會發生什麼事,這是 Redis 使用者非常熟悉且廣泛運用的功能,因為這是最大化伺服器使用率的唯一方法,而且在應用程式中通常有不少地方可以在同一時間執行多個操作。

使用包含 32 個操作的 pipeline,數據出現了劇烈的變化。我的小型執行個體僅使用單一核心就能達到 250k ops/sec,這相當於前述 benchmark 中使用 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 秒的 system time,以及 8.46 秒在 Redis 行程本身。

在 pipeline 中使用較小的數字會得到介於中間的結果。pipeline 為 4 時=100k ops/sec(換算到 benchmark 中使用的大型執行個體上,應該約為 400k ops/sec),pipeline 為 8 時=180k ops/sec,依此類推。

所以基本上,以這種方式對 Redis 與 AerospikeDB 進行 benchmarking 會得到極為相似的結果,並非巧合。或多或少,你測試的並不是資料庫本身,而是網路堆疊與核心。如果資料庫只需透過一次 read 與一次 write 系統呼叫就能處理查詢,而沒有其他巨大的浪費,就會得到這樣的結果,而在此情況下,實際處理查詢的時間很短,因為我們談的是能完全放入記憶體的資料(順帶一提,在 Redis 中 10M 個 100k 的鍵只會用到那些執行個體所配置記憶體的一小部分)。

然而,關於這一點還有更多值得討論。那麼我們能執行的操作呢?只測試 Redis 的 GET/SET,就好比測試一輛法拉利,卻只檢查它在下雨時清潔後視鏡的能力。

Redis 架構的一個基本特點是,差異很大的操作成本卻很相近,那麼,像我們那個龐大的 Facebook 遊戲,將已結束遊戲的分數發布以建立排行榜的情況又如何呢?

同一個單一行程在執行以下查詢時可達到 110k ops/sec:ZADD myscores <random-int> <random-int>。

但讓我們要求更多,如果同時使用 HyperLogLog(超對數基數估計)來估算基數,一邊新增元素、一邊用兩個 redis-benchmark 行程讀取當前的估計值,又會如何呢?集合大小同樣是 1000 萬。因此在這次測試中,我啟動了一個執行 PFADD、加入集合中隨機元素的 benchmark,以及另一個同時對同一個 HyperLogLog 執行 PFCOUNT 的 benchmark。兩者同時都達到了 250k ops/sec,合計以單一 Redis 行程每秒處理了五十萬次操作。

在 Redis 中執行複雜操作就類似於 pipelining。你會希望每次 read/write 都*做更多*的事,否則你的效能就會被 I/O 所主導。

好,以下是幾個有用的結論。1)GET/SET 的 benchmark 並不是比較不同資料庫系統的好方法。2)更好的效能比較方式是依使用情境來比較。你可以說,針對某個特定的使用情境,使用不同的資料模型、結構描述、查詢、策略,用兩種不同的資料庫系統處理同一個應用程式的相同流量,各需要多少執行個體?3)請使用大多數人實際會使用的執行個體類型進行測試,超大型的執行個體類型可能會掩蓋某些資料庫系統的低效率,而且無論如何也不是大多數人會使用的類型。

我們會繼續為速度優化 Redis,也會繼續避免發布比較性的 benchmark。

[感謝 Amazon AWS 免費提供 EC2 的存取權限]

原文由 Salvatore Sanfilippo 發布

本文章由 muse-spark-1.2-contributor 進行翻譯