はじめに
本記事はLaravel #2 Advent Calendar 2019の8日目の記事です。
TL;DR
- 端的に言うと
GeneaLabs/laravel-model-caching
1を用いたデータベースの結果キャッシュのススメ - データベースへのクエリ数を何とか減らしたい場合に有効
- 追加実装することなく、クエリ結果をキャッシュしてくれる
- 「雑にキャッシュする」とは、
GeneaLabs/laravel-model-caching
が複雑なリレーションをキャッシュしてくれないから - サンプルアプリを作ったので、とにかく手を動かして覚えたい方は、こちら2を参照のこと
Laravelのキャッシュといえば
Laravel
公式のドキュメント3にあるように、データベースから取得した結果を特定のキーに紐づけてキャッシュする方法を思い浮かべると思います。
$value = Cache::get('key', function () {
return DB::table(...)->get();
});
ただ、この方法では、以下の問題があります。
- 全てのデータ取得箇所に追加の実装が必要
- ルートパラメータやバリデーション(
exists
)など、フレームワークやライブラリ側でデータ取得している箇所については(簡単に)キャッシュできない
GeneaLabs/laravel-model-caching
のススメ
私なりのオススメポイントは以下のとおり。
- インストールして、少し設定するだけで、(それなりに)動いてくれる
- フレームワークやライブラリ側で呼び出されるクエリもキャッシュしてくれる
注意点
-
GeneaLabs/laravel-model-caching
は、WHERE句
やORDER BY句
などの条件をキャッシュキーにする - 条件が複雑になればなるほど、キャッシュ効率(ヒット率)は下がっていく(同じ条件のクエリが続けて実行されればその限りではないものの)
-
Eager Loading
[^Eager Loading]などによるクエリ効率化(=N+1問題
解消)とGeneaLabs/laravel-model-caching
は相性が悪い- データが多いと
IN
句が長くなり、結果的にキャッシュ効率が下がる - 後述の
HasOneThrough
やHasManyThrough
なリレーションモデルはキャッシュしないという問題がある
- データが多いと
- 複雑なクエリの場合は、オーソドックスなキャッシング3を使ったほうが良さそう
サンプルアプリ実装上のポイント
サンプルアプリ実装上のポイントは以下のとおりです。
- DBはSQLite
- データベースファイルは、SQLite Sample Database And Its Diagram (in PDF format)からダウンロード
-
albums
、artists
、tracks
の3つのリソースについて、いくつかのAPIを実装 - キャッシュストレージは
APCu
セットアップ方法については、imunew/laravel-model-caching-exampleのREADME
を参照のこと。
docker-compose up -d
でアプリを起動し、http://localhost/api/artists
にアクセスすると、以下のようなjson
が返ってきます。
{
"artists": [
{
"id": 1,
"name": "AC/DC",
"albums": [
{
"id": 1,
"title": "For Those About To Rock We Salute You"
},
{
"id": 4,
"title": "Let There Be Rock"
}
]
},
{"..."},
{
"id": 275,
"name": "Philip Glass Ensemble",
"albums": [
{
"id": 347,
"title": "Koyaanisqatsi (Soundtrack from the Motion Picture)"
}
]
}
]
}
APC管理画面を配置
http://localhost/apc.php
にアクセスすると、下記のような管理画面に遷移します。
krakjoe/apcu: APCu - APC User Cacheのapc.php
をpublic
にそのまま置きました。
キャッシュの操作などで、BASIC認証が必要となるのですが、デフォルトパスワードではログインできないので、そこだけ変更しています。
キャッシュが効いているか確認する
itsgoingd/clockworkというプロファイリングツールをインストールしているので、ブラウザで実行されたSQLなど確認できます。
Chrome
(とFirefox
)用のエクステンション[^Chrome Extension]があるので、インストールしておきます。
キャッシュなしの場合
事前に前述のAPC管理画面からキャッシュをクリアしておきます。
http://localhost/api/artists/1
にアクセスすると、以下のようにClockwork
のDatabase
タブで2つのクエリが実行されたことを確認できます。
SELECT * FROM "artists" WHERE "ArtistId" = '1' LIMIT 1
SELECT * FROM "albums" WHERE "albums"."ArtistId" = '1'
and "albums"."ArtistId" IS not NULL
キャッシュありの場合
続けて、もう一度、http://localhost/api/artists/1
にアクセスします。
すると、実行されたクエリがないため、Database
タブが表示されません。
Cache
タブを確認すると、キャッシュエントリの件数などを確認することができます。
キャッシュの詳細
APC管理画面のUser Cache Entries
ボタンからキャッシュエントリーの一覧が表示されます。
リストをクリックすると、各キャッシュエントリの詳細を確認することができます。
キャッシュされないケース
GeneaLabs/laravel-model-caching
のissues
[^Github Issues]にもあるように、HasOneThrough
やHasManyThrough
なリレーションモデルはキャッシュしない。
試しに、http://localhost/api/artists/1/tracks
に複数回アクセスしても、下記のSQLが毎回実行されます。
SELECT "tracks".*, "albums"."ArtistId" as "laravel_through_key"
FROM "tracks"
inner join "albums" on "albums"."AlbumId" = "tracks"."AlbumId"
WHERE "albums"."ArtistId" = '1'
尚、Lazy Loading
でもEager Loading
でも同じくキャッシュされなかった。
おわりに
2019-12-08
時点でv0.7.3
とまだまだ発展途上なGeneaLabs/laravel-model-caching
ですが、
今後のアップデートを注視しつつ、上手に活用していきたいと思います。
興味ある方は、サンプルアプリを使って研究してみてください。
ではでは。
-
https://laravel.com/docs/6.x/cache
[^Eager Loading]: https://laravel.com/docs/6.x/eloquent-relationships#eager-loading
[^Chrome Extension]: https://chrome.google.com/webstore/detail/clockwork/dmggabnehkmmfmdffgajcflpdjlnoemp
[^Github Issues]: https://github.com/GeneaLabs/laravel-model-caching/issues/279#issuecomment-531597866 ↩ ↩2