為什麼我們不做 Redis 與其他資料庫的效能比較
原文由 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/。
我會拿這份測試來說明,為什麼效能測試是個容易誤導人的東西。在這份測試中,不知為何使用了配備 244 GB 記憶體(!)的超大型 EC2 執行個體。那是 R3.8xlarge 執行個體。而我的測試則會改用更貼近真實世界的 m3.medium 執行個體。
使用這種怪獸級的執行個體,Redis 在單節點的情況下能達到每秒 12.8 萬次操作。我的 EC2 執行個體規格要小得多,從另一台 EC2 執行個體用 redis-benchmark 測試,在不使用 pipelining、資料大小同樣是 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 成本。
現在來看看如果改用 pipelining 會發生什麼事,這是 Redis 使用者非常熟悉、也經常利用的功能,因為這是最大化伺服器使用率的唯一方法,而且在應用程式中通常有不少地方可以同時執行多個操作。
當 pipeline 設為 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 行程本身。
把 pipeline 的數量調小,會得到介於中間的結果。Pipeline 為 4 時是每秒 10 萬次操作(換算到測試中那台更大的執行個體上,大約是每秒 40 萬次),pipeline 為 8 時是每秒 18 萬次,依此類推。
所以,基本上用這種方式對 Redis 和 AerospikeDB 做效能測試會得到非常相近的結果,並非巧合。你或多或少測的不是資料庫本身,而是網路堆疊和核心。只要資料庫能用一次 read 和一次 write 系統呼叫來處理查詢、而沒有其他巨大的浪費,就會得到這樣的結果,而在這裡,處理實際查詢的時間很短,因為我們談的是完全能放進記憶體的資料(順帶一提,在 Redis 中 1000 萬個 100k 的 key 所用的記憶體,只是那些執行個體所配置記憶體的一小部分)。
不過,關於這件事還有更多要說的。我們能執行的操作種類呢?只測 Redis 的 GET/SET,就像測試一台法拉利,卻只檢查它在下雨時清理後照鏡的能力好不好。
Redis 架構的一個根本特點是,差異很大的操作成本卻很相近,那麼,像我們那個大型的 Facebook 遊戲,發布已完成遊戲的分數來建立排行榜,又會如何呢?
同樣是單一行程,當查詢是:ZADD myscores <random-int> <random-int> 時,每秒可以執行 11 萬次操作。
但讓我們要求更多,如果用 HyperLogLog 來估算基數,同時用兩個 redis-benchmark 行程在同一個 HyperLogLog 上一個不斷用隨機元素執行 PFADD、另一個同時執行 PFCOUNT 讀取目前的估計值呢?集合大小同樣是 1000 萬。所以在這次測試中,我啟動了一個執行 PFADD 的 benchmark,用集合中的隨機元素,另一個同時在同一個 HyperLogLog 上做 PFCOUNT。兩者同時都達到了每秒 25 萬次操作,用單一 Redis 行程合計每秒 50 萬次操作。
在 Redis 中,執行複雜操作就跟使用 pipelining 類似。你會希望每次 read/write 能做*更多*的事,否則效能就會被 I/O 主導。
好,幾個有用的結論。1)GET/SET 的效能測試不是比較不同資料庫系統的好方法。2)更好的效能比較方式是依使用情境來比。你應該說,針對某個特定的使用情境,使用不同的資料模型、結構描述、查詢、策略,用兩套不同的資料庫系統處理同樣的應用流量各需要多少台執行個體?3)要用大多數人實際會用的執行個體類型來測試,超大型的執行個體可能會掩蓋某些資料庫系統的低效率,而且無論如何那也不是大多數人會用的類型。
我們會繼續為速度而優化 Redis,也會繼續避免發布比較式的效能測試。
[感謝 Amazon AWS 免費提供 EC2 存取權限]
隨機一篇部落格
留言
登入後參與討論