Skip to content

キャッシュ

はじめに

アプリケーションが実行するデータの取得や処理のタスクの中には、CPUに負荷がかかったり、完了までに数秒かかったりするものがあります。このような場合、取得したデータを一定期間キャッシュしておくことで、後続の同じデータのリクエストで迅速に取得できるようになります。キャッシュされたデータは通常、MemcachedRedisなどの非常に高速なデータストアに保存されます。

幸いなことに、Laravelはさまざまなキャッシュバックエンドに対して表現力豊かで統一されたAPIを提供しており、これにより高速なデータ取得とWebアプリケーションのスピードアップを実現できます。

設定

アプリケーションのキャッシュ設定ファイルはconfig/cache.phpにあります。このファイルで、アプリケーション全体でデフォルトで使用するキャッシュストアを指定できます。Laravelは、MemcachedRedisDynamoDB、リレーショナルデータベースなど、一般的なキャッシュバックエンドをすぐにサポートしています。さらに、ファイルベースのキャッシュドライバも利用可能で、arrayと`null`キャッシュドライバは自動テスト用の便利なキャッシュバックエンドを提供します。

キャッシュ設定ファイルには、他にもさまざまなオプションが含まれていますが、これらについては後で確認できます。デフォルトでは、Laravelはdatabaseキャッシュドライバを使用するように設定されており、シリアライズされたキャッシュデータをアプリケーションのデータベースに保存します。

ドライバの前提条件

データベース

databaseキャッシュドライバを使用する場合、キャッシュデータを格納するデータベーステーブルが必要です。通常、これはLaravelのデフォルトの0001_01_01_000001_create_cache_table.php データベースマイグレーションに含まれています。ただし、アプリケーションにこのマイグレーションが含まれていない場合は、make:cache-table Artisanコマンドを使用して作成できます。

php artisan make:cache-table

php artisan migrate

Memcached

Memcachedドライバを使用するには、Memcached PECLパッケージをインストールする必要があります。config/cache.php設定ファイルにすべてのMemcachedサーバーをリストアップできます。このファイルには、開始するためのmemcached.serversエントリが既に含まれています。

'memcached' => [
    // ...

    'servers' => [
        [
            'host' => env('MEMCACHED_HOST', '127.0.0.1'),
            'port' => env('MEMCACHED_PORT', 11211),
            'weight' => 100,
        ],
    ],
],

必要に応じて、hostオプションをUNIXソケットパスに設定できます。その場合、portオプションを0に設定する必要があります。

'memcached' => [
    // ...

    'servers' => [
        [
            'host' => '/var/run/memcached/memcached.sock',
            'port' => 0,
            'weight' => 100
        ],
    ],
],

Redis

LaravelでRedisキャッシュを使用する前に、PECLを介してPhpRedis PHP拡張機能をインストールするか、Composerを介してpredis/predisパッケージ(~2.0)をインストールする必要があります。Laravel Sailには、この拡張機能が既に含まれています。さらに、公式のLaravelデプロイプラットフォームであるLaravel ForgeLaravel Vaporには、デフォルトでPhpRedis拡張機能がインストールされています。

Redisの設定に関する詳細情報は、Laravelドキュメントページを参照してください。

DynamoDB

DynamoDBキャッシュドライバを使用する前に、すべてのキャッシュデータを格納するDynamoDBテーブルを作成する必要があります。通常、このテーブルはcacheという名前にします。ただし、cache設定ファイル内のstores.dynamodb.table設定値に基づいてテーブル名を指定する必要があります。テーブル名は、DYNAMODB_CACHE_TABLE環境変数を介して設定することもできます。

このテーブルには、アプリケーションのcache設定ファイル内のstores.dynamodb.attributes.key設定項目の値に対応する名前を持つ文字列のパーティションキーも必要です。デフォルトでは、パーティションキーはkeyという名前になります。

次に、LaravelアプリケーションがDynamoDBと通信できるようにAWS SDKをインストールします。

composer require aws/aws-sdk-php

さらに、DynamoDBキャッシュストアの設定オプションに値を指定する必要があります。通常、これらのオプション(AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYなど)は、アプリケーションの.env設定ファイルで定義する必要があります。

'dynamodb' => [
    'driver' => 'dynamodb',
    'key' => env('AWS_ACCESS_KEY_ID'),
    'secret' => env('AWS_SECRET_ACCESS_KEY'),
    'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
    'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
    'endpoint' => env('DYNAMODB_ENDPOINT'),
],

キャッシュの使用

キャッシュインスタンスの取得

キャッシュストアインスタンスを取得するには、Cacheファサードを使用できます。これは、このドキュメント全体で使用するものです。Cacheファサードは、Laravelのキャッシュ契約の基礎となる実装への便利で簡潔なアクセスを提供します。

<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Cache;

class UserController extends Controller
{
    /**
     * アプリケーションのすべてのユーザーのリストを表示します。
     */
    public function index(): array
    {
        $value = Cache::get('key');

        return [
            // ...
        ];
    }
}

複数のキャッシュストアへのアクセス

Cacheファサードを使用すると、storeメソッドを介してさまざまなキャッシュストアにアクセスできます。storeメソッドに渡されるキーは、cache設定ファイル内のstores設定配列にリストされているストアのいずれかに対応している必要があります。

$value = Cache::store('file')->get('foo');

Cache::store('redis')->put('bar', 'baz', 600); // 10分

キャッシュからのアイテムの取得

Cacheファサードのgetメソッドは、キャッシュからアイテムを取得するために使用されます。アイテムがキャッシュに存在しない場合、nullが返されます。必要に応じて、getメソッドに2番目の引数を渡して、アイテムが存在しない場合に返すデフォルト値を指定できます。

$value = Cache::get('key');

$value = Cache::get('key', 'default');

デフォルト値としてクロージャを渡すこともできます。指定されたアイテムがキャッシュに存在しない場合、クロージャの結果が返されます。クロージャを渡すことで、データベースや他の外部サービスからデフォルト値の取得を遅延させることができます。

$value = Cache::get('key', function () {
    return DB::table(/* ... */)->get();
});

アイテムの存在の確認

hasメソッドを使用して、アイテムがキャッシュに存在するかどうかを確認できます。アイテムが存在するがその値がnullの場合、このメソッドはfalseを返します。

if (Cache::has('key')) {
    // ...
}

値の増減

incrementおよびdecrementメソッドを使用して、キャッシュ内の整数アイテムの値を調整できます。これらのメソッドは、アイテムの値を増減する量を示すオプションの2番目の引数を受け入れます。

// 値が存在しない場合は初期化します...
Cache::add('key', 0, now()->addHours(4));

// 値を増減します...
Cache::increment('key');
Cache::increment('key', $amount);
Cache::decrement('key');
Cache::decrement('key', $amount);

取得と保存

キャッシュからアイテムを取得したいが、要求されたアイテムが存在しない場合はデフォルト値を保存したい場合があります。たとえば、すべてのユーザーをキャッシュから取得するか、存在しない場合はデータベースから取得してキャッシュに追加する場合です。これは、Cache::rememberメソッドを使用して行うことができます。

$value = Cache::remember('users', $seconds, function () {
    return DB::table('users')->get();
});

キャッシュにアイテムが存在しない場合、rememberメソッドに渡されたクロージャが実行され、その結果がキャッシュに配置されます。

rememberForeverメソッドを使用して、キャッシュからアイテムを取得するか、存在しない場合は永続的に保存することもできます。

$value = Cache::rememberForever('users', function () {
    return DB::table('users')->get();
});

Stale While Revalidate

Cache::rememberメソッドを使用する際、キャッシュされた値が期限切れになった場合、一部のユーザーは遅い応答時間を経験するかもしれません。特定の種類のデータに対しては、キャッシュされた値が再計算されている間に部分的に古いデータを提供することで、キャッシュされた値が計算されている間に一部のユーザーが遅い応答時間を経験することを防ぐことができます。これはしばしば「stale-while-revalidate」パターンと呼ばれ、Cache::flexibleメソッドはこのパターンの実装を提供します。

flexibleメソッドは、キャッシュされた値が「新鮮」であると見なされる期間と、「古く」なる期間を指定する配列を受け取ります。配列の最初の値はキャッシュが新鮮であると見なされる秒数を表し、2番目の値は再計算が必要になるまで古いデータとして提供できる期間を定義します。

リクエストが新鮮な期間内(最初の値の前)に行われた場合、キャッシュは再計算なしで即座に返されます。リクエストが古い期間内(2つの値の間)に行われた場合、古い値がユーザーに提供された後に、応答が送信され、キャッシュ値の更新のために遅延関数が登録されます。リクエストが2番目の値の後に行われた場合、キャッシュは期限切れと見なされ、値は即座に再計算されます。これにより、ユーザーにとって遅い応答が発生する可能性があります。

$value = Cache::flexible('users', [5, 10], function () {
    return DB::table('users')->get();
});

取得と削除

キャッシュからアイテムを取得してから削除する必要がある場合は、pullメソッドを使用できます。getメソッドと同様に、アイテムがキャッシュに存在しない場合はnullが返されます。

$value = Cache::pull('key');

$value = Cache::pull('key', 'default');

キャッシュへのアイテムの保存

Cacheファサードのputメソッドを使用して、キャッシュにアイテムを保存できます。

Cache::put('key', 'value', $seconds = 10);

保存時間がputメソッドに渡されない場合、アイテムは無期限に保存されます。

Cache::put('key', 'value');

秒数を整数で渡す代わりに、キャッシュされたアイテムの希望する有効期限を表すDateTimeインスタンスを渡すこともできます。

Cache::put('key', 'value', now()->addMinutes(10));

存在しない場合に保存

addメソッドは、アイテムがまだキャッシュストアに存在しない場合にのみ、アイテムをキャッシュに追加します。アイテムが実際にキャッシュに追加された場合、メソッドはtrueを返します。それ以外の場合、メソッドはfalseを返します。addメソッドはアトミック操作です。

Cache::add('key', 'value', $seconds);

永続的に保存

foreverメソッドを使用して、アイテムをキャッシュに永続的に保存できます。これらのアイテムは期限切れにならないため、forgetメソッドを使用して手動でキャッシュから削除する必要があります。

Cache::forever('key', 'value');

Note

Memcachedドライバを使用している場合、「永続的に」保存されたアイテムは、キャッシュがサイズ制限に達したときに削除される可能性があります。

キャッシュからのアイテムの削除

forgetメソッドを使用して、キャッシュからアイテムを削除できます。

Cache::forget('key');

また、有効期限の秒数としてゼロまたは負の数を指定することで、アイテムを削除することもできます。

Cache::put('key', 'value', 0);

Cache::put('key', 'value', -5);

flushメソッドを使用して、キャッシュ全体をクリアすることができます。

Cache::flush();

Warning

キャッシュのフラッシュは、設定されたキャッシュの「プレフィックス」を尊重せず、キャッシュ内のすべてのエントリを削除します。他のアプリケーションと共有されているキャッシュをクリアする際には、この点を慎重に考慮してください。

キャッシュヘルパー

Cacheファサードを使用するだけでなく、グローバルなcache関数を使用してキャッシュを通じてデータを取得および保存することもできます。cache関数が単一の文字列引数で呼び出されると、指定されたキーの値が返されます。

$value = cache('key');

キーと値のペアの配列と有効期限を関数に渡すと、指定された期間の間、キャッシュに値が保存されます。

cache(['key' => 'value'], $seconds);

cache(['key' => 'value'], now()->addMinutes(10));

cache関数が引数なしで呼び出されると、Illuminate\Contracts\Cache\Factoryの実装のインスタンスが返され、他のキャッシュメソッドを呼び出すことができます。

cache()->remember('users', $seconds, function () {
    return DB::table('users')->get();
});

Note

グローバルなcache関数の呼び出しをテストする際には、ファサードのテストと同様にCache::shouldReceiveメソッドを使用できます。

アトミックロック

Warning

この機能を利用するには、アプリケーションがmemcachedredisdynamodbdatabasefile、またはarrayキャッシュドライバをアプリケーションのデフォルトキャッシュドライバとして使用している必要があります。さらに、すべてのサーバーが同じ中央キャッシュサーバーと通信している必要があります。

ロックの管理

アトミックロックを使用すると、競合状態を心配することなく分散ロックを操作できます。たとえば、Laravel Forgeはアトミックロックを使用して、一度に1つのサーバーでリモートタスクが実行されることを保証します。Cache::lockメソッドを使用してロックを作成および管理できます。

use Illuminate\Support\Facades\Cache;

$lock = Cache::lock('foo', 10);

if ($lock->get()) {
    // ロックが10秒間取得されました...

    $lock->release();
}

getメソッドはクロージャも受け取ります。クロージャが実行された後、Laravelは自動的にロックを解放します。

Cache::lock('foo', 10)->get(function () {
    // ロックが10秒間取得され、自動的に解放されます...
});

ロックがリクエスト時に利用できない場合、Laravelに指定された秒数待機するように指示できます。指定された時間制限内にロックを取得できない場合、Illuminate\Contracts\Cache\LockTimeoutExceptionがスローされます。

use Illuminate\Contracts\Cache\LockTimeoutException;

$lock = Cache::lock('foo', 10);

try {
    $lock->block(5);

    // 最大5秒間待機してロックが取得されました...
} catch (LockTimeoutException $e) {
    // ロックを取得できませんでした...
} finally {
    $lock->release();
}

上記の例は、クロージャをblockメソッドに渡すことで簡略化できます。このメソッドにクロージャが渡されると、Laravelは指定された秒数の間ロックを取得しようとし、クロージャ実行後、自動的にロックが解放されます。

Cache::lock('foo', 10)->block(5, function () {
    // 最大5秒間待機してロックが取得されました...
});

プロセス間でのロックの管理

場合によっては、あるプロセスでロックを取得し、別のプロセスで解放することが望ましい場合があります。たとえば、Webリクエスト中にロックを取得し、そのリクエストによってトリガーされたキュージョブの終了時にロックを解放することがあります。このシナリオでは、ロックのスコープ付き「所有者トークン」をキュージョブに渡し、ジョブが指定されたトークンを使用してロックを再インスタンス化できるようにする必要があります。

以下の例では、ロックが正常に取得された場合にキュージョブをディスパッチします。さらに、ロックのownerメソッドを介してキュージョブにロックの所有者トークンを渡します。

$podcast = Podcast::find($id);

$lock = Cache::lock('processing', 120);

if ($lock->get()) {
    ProcessPodcast::dispatch($podcast, $lock->owner());
}

アプリケーションのProcessPodcastジョブ内で、所有者トークンを使用してロックを復元し、解放します。

Cache::restoreLock('processing', $this->owner)->release();

現在の所有者を尊重せずにロックを解放したい場合は、forceReleaseメソッドを使用できます。

Cache::lock('processing')->forceRelease();

カスタムキャッシュドライバの追加

ドライバの作成

カスタムキャッシュドライバを作成するには、まずIlluminate\Contracts\Cache\Store契約を実装する必要があります。したがって、MongoDBキャッシュの実装は次のようになります。

<?php

namespace App\Extensions;

use Illuminate\Contracts\Cache\Store;

class MongoStore implements Store
{
    public function get($key) {}
    public function many(array $keys) {}
    public function put($key, $value, $seconds) {}
    public function putMany(array $values, $seconds) {}
    public function increment($key, $value = 1) {}
    public function decrement($key, $value = 1) {}
    public function forever($key, $value) {}
    public function forget($key) {}
    public function flush() {}
    public function getPrefix() {}
}

各メソッドをMongoDB接続を使用して実装する必要があります。各メソッドの実装例については、Laravelフレームワークのソースコード内のIlluminate\Cache\MemcachedStoreを参照してください。実装が完了したら、Cacheファサードのextendメソッドを呼び出してカスタムドライバの登録を完了することができます。

Cache::extend('mongo', function (Application $app) {
    return Cache::repository(new MongoStore);
});

Note

カスタムキャッシュドライバのコードをどこに配置するか迷った場合、appディレクトリ内にExtensions名前空間を作成することができます。ただし、Laravelには厳格なアプリケーション構造がなく、アプリケーションを好みに合わせて自由に整理できることに注意してください。

ドライバの登録

Laravelにカスタムキャッシュドライバを登録するために、Cacheファサードのextendメソッドを使用します。他のサービスプロバイダがbootメソッド内でキャッシュされた値を読み取ろうとする可能性があるため、カスタムドライバはbootingコールバック内で登録する必要があります。bootingコールバックを使用することで、アプリケーションのサービスプロバイダのbootメソッドが呼び出される直前に、すべてのサービスプロバイダのregisterメソッドが呼び出された後に、カスタムドライバが登録されることを保証できます。アプリケーションのApp\Providers\AppServiceProviderクラスのregisterメソッド内でbootingコールバックを登録します。

<?php

namespace App\Providers;

use App\Extensions\MongoStore;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * 任意のアプリケーションサービスを登録します。
     */
    public function register(): void
    {
        $this->app->booting(function () {
             Cache::extend('mongo', function (Application $app) {
                 return Cache::repository(new MongoStore);
             });
         });
    }

    /**
     * 任意のアプリケーションサービスを起動します。
     */
    public function boot(): void
    {
        // ...
    }
}

extendメソッドに渡される最初の引数はドライバの名前です。これはconfig/cache.php設定ファイル内のdriverオプションに対応します。2番目の引数はIlluminate\Cache\Repositoryインスタンスを返すべきクロージャです。クロージャには$appインスタンスが渡されます。これはサービスコンテナのインスタンスです。

拡張機能が登録されたら、アプリケーションのconfig/cache.php設定ファイル内のCACHE_STORE環境変数またはdefaultオプションを拡張機能の名前に更新します。

イベント

すべてのキャッシュ操作でコードを実行するには、キャッシュによってディスパッチされる様々なイベントをリッスンすることができます。

イベント名
Illuminate\Cache\Events\CacheHit
Illuminate\Cache\Events\CacheMissed
Illuminate\Cache\Events\KeyForgotten
Illuminate\Cache\Events\KeyWritten

パフォーマンスを向上させるために、アプリケーションのconfig/cache.php設定ファイル内で特定のキャッシュストアのevents設定オプションをfalseに設定することで、キャッシュイベントを無効にすることができます。

'database' => [
    'driver' => 'database',
    // ...
    'events' => false,
],

ユーザーノート