まえがき
梅雨のせいでコインランドリーで乾燥機を回している時ふと、
「どれくらいのデータ量だとRedis(キャッシュ)導入に価値がある?
だって、一般的な処理だと早くなっても数ミリ秒の世界だし」と考え始めました。(唐突)
とりあえず100万件のデータでRedis導入にどれ程の価値があるのか、いざ検証してみます。
検証環境
- ローカルPC (RAM 8GB)
- Redis 6.0.5 (maxmemory 0=無制限)
- Rails 6.0.3 (API mode)
- MySQL
※ 今回はDBに存在する100万件のレコードをそのままRedisに突っ込みます。
100万件の投稿データ作成
今回検証のアプリケーションとしてRailsを使うのでseed ※でデータ投入しても良かったんですけど、何分遅いのでストアドプロシージャでinsertしていきます。
delimiter //
create procedure bulkinsert()
begin
SET @count = 0;
SET @limit = 1000000;
WHILE @limit > @count DO
INSERT INTO comedians (name, created_at)
VALUES ('渡部 建', now());
SET @count = @count + 1;
END WHILE;
end
//
delimiter ;
call bulkinsert();
DROP PROCEDURE IF EXISTS bulkinsert;
[rails_dev_redis]> select count(*) from comedians;
+----------+
| count(*) |
+----------+
| 1000000 |
+----------+
1 row in set (0.26 sec)
はい、100万件データがありますね。
model.to_aの実行結果をキャッシュ
キャッシュの引数にActiveRecordを渡すと、select文だけキャッシュするので、to_aで。
技術ブログによくあるパターンですね。
Comedian.all.to_a
計測結果
Processing by ComediansController#benchmark as HTML
user system total real
Rails_Cache_Fetch 38.901311 2.737989 41.639300 ( 52.607612)
Completed 200 OK in 52610ms (Views: 0.7ms | ActiveRecord: 0.0ms | Allocations: 14003982)
ご、52秒...。(´-`).。oO(なぜだ)
Redisのデータの読み込みが遅い問題
A String value can be at max 512 Megabytes in length.
Redisの公式ドキュメントにあるようにstring型は512MBまでしかサポートされていない。
ここに原因があるのではないかと考え、サイズを調べてみることに。
調査結果
Processing by ComediansController#benchmark as HTML
Rails_Cache_Fetch length: 2401298
Completed 200 OK in 25ms (Views: 0.9ms | ActiveRecord: 0.0ms | Allocations: 329)
2.4MBしか使ってないので、sizeの問題ではなさそう。
Redis側のデータを確認してみると、バイナリ文字が入っている。
Rails.cacheがバイナリから正規化する時間分だけ、遅くなっているのかもと考え、
redis.setでjsonを突っ込んでみることに。
計測結果
Processing by ComediansController#benchmark as HTML
user system total real
Redis_Get 7.533286 0.696594 8.229880 ( 10.452844)
Redis_Get length: 86100083
Completed 200 OK in 12306ms (Views: 0.4ms | ActiveRecord: 0.0ms | Allocations: 7000593)
10秒か〜。早くはなったんですけど、こういうことじゃないんですよね。
データの中身がバイナリじゃないので、8600万文字入ってます。
試行錯誤の末、高速化の成功
検索しても打開策がなかったので、色々試してみることに。
key-valueを複数もつパターンもやってみましたが、結局ループするとその分だけ遅いんですよね。
試行錯誤した結果、Rails.cacheでjsonを突っ込むと高速で参照できることがわかりました。
以下3パターンのRead/Writeの速度計測結果を貼ります。
オブジェクト・関数 | 形式 | ベンチマーク上の表記 |
---|---|---|
redis | json | Redis_Set・Redis_Get |
Rails.cache | json | Rails_Cache_Write_Json・Rails_Cache_Read_Json |
Rails.cache | Array | Rails_Cache_Write_Array・Rails_Cache_Read_Array |
Set時の速度計測結果
Processing by ComediansController#benchmark as */*
user system total real
Redis_Set Comedian Load (968.2ms) SELECT `comedians`.* FROM `comedians`
↳ app/controllers/comedians_controller.rb:9:in `block (2 levels) in benchmark'
134.868704 4.608951 139.477655 (150.200997)
Rails_Cache_Write_Json CACHE Comedian Load (0.0ms) SELECT `comedians`.* FROM `comedians`
↳ app/controllers/comedians_controller.rb:12:in `block (2 levels) in benchmark'
117.643508 2.911988 120.555496 (121.557569)
Rails_Cache_Write_Array CACHE Comedian Load (0.0ms) SELECT `comedians`.* FROM `comedians`
↳ app/controllers/comedians_controller.rb:15:in `block (2 levels) in benchmark'
39.717218 1.970096 41.687314 ( 41.873064)
意外にもArrayが一番早いです。
Get時の速度計測結果
user system total real
Redis_Get 12.294169 0.375882 12.670051 ( 12.914826)
Rails_Cache_Read_Json 0.196984 0.170824 0.367808 ( 0.321568)
Rails_Cache_Read_Array 24.414510 1.228965 25.643475 ( 25.746937)
Get_DB CACHE Comedian Load (0.1ms) SELECT `comedians`.* FROM `comedians`
↳ app/controllers/comedians_controller.rb:30:in `block (2 levels) in benchmark'
117.189727 4.600185 121.789912 (122.321526)
Rails_Cache_Read_Jsonだと0.3秒で100万件を読み込めています。
Get_DB(122秒)はDBから直接fetch allした場合の速度で、比較の為に入れてます。
文字数のカウント
Redis length: 86099997
Rails_Cache_Json length: 2744225
Rails_Cache_Read_Array length: 2399348
redis.setでjsonを格納した場合、8600万文字入っていたのに対し、
Rails.cacheは270万文字と驚異的な短さです。
バイナリで保存しているのは容量の面で非常に有利ということですね。
あとがき
「どれくらいのデータだと導入に価値があるか」という本題でしたが、
100万未満のデータ量で参照に1秒以上かかってたら導入の価値はあるかなと思います。
たった4行のコード追加だけで、100万件読み込むのに1秒切れると思っていなかったので、ただただ脱帽しております。
ありがとうサルバトーレ、ありがとうここまで読んでくれた読者。