LoginSignup
13
10

More than 3 years have passed since last update.

Laravelで雑にクエリ結果をキャッシュする

Last updated at Posted at 2019-12-07

はじめに

本記事はLaravel #2 Advent Calendar 2019の8日目の記事です。

TL;DR

  • 端的に言うとGeneaLabs/laravel-model-caching1を用いたデータベースの結果キャッシュのススメ
  • データベースへのクエリ数を何とか減らしたい場合に有効
  • 追加実装することなく、クエリ結果をキャッシュしてくれる
  • 「雑にキャッシュする」とは、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 Loading4などによるクエリ効率化(=N+1問題解消)とGeneaLabs/laravel-model-cachingは相性が悪い
    • データが多いとIN句が長くなり、結果的にキャッシュ効率が下がる
    • 後述のHasOneThroughHasManyThroughなリレーションモデルはキャッシュしないという問題がある
  • 複雑なクエリの場合は、オーソドックスなキャッシング3を使ったほうが良さそう

サンプルアプリ実装上のポイント

サンプルアプリ実装上のポイントは以下のとおりです。

セットアップ方法については、imunew/laravel-model-caching-exampleREADMEを参照のこと。

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にアクセスすると、下記のような管理画面に遷移します。

apcu.png

krakjoe/apcu: APCu - APC User Cacheapc.phppublicにそのまま置きました。
キャッシュの操作などで、BASIC認証が必要となるのですが、デフォルトパスワードではログインできないので、そこだけ変更しています。

キャッシュが効いているか確認する

itsgoingd/clockworkというプロファイリングツールをインストールしているので、ブラウザで実行されたSQLなど確認できます。
Chrome(とFirefox)用のエクステンション5があるので、インストールしておきます。

キャッシュなしの場合

事前に前述のAPC管理画面からキャッシュをクリアしておきます。

http://localhost/api/artists/1にアクセスすると、以下のようにClockworkDatabaseタブで2つのクエリが実行されたことを確認できます。

SELECT * FROM "artists" WHERE "ArtistId" = '1' LIMIT 1
SELECT * FROM "albums" WHERE "albums"."ArtistId" = '1' 
and "albums"."ArtistId" IS not NULL

スクリーンショット 2019-12-07 20.53.43.png

キャッシュありの場合

続けて、もう一度、http://localhost/api/artists/1にアクセスします。

すると、実行されたクエリがないため、Databaseタブが表示されません。
Cacheタブを確認すると、キャッシュエントリの件数などを確認することができます。

スクリーンショット 2019-12-07 21.00.57.png

キャッシュの詳細

APC管理画面のUser Cache Entriesボタンからキャッシュエントリーの一覧が表示されます。

apcu_cache_list.png

リストをクリックすると、各キャッシュエントリの詳細を確認することができます。

apcu_cache_entry.png

キャッシュされないケース

GeneaLabs/laravel-model-cachingissues6にもあるように、HasOneThroughHasManyThroughなリレーションモデルはキャッシュしない。

試しに、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ですが、
今後のアップデートを注視しつつ、上手に活用していきたいと思います。
興味ある方は、サンプルアプリを使って研究してみてください。
ではでは。

13
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
10