キャッシュ
イントロダクション
アプリケーションが実行するデータ取得や処理タスクの中には、CPUに負荷がかかるものや、完了までに数秒かかるものがあります。このような場合、取得したデータを一定期間キャッシュしておき、以降の同じデータに対するリクエストですばやく取得できるようにするのが一般的です。キャッシュされたデータは通常、MemcachedやRedisのような非常に高速なデータストアに保存されます。
ありがたいことに、Laravelはさまざまなキャッシュバックエンドに対応する、表現力豊かで統一されたAPIを提供しており、その驚異的な高速データ取得を活用して、Webアプリケーションを高速化できます。
設定
アプリケーションのキャッシュ設定ファイルはconfig/cache.phpにあります。このファイルで、アプリケーション全体でデフォルトで使用するキャッシュストアを指定します。Laravelは、Memcached、Redis、DynamoDB、リレーショナルデータベースといった一般的なキャッシュバックエンドを最初からサポートしています。さらに、ファイルベースのキャッシュドライバも利用でき、arrayと`null`キャッシュドライバは自動テストに便利なキャッシュバックエンドを提供します。
キャッシュ設定ファイルには、他にもさまざまなオプションが含まれているので、確認してください。デフォルトでLaravelは、シリアライズされたキャッシュオブジェクトをアプリケーションのデータベースに保存するdatabaseキャッシュドライバを使用するように設定されています。
ドライバの前提条件
データベース
databaseキャッシュドライバを使用する場合、キャッシュデータを格納するデータベーステーブルが必要です。通常、これはLaravelのデフォルトの0001_01_01_000001_create_cache_table.php データベースマイグレーションに含まれています。しかし、アプリケーションにこのマイグレーションが含まれていない場合は、make:cache-table Artisanコマンドを使用して作成できます。
1php artisan make:cache-table2 3php artisan migrate
Memcached
Memcachedドライバを使用するには、Memcached PECLパッケージがインストールされている必要があります。すべてのMemcachedサーバは、config/cache.php設定ファイルにリストアップします。このファイルには、すぐに使い始められるようにmemcached.serversエントリがすでに含まれています。
1'memcached' => [ 2 // ... 3 4 'servers' => [ 5 [ 6 'host' => env('MEMCACHED_HOST', '127.0.0.1'), 7 'port' => env('MEMCACHED_PORT', 11211), 8 'weight' => 100, 9 ],10 ],11],
必要であれば、hostオプションにUNIXソケットのパスを設定することもできます。その場合、portオプションは0に設定してください。
1'memcached' => [ 2 // ... 3 4 'servers' => [ 5 [ 6 'host' => '/var/run/memcached/memcached.sock', 7 'port' => 0, 8 'weight' => 100 9 ],10 ],11],
Redis
LaravelでRedisキャッシュを使用する前に、PECL経由でPhpRedis PHP拡張機能をインストールするか、Composer経由でpredis/predisパッケージ(~2.0)をインストールする必要があります。Laravel Sailには、この拡張機能がすでに含まれています。さらに、Laravel CloudやLaravel Forgeなどの公式Laravelアプリケーションプラットフォームには、デフォルトでPhpRedis拡張機能がインストールされています。
Redisの設定に関する詳細は、LaravelドキュメントのRedisのページを参照してください。
DynamoDB
DynamoDBキャッシュドライバを使用する前に、キャッシュされたすべてのデータを保存するためのDynamoDBテーブルを作成する必要があります。通常、このテーブルの名前はcacheとします。しかし、テーブル名はcache設定ファイル内のstores.dynamodb.table設定値に基づいて命名する必要があります。テーブル名はDYNAMODB_CACHE_TABLE環境変数経由でも設定できます。
このテーブルには、アプリケーションのcache設定ファイル内のstores.dynamodb.attributes.key設定項目の値に対応する名前を持つ、文字列のパーティションキーも必要です。デフォルトでは、パーティションキーはkeyという名前にする必要があります。
通常、DynamoDBは期限切れのアイテムをテーブルから積極的に削除しません。したがって、テーブルでTime to Live (TTL)を有効にする必要があります。テーブルのTTL設定を構成する際には、TTL属性名をexpires_atに設定してください。
次に、LaravelアプリケーションがDynamoDBと通信できるように、AWS SDKをインストールします。
1composer require aws/aws-sdk-php
さらに、DynamoDBキャッシュストアの設定オプションに値が提供されていることを確認してください。通常、AWS_ACCESS_KEY_IDやAWS_SECRET_ACCESS_KEYなどのこれらのオプションは、アプリケーションの.env設定ファイルで定義する必要があります。
1'dynamodb' => [2 'driver' => 'dynamodb',3 'key' => env('AWS_ACCESS_KEY_ID'),4 'secret' => env('AWS_SECRET_ACCESS_KEY'),5 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),6 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),7 'endpoint' => env('DYNAMODB_ENDPOINT'),8],
MongoDB
MongoDBを使用している場合、公式のmongodb/laravel-mongodbパッケージによってmongodbキャッシュドライバが提供され、mongodbデータベース接続を使用して設定できます。MongoDBはTTLインデックスをサポートしており、これを使用して期限切れのキャッシュアイテムを自動的にクリアできます。
MongoDBの設定に関する詳細については、MongoDBのキャッシュとロックのドキュメントを参照してください。
キャッシュの使用法
キャッシュインスタンスの取得
キャッシュストアのインスタンスを取得するには、Cacheファサードを使用します。このドキュメント全体でこれを使用します。Cacheファサードは、Laravelのキャッシュ契約の基盤となる実装への便利で簡潔なアクセスを提供します。
1<?php 2 3namespace App\Http\Controllers; 4 5use Illuminate\Support\Facades\Cache; 6 7class UserController extends Controller 8{ 9 /**10 * Show a list of all users of the application.11 */12 public function index(): array13 {14 $value = Cache::get('key');15 16 return [17 // ...18 ];19 }20}
複数のキャッシュストアへのアクセス
Cacheファサードを使用して、storeメソッド経由でさまざまなキャッシュストアにアクセスできます。storeメソッドに渡すキーは、cache設定ファイルのstores設定配列にリストされているストアのいずれかに対応している必要があります。
1$value = Cache::store('file')->get('foo');2 3Cache::store('redis')->put('bar', 'baz', 600); // 10 Minutes
キャッシュからのアイテム取得
Cacheファサードのgetメソッドは、キャッシュからアイテムを取得するために使用します。キャッシュにアイテムが存在しない場合、nullが返されます。必要であれば、getメソッドに2番目の引数として、アイテムが存在しない場合に返したいデフォルト値を指定できます。
1$value = Cache::get('key');2 3$value = Cache::get('key', 'default');
デフォルト値としてクロージャを渡すこともできます。指定したアイテムがキャッシュに存在しない場合、クロージャの結果が返されます。クロージャを渡すことで、データベースや他の外部サービスからのデフォルト値の取得を遅延させることができます。
1$value = Cache::get('key', function () {2 return DB::table(/* ... */)->get();3});
アイテムの存在確認
hasメソッドを使用して、アイテムがキャッシュに存在するかどうかを判断できます。このメソッドは、アイテムが存在してもその値がnullの場合はfalseを返します。
1if (Cache::has('key')) {2 // ...3}
値の増減
incrementメソッドとdecrementメソッドを使用して、キャッシュ内の整数アイテムの値を調整できます。これらのメソッドは両方とも、アイテムの値を増減させる量を指定するオプションの第2引数を受け入れます。
1// Initialize the value if it does not exist...2Cache::add('key', 0, now()->addHours(4));3 4// Increment or decrement the value...5Cache::increment('key');6Cache::increment('key', $amount);7Cache::decrement('key');8Cache::decrement('key', $amount);
取得と保存
キャッシュからアイテムを取得したいが、リクエストされたアイテムが存在しない場合はデフォルト値を保存したい場合があります。たとえば、キャッシュからすべてのユーザーを取得したいが、存在しない場合はデータベースから取得してキャッシュに追加したい場合があります。これはCache::rememberメソッドを使用して行えます。
1$value = Cache::remember('users', $seconds, function () {2 return DB::table('users')->get();3});
アイテムがキャッシュに存在しない場合、rememberメソッドに渡されたクロージャが実行され、その結果がキャッシュに配置されます。
rememberForeverメソッドを使用して、キャッシュからアイテムを取得するか、存在しない場合は永久に保存できます。
1$value = Cache::rememberForever('users', function () {2 return DB::table('users')->get();3});
Stale While Revalidate(再検証中の古いデータの提供)
Cache::rememberメソッドを使用すると、キャッシュされた値が期限切れの場合、一部のユーザーで応答時間が遅くなることがあります。特定の種類のデータでは、キャッシュされた値がバックグラウンドで再計算される間、部分的に古いデータを提供できるようにすると便利な場合があります。これにより、キャッシュされた値が計算されている間に一部のユーザーが応答時間の遅延を経験するのを防ぎます。これはしばしば「stale-while-revalidate」パターンと呼ばれ、Cache::flexibleメソッドはこのパターンの実装を提供します。
flexibleメソッドは、キャッシュされた値が「新鮮(fresh)」と見なされる期間と、「古い(stale)」になる時期を指定する配列を受け入れます。配列の最初の値はキャッシュが新鮮と見なされる秒数を表し、2番目の値は再計算が必要になる前に古いデータとして提供できる期間を定義します。
リクエストが新鮮な期間内(最初の値より前)に行われた場合、キャッシュは再計算なしですぐに返されます。リクエストが古い期間内(2つの値の間)に行われた場合、古い値がユーザーに提供され、応答がユーザーに送信された後にキャッシュ値を更新するための遅延関数が登録されます。リクエストが2番目の値の後にされた場合、キャッシュは期限切れと見なされ、値はすぐに再計算されます。これにより、ユーザーの応答が遅くなる可能性があります。
1$value = Cache::flexible('users', [5, 10], function () {2 return DB::table('users')->get();3});
取得と削除
キャッシュからアイテムを取得してからそのアイテムを削除する必要がある場合は、pullメソッドを使用できます。getメソッドと同様に、アイテムがキャッシュに存在しない場合はnullが返されます。
1$value = Cache::pull('key');2 3$value = Cache::pull('key', 'default');
キャッシュへのアイテム保存
Cacheファサードのputメソッドを使用して、キャッシュにアイテムを保存できます。
1Cache::put('key', 'value', $seconds = 10);
putメソッドに保存時間が渡されない場合、アイテムは無期限に保存されます。
1Cache::put('key', 'value');
秒数を整数で渡す代わりに、キャッシュされたアイテムの希望する有効期限を表すDateTimeインスタンスを渡すこともできます。
1Cache::put('key', 'value', now()->addMinutes(10));
存在しない場合のみ保存
addメソッドは、アイテムがキャッシュストアにまだ存在しない場合にのみ、アイテムをキャッシュに追加します。アイテムが実際にキャッシュに追加された場合、このメソッドはtrueを返します。それ以外の場合、メソッドはfalseを返します。addメソッドはアトミックな操作です。
1Cache::add('key', 'value', $seconds);
アイテムの永久保存
foreverメソッドを使用して、アイテムをキャッシュに永続的に保存できます。これらのアイテムは期限切れにならないため、forgetメソッドを使用して手動でキャッシュから削除する必要があります。
1Cache::forever('key', 'value');
Memcachedドライバを使用している場合、「永久に」保存されたアイテムは、キャッシュがサイズ制限に達したときに削除される可能性があります。
キャッシュからのアイテム削除
forgetメソッドを使用して、キャッシュからアイテムを削除できます。
1Cache::forget('key');
有効期限の秒数にゼロまたは負の数を指定してアイテムを削除することもできます。
1Cache::put('key', 'value', 0);2 3Cache::put('key', 'value', -5);
flushメソッドを使用して、キャッシュ全体をクリアできます。
1Cache::flush();
キャッシュをフラッシュすると、設定したキャッシュの「プレフィックス」は尊重されず、キャッシュからすべてのエントリが削除されます。他のアプリケーションと共有しているキャッシュをクリアする際には、この点を慎重に検討してください。
キャッシュヘルパ
Cacheファサードを使用するのに加え、グローバルなcache関数を使用して、キャッシュを介してデータを取得および保存することもできます。cache関数が単一の文字列引数で呼び出されると、指定されたキーの値を返します。
1$value = cache('key');
キーと値のペアの配列と有効期限を関数に指定すると、指定された期間、キャッシュに値が保存されます。
1cache(['key' => 'value'], $seconds);2 3cache(['key' => 'value'], now()->addMinutes(10));
cache関数が引数なしで呼び出されると、Illuminate\Contracts\Cache\Factory実装のインスタンスを返し、他のキャッシュメソッドを呼び出すことができます。
1cache()->remember('users', $seconds, function () {2 return DB::table('users')->get();3});
グローバルなcache関数への呼び出しをテストする場合、ファサードをテストするのと同じように、Cache::shouldReceiveメソッドを使用できます。
アトミックロック
この機能を利用するには、アプリケーションのデフォルトキャッシュドライバとしてmemcached、redis、dynamodb、database、file、またはarrayキャッシュドライバを使用している必要があります。さらに、すべてのサーバが同じ中央キャッシュサーバと通信している必要があります。
ロックの管理
アトミックロックを使用すると、競合状態を心配することなく分散ロックを操作できます。たとえば、Laravel Cloudはアトミックロックを使用して、サーバ上で一度に1つのリモートタスクしか実行されないようにしています。Cache::lockメソッドを使用してロックを作成および管理できます。
1use Illuminate\Support\Facades\Cache;2 3$lock = Cache::lock('foo', 10);4 5if ($lock->get()) {6 // Lock acquired for 10 seconds...7 8 $lock->release();9}
getメソッドはクロージャも受け入れます。クロージャが実行された後、Laravelは自動的にロックを解放します。
1Cache::lock('foo', 10)->get(function () {2 // Lock acquired for 10 seconds and automatically released...3});
リクエストした瞬間にロックが利用できない場合は、Laravelに指定した秒数待機するように指示できます。指定された時間内にロックが取得できない場合、Illuminate\Contracts\Cache\LockTimeoutExceptionがスローされます。
1use Illuminate\Contracts\Cache\LockTimeoutException; 2 3$lock = Cache::lock('foo', 10); 4 5try { 6 $lock->block(5); 7 8 // Lock acquired after waiting a maximum of 5 seconds... 9} catch (LockTimeoutException $e) {10 // Unable to acquire lock...11} finally {12 $lock->release();13}
上記の例は、blockメソッドにクロージャを渡すことで簡略化できます。このメソッドにクロージャを渡すと、Laravelは指定された秒数ロックを取得しようと試み、クロージャが実行されると自動的にロックを解放します。
1Cache::lock('foo', 10)->block(5, function () {2 // Lock acquired after waiting a maximum of 5 seconds...3});
プロセス間でのロック管理
あるプロセスでロックを取得し、別のプロセスでそれを解放したい場合があります。たとえば、Webリクエスト中にロックを取得し、そのリクエストによってトリガされたキュー投入されたジョブの終了時にロックを解放したい場合があります。このシナリオでは、ロックのスコープ付き「所有者トークン」をキュー投入されたジョブに渡し、そのジョブが指定されたトークンを使用してロックを再インスタンス化できるようにする必要があります。
以下の例では、ロックが正常に取得された場合にキュー投入されたジョブをディスパッチします。さらに、ロックのownerメソッドを介して、ロックの所有者トークンをキュー投入されたジョブに渡します。
1$podcast = Podcast::find($id);2 3$lock = Cache::lock('processing', 120);4 5if ($lock->get()) {6 ProcessPodcast::dispatch($podcast, $lock->owner());7}
アプリケーションのProcessPodcastジョブ内で、所有者トークンを使用してロックを復元し、解放できます。
1Cache::restoreLock('processing', $this->owner)->release();
現在の所有者を尊重せずにロックを解放したい場合は、forceReleaseメソッドを使用できます。
1Cache::lock('processing')->forceRelease();
カスタムキャッシュドライバの追加
ドライバの記述
カスタムキャッシュドライバを作成するには、まずIlluminate\Contracts\Cache\Store 契約を実装する必要があります。したがって、MongoDBキャッシュの実装は次のようになります。
1<?php 2 3namespace App\Extensions; 4 5use Illuminate\Contracts\Cache\Store; 6 7class MongoStore implements Store 8{ 9 public function get($key) {}10 public function many(array $keys) {}11 public function put($key, $value, $seconds) {}12 public function putMany(array $values, $seconds) {}13 public function increment($key, $value = 1) {}14 public function decrement($key, $value = 1) {}15 public function forever($key, $value) {}16 public function forget($key) {}17 public function flush() {}18 public function getPrefix() {}19}
MongoDB接続を使用して、これらの各メソッドを実装するだけです。各メソッドの実装方法の例については、LaravelフレームワークのソースコードにあるIlluminate\Cache\MemcachedStoreを参照してください。実装が完了したら、Cacheファサードのextendメソッドを呼び出して、カスタムドライバの登録を完了します。
1Cache::extend('mongo', function (Application $app) {2 return Cache::repository(new MongoStore);3});
カスタムキャッシュドライバのコードをどこに置くか迷っている場合は、appディレクトリ内にExtensions名前空間を作成できます。ただし、Laravelには厳密なアプリケーション構造はなく、好みに応じてアプリケーションを自由に構成できることを覚えておいてください。
ドライバの登録
カスタムキャッシュドライバをLaravelに登録するには、Cacheファサードのextendメソッドを使用します。他のサービスプロバイダがbootメソッド内でキャッシュされた値を読み取ろうとする可能性があるため、カスタムドライバをbootingコールバック内に登録します。bootingコールバックを使用することで、カスタムドライバがアプリケーションのサービスプロバイダのbootメソッドが呼び出される直前、かつすべてのサービスプロバイダのregisterメソッドが呼び出された後に登録されることを保証します。bootingコールバックは、アプリケーションのApp\Providers\AppServiceProviderクラスのregisterメソッド内に登録します。
1<?php 2 3namespace App\Providers; 4 5use App\Extensions\MongoStore; 6use Illuminate\Contracts\Foundation\Application; 7use Illuminate\Support\Facades\Cache; 8use Illuminate\Support\ServiceProvider; 9 10class AppServiceProvider extends ServiceProvider11{12 /**13 * Register any application services.14 */15 public function register(): void16 {17 $this->app->booting(function () {18 Cache::extend('mongo', function (Application $app) {19 return Cache::repository(new MongoStore);20 });21 });22 }23 24 /**25 * Bootstrap any application services.26 */27 public function boot(): void28 {29 // ...30 }31}
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に設定することで、キャッシュイベントを無効にできます。
1'database' => [2 'driver' => 'database',3 // ...4 'events' => false,5],