本投稿はTECOTEC Advent Calendar 2022の2日目の記事です。
こんにちは、証券フロンティア事業部の西永です。
今回はLaravel内で行われているRedisの仕組みを覗いた際、気づいたことを備忘録として残したいと思います。
そもそもRedisとは
Redis は、リモートディクショナリサーバー の略で、高速でオープンソースのメモリ内 key-value データストアです。このプロジェクトは、Redis の最初のデベロッパーである Salvatore Sanfilippo 氏が、イタリアのスタートアップのスケーラビリティを改善したいと考えていたときに始まりました。そこから、彼はデータベース、キャッシュ、メッセージブローカー、キューとして利用されている Redis を開発しました。 aws.amazon.com
主にキャッシュとして使うkey-valueデータストア(KVS)ですね。
AWSだとAmazon MemoryDB for Redis と Amazon ElastiCache for Redisで使えますが、今回はAWS本体ではなくRedisに着目します。
キャッシュの仕組みについて
ここのブログが非常に参考になります。大体ここを見れば分かると思います。 www.bit-hive.com
超かいつまんで書くと、タグの中身を|
で結合&sha1()
で変換したものをキーとして採用することで、タグの削除を行う際にキーの全件検索(keys *
)を防いでいます。
(Redisでキーの抽出を行う際はO(N)なのでキーが多いほど時間がかかります)
取得例
use Illuminate\Support\Facades\Cache; Cache::store('cache')->tags('user_subscriptions', 'user_subscriptions_user_id_ユーザー名')->remember('isSubscribed', $cacheTime, $callback);
ちなみにこの6373257fa23fc828194949
の値はどうやって生成しているかについてですが、こちらもLaravelの方で独自に生成しています。
github.com
uniqid('', true)
で4b340550242239.64159797
のような文字列を生成した後、str_replace()
で4b34055024223964159797
に置換して使用しています。
値が被ったら意味がないのでその対策ですね。
standard_refについて
あともう一つ、Laravel経由でRedisを使う(セットする)際、タグ*1と実数値*2以外にもstandard_ref
なるものが設定されます。
「キャッシュの仕組みについて」で取り上げた仕組みだけだとこのstandard_ref
は不要そうに思えますが、こちらは一体何に使えるのでしょうか。
まず、Laravelのソースを確認すると下記で使われており、辿っていくとput()
/set()
/remember()
する際に同時にstandard_ref
がセットされるような構造になっています。
github.com
つまり、Laravel経由で値を設定する際は大体standard_ref
が設定されることになります。
次にこのstandard_ref
の中身はどうやって見るのかについてですが、こちらはRedisのSet型を利用しているため、その関連のコマンド等は大体使用できます。
redis.shibu.jp
上記画像を見れば分かる通り、standard_ref
にはそのタグに関連した実数値のキー情報が入っています。
つまりstandard_ref
を用いれば、タグに紐づいている実数値のキーを逆引きで持ってくることができます。
この逆引きを利用して、Laravel内部ではCache::store('cache')->tags()->flush()
を行う際、タグに紐づいている実数値のキーを一緒に消すようにしています。
(flush()
-> deleteStandardKeys()
-> deleteKeysByReference()
-> deleteValues()
の箇所で消しています)
github.com
削除する際、keys
を使って検索して削除だと計算量が多くなりがち*3なので、計算量の肥大化を防ぐ工夫が行われているのは興味深いですね。
しかしこのstandard_ref
ですが、自由に保持期間が設定できる実数値のキーとは違い保持期間が無期限のため、キーの削除生成を繰り返しているとどんどん不要なstandard_ref
が増えてメモリを圧迫していきます。
この問題はLaravelのissueでも取り上げられており、コラボレーターの回答としては
If you can't flush your cache database on a regular basis, don't use tags or write a custom script to clean up leftovers.
(DeepL翻訳) キャッシュデータベースを定期的にフラッシュできない場合は、タグを使用しないか、カスタムスクリプトを書いて残飯をクリーンアップしてください。 github.com
とのこと。
カスタムスクリプトについてはそのissue内に有志の方が作られたスクリプトが載せられているので、それを用いることでメモリ圧迫問題を回避できる(かも)。
一方Laravel 8でタグの削除については改善されており、standard_ref
に加えてタグ*4も無期限保持ですが、Cache::store($cacheType)->tags($tags)->flush()
を使用した際にちゃんとタグは物理削除するよう修正されています。
github.com
Redisでメモリが圧迫気味の方は確認してみてはいかがでしょうか。
おわりに
今取り組んでいる案件でもLaravelでRedisを使用した構築を用いており、その一環として調査した内容を取り上げました。
同様の方がいらっしゃいましたら参考になりますと幸いです。
Amazon Web Servicesおよびかかる資料で使用されるその他の AWS 商標 は、米国および/またはその他の諸国における、Amazon.com, Inc. またはその関連会社の商標です。
*1:上記の例だとlaravel_database_laravel_cache:tag:user_subscriptions:key、laravel_database_laravel_cache:tag:user_subscriptions_user_id_ユーザー名:key
*2:上記の例だとlaravel_database_laravel_cache: 202e2eb93be77192890455ea1e7c7769c084f4f5:isSubscribed
*3:「keys」は計算量O(N)
*4:上記の例だとlaravel_database_laravel_cache:tag:user_subscriptions:key、laravel_database_laravel_cache:tag:user_subscriptions_user_id_ユーザー名:key