mcpField
L
laravel-caching-strategies

laravel-caching-strategies

github.com/iserter開発4893 回インストール27 週間前に更新
リポジトリ

laravel-caching-strategies について

Laravel 開発者向けに、アプリケーションデータやクエリのキャッシュパターンを解説するスキルです。有効期限、タグによる無効化、ロック、メモ化、キー規則、注意点を扱います。

主な機能

  1. 01期限付きまたは無期限のキャッシュ
  2. 02タグによる関連エントリーの無効化
  3. 03排他的処理のためのアトミックロック
  4. 04リクエスト中のデータのメモ化
  5. 05一貫したキャッシュキー規則
  6. 06null 値とキャッシュ上の注意点への対処

活用シーン

  1. 01コストの高いデータベースクエリのキャッシュ
  2. 02データ変更後の関連キャッシュの削除
  3. 03同じ処理の同時実行の防止
  4. 04リクエスト中のキャッシュ参照の重複削減

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/iserter/laravel-claude-agents --skill laravel-caching-strategies

インストール後、Claude Code で/laravel-caching-strategiesを入力して読み込まれたか確認します。

詳細

Laravel のキャッシュ戦略

基本的なキャッシュパターン

use Illuminate\Support\Facades\Cache;

// ✅ remember - cache for a duration
$users = Cache::remember('users:active', now()->addMinutes(30), function () {
    return User::where('active', true)->get();
});

// ✅ rememberForever - cache until manually cleared
$settings = Cache::rememberForever('app:settings', function () {
    return Setting::all()->pluck('value', 'key');
});

// ✅ flexible - stale-while-revalidate pattern
// Fresh for 5 min, serves stale for up to 15 min while revalidating in background
$stats = Cache::flexible('dashboard:stats', [300, 900], function () {
    return DashboardStats::calculate();
});

// ✅ put / get / forget
Cache::put('key', $value, now()->addHours(1));
$value = Cache::get('key', 'default');
Cache::forget('key');

// ❌ Querying the database when cache would suffice
$settings = Setting::all(); // Every request hits the DB

グループ単位で無効化するためのキャッシュタグ

// ✅ Tag related cache entries
Cache::tags(['posts', 'users'])->put("user:{$userId}:posts", $posts, 3600);
Cache::tags(['posts'])->put("post:{$postId}", $post, 3600);
Cache::tags(['users'])->put("user:{$userId}:profile", $profile, 3600);

// ✅ Flush all caches for a tag group
Cache::tags(['posts'])->flush(); // Clears all post-related caches

// ✅ Retrieve tagged cache
$posts = Cache::tags(['posts', 'users'])->get("user:{$userId}:posts");

// Note: Cache tags are only supported by redis and memcached drivers

アトミックロック

use Illuminate\Support\Facades\Cache;

// ✅ Prevent concurrent execution
$lock = Cache::lock('processing:order:' . $orderId, 10); // 10 second lock

if ($lock->get()) {
    try {
        // Process order exclusively
        $this->processOrder($orderId);
    } finally {
        $lock->release();
    }
}

// ✅ Block and wait for lock (up to 5 seconds)
$lock = Cache::lock('report:generate', 30);

$lock->block(5, function () {
    // Acquired lock, do work
    $this->generateReport();
}); // Lock auto-released after closure

// ✅ Cross-process lock with owner token
$lock = Cache::lock('deployment', 120);

if ($lock->get()) {
    $owner = $lock->owner();
    // Pass $owner to another process
}

// In the other process
Cache::restoreLock('deployment', $owner)->release();

// ❌ Forgetting to release locks
$lock->get();
$this->doWork(); // If this throws, lock is never released

キャッシュのメモ化

use Illuminate\Support\Facades\Cache;

// ✅ memo - in-memory cache for the current request lifecycle
// Avoids repeated cache store lookups within the same request
$config = Cache::memo('app:config', function () {
    return Config::loadFromDatabase();
});

// Subsequent calls return the in-memory value without hitting Redis/Memcached
$config = Cache::memo('app:config', fn () => Config::loadFromDatabase());

モデルのキャッシュパターン

class Product extends Model
{
    // ✅ Cache on read with automatic invalidation
    public static function findCached(int $id): ?self
    {
        return Cache::remember(
            "product:{$id}",
            now()->addHour(),
            fn () => static::find($id)
        );
    }

    // ✅ Invalidate cache on model changes
    protected static function booted(): void
    {
        static::saved(function (Product $product) {
            Cache::forget("product:{$product->id}");
            Cache::tags(['products'])->flush();
        });

        static::deleted(function (Product $product) {
            Cache::forget("product:{$product->id}");
            Cache::tags(['products'])->flush();
        });
    }
}

// ✅ Cache query results with tags for group invalidation
class ProductService
{
    public function getFeatured(): Collection
    {
        return Cache::tags(['products'])->remember(
            'products:featured',
            now()->addMinutes(30),
            fn () => Product::where('featured', true)->with('category')->get()
        );
    }
}

キャッシュキーの規則

// ✅ Use consistent, descriptive key patterns
"user:{$userId}:profile"
"post:{$postId}:comments:page:{$page}"
"tenant:{$tenantId}:settings"
"api:github:repos:{$username}"
"report:daily:{$date}"

// ✅ Include cache-busting identifiers when data shape changes
"v2:user:{$userId}:profile"

// ❌ Vague or collision-prone keys
"data"
"user"
"temp"
"cache_1"

よくある落とし穴

// ❌ Caching null results without handling them
$user = Cache::remember("user:{$id}", 3600, fn () => User::find($id));
// If user doesn't exist, null is cached for an hour

// ✅ Handle null explicitly
$user = Cache::remember("user:{$id}", 3600, function () use ($id) {
    return User::find($id) ?? new NullUser();
});

// ❌ No invalidation strategy
Cache::forever('products:all', Product::all());
// Data becomes stale with no way to refresh

// ✅ Use TTL or event-based invalidation
Cache::remember('products:all', now()->addMinutes(15), fn () => Product::all());

// ❌ Caching too aggressively (serialization cost > query cost)
Cache::remember('user:count', 3600, fn () => User::count());
// Simple COUNT queries are often fast enough without caching

// ✅ Cache expensive operations
Cache::remember('dashboard:analytics', 3600, function () {
    return DB::table('orders')
        ->selectRaw('DATE(created_at) as date, SUM(total) as revenue')
        ->groupByRaw('DATE(created_at)')
        ->orderBy('date')
        ->get();
});

// ❌ Cache stampede - many requests regenerate cache simultaneously
// ✅ Use flexible() for stale-while-revalidate or atomic locks for regeneration

チェックリスト

  • 手動で get/put を使う代わりに、remember/rememberForever を使用している
  • 短時間の古いデータが許容される高トラフィックのキーには、flexible() を使用している
  • グループ単位で無効化するためにキャッシュタグを使用している(Redis/Memcached のみ)
  • 排他的な操作にアトミックロックを使用している
  • モデルのキャッシュを保存/削除イベントで無効化している
  • キャッシュキーが一貫した命名規則に従っている
  • TTL が適切に設定されている(長すぎず、短すぎない)
  • 何もキャッシュしない事態を避けるため、null/空の結果を処理している
  • 高コストのクエリはキャッシュし、単純なクエリはキャッシュしていない
  • flexible() またはロックでキャッシュスタンピードを防いでいる

コメント