最近業務の一貫で他エンジニアのコードレビューをしている時にコメントした件を備忘録として残しておく。
自身が所属するプロジェクトではPHP×Laravelを使用していて、今回以下のような実装要件であった。
複数のリスト型キーをRedisにセットする
そして以下のような実装がされていた。
foreach($key_map as $key => $value) {
Redis::lpush($key, $value);
Redis::expire($key, self::CACHE_EXPIRE_TIME);
}
普通に動くと思うし、パフォーマンスを気にする必要ないのなら、スルーでもいいですが、せっかくなので、
Laravelの公式ドキュメント( https://readouble.com/laravel/5.4/ja/redis.html )をみると、こんなコマンドがあるよと。
パイプラインコマンド
一回の操作でサーバに対し多くのコマンドを送る必要がある場合はパイプラインを使うべきでしょう。pipelineメソッドは引数をひとつだけ取り、Redisインスタンスを取る「クロージャ」です。このRedisインスタンスで全コマンドを発行し、一回の操作で全部実行できます。
Redis::pipeline(function ($pipe) {
for ($i = 0; $i < 1000; $i++) {
$pipe->set("key:$i", $i);
}
});
ふむふむ、インスタンス内で全コマンドを発行して一回の操作で全部実行とな。
それは確かにパフォーマンス良さそう。
ということで適当にartisanコマンド作って試してみる。
こんな感じ
// 直接
$startTime = microtime(true);
for ($i = 0; $i < $count; $i++) {
Redis::lpush('test_key' . $i, 'hoge');
}
$runningTime = microtime(true) - $startTime;
var_dump('straight running time: ' . $runningTime . ' [s]');
// pipeline
$startTime = microtime(true);
$redis = Redis::connection('redis_0');
$redis->pipeline(function ($pipe) use($count) {
for ($i = 0; $i < $count; $i++) {
$pipe->lpush('test_key_pipeline' . $i, 'hoge');
}
});
$runningTime = microtime(true) - $startTime;
var_dump('pipeline running time: ' . $runningTime . ' [s]');
↓↓結果↓↓
// 10件一括
# php artisan redis_test 10
string(42) "straight running time: 0.0122230052948 [s]"
string(46) "pipeline running time: 0.00022292137145996 [s]"
// 1000件一括
# php artisan redis_test 1000
string(43) "straight running time: 0.15764403343201 [s]"
string(45) "pipeline running time: 0.0015499591827393 [s]"
パフォーマンス的には100倍くらいでてる。使わない理由はなさそう。
setとかはアトミックにそもそもできるから、使う必要はないだろうけど、
リスト型やハッシュ型とかで複数一括実行が必要になった際は使えそうです。
注意なのが、Redis複数台ある時は、全部に同じコマンドが発行されて無駄が出たりするので、
手前でconnectionごとにグルーピングして、それぞれのRedisサーバーに対して適切なキーリストを渡してあげることかなと思います。
これからも勉学に励みます。