LaravelとRedisでキャッシュを実装してみた|Cache Asideパターンとデータ不整合を検証

DB・Redisの記事のアイキャッチ Laravel

はじめに

今月は「知見のない技術を学ぼう月間」ということで、前回に引き続き、Redisを学習していきます。前回の記事がこちらになります。

Redisとは?Dockerで起動して基本コマンドを試してみた【超入門】
Redisとは何かを初心者向けに解説します。 DockerでRedisを起動し、redis-cliを使ってSET・GET・EXPIRE・TTLなどの基本コマンドや、String・List・Hash型の操作を実際に試してみました。

前回はRedisの超基礎的な内容を学習しました。今回はより実践的にRedisを使えるように、Laravelアプリと連携して使ってみました。

私が使用した環境については以下の通りです。

Composer version 2.10.3 2026-08-27 13:34:23
PHP version 8.3.35 (/usr/local/bin/php)
Laravel Framework 13.37.0

ディレクトリ構成

redis-lab/
├── docker-compose.yml
├── .env                  # Docker Compose用の環境変数
├── .gitignore
├── README.md
├── docker-config/
│   ├── nginx/
│   │   ├── Dockerfile
│   │   └── default.conf
│   ├── php/
│   │   ├── Dockerfile
│   │   └── php.ini
│   ├── mariadb/
│   │   └── data/         # MariaDBの永続化データ
│   └── redis/
│       └── data/         # Redisの永続化データ
└── src/                  # Laravelアプリケーション
    ├── app/
    ├── bootstrap/
    ├── config/
    ├── database/
    ├── public/
    ├── resources/
    ├── routes/
    ├── storage/
    ├── tests/
    ├── .env              # Laravel用の環境変数
    ├── artisan
    ├── composer.json
    └── composer.lock

上記の環境をdockerで構築していきます。学習用に作成したプロジェクトもGitHubで公開していますので、こちらを参考にしてください。

GitHub – yu-corder/redis-lab: A Docker-based Laravel development environment with Redis and MariaDB for learning and experimenting with caching.
A Docker-based Laravel development environment with Redis and MariaDB for learning and experimenting with caching. – yu-…

環境構築

docker や Laravel のコマンド等については深く触れませんので、ご容赦ください。

docker-compose.yml になります。※学習のための、ローカル開発環境用です。

services:
  web:
    build: ./docker-config/php
    container_name: redis-php
    volumes:
      - ./src:/var/www/
    depends_on:
      - mariadb

  nginx:
    ports:
      - "80:80"
    build: ./docker-config/nginx
    container_name: redis-nginx
    volumes:
      - ./src:/var/www
      - ./docker-config/nginx/default.conf:/etc/nginx/http.d/default.conf
    depends_on:
      - web

  mariadb:
    image: mariadb
    container_name: redis-mariadb
    environment:
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
      TZ: ${TZ}
    volumes:
      - ./docker-config/mariadb/data:/var/lib/mysql

  redis:
    image: "redis:7.4"
    volumes:
      - "./docker-config/redis/data:/data"

docker-configディレクトリ以下にDockerfileを配置します。

redis-lab/
├── docker-compose.yml
├── .env                  # Docker Compose用の環境変数
├── .gitignore
├── README.md
├── docker-config/
│   ├── nginx/
│   │   ├── Dockerfile
│   │   └── default.conf
│   ├── php/
│   │   ├── Dockerfile
│   │   └── php.ini
│   ├── mariadb/
│   │   └── data/         # MariaDBの永続化データ
│   └── redis/
│       └── data/         # Redisの永続化データ
└── src/                  # Laravelアプリケーション
    ├── app/
    ├── bootstrap/
    ├── config/
    ├── database/
    ├── public/
    ├── resources/
    ├── routes/
    ├── storage/
    ├── tests/
    ├── .env              # Laravel用の環境変数
    ├── artisan
    ├── composer.json
    └── composer.lock

php/Dockerfile ですが、PhpRedis拡張のインストールをします。また、docker-php-ext-enable redis でPECLを経由してインストールしたPHP拡張モジュールを有効化します。

 FROM php:8.3-fpm
 WORKDIR /var/www
 ADD . /var/www
 
 RUN chown -R www-data:www-data /var/www
 
 RUN cd /usr/bin && curl -fsSL https://getcomposer.org/installer | php && ln -s /usr/bin/composer.phar /usr/bin/composer
 
 RUN apt-get update \
   && apt-get install -y \
   gcc \
   make \
   git \
   unzip \
   vim \
   libpng-dev \
   libjpeg-dev \
   libfreetype6-dev \
   libmcrypt-dev \
   libpq-dev \
   curl \
   gnupg \
   openssl \
   && docker-php-ext-install pdo_mysql mysqli \
   && docker-php-ext-configure gd --with-freetype --with-jpeg \
   && docker-php-ext-install -j$(nproc) gd \
   && apt-get clean \
   && rm -rf /var/lib/apt/lists/*

 # Install PhpRedis extension
 RUN pecl install redis \
   && docker-php-ext-enable redis
 
 
 COPY php.ini /usr/local/etc/php/
 
 
 RUN curl -sL https://deb.nodesource.com/setup_18.x | bash - \
   && apt-get update \
   && apt-get install -y nodejs \
   && apt-get clean \
   && rm -rf /var/lib/apt/lists/* 
 
 RUN npm install -g n \
   && n stable \
   && npm update -g npm

nginx/Dockerfile です。

 FROM alpine:3.22
 RUN apk update && \
     apk add --no-cache nginx
 RUN mkdir -p /run/nginx
 CMD nginx -g "daemon off;"

こちらの内容でビルドと起動をします。

test@test redis-lab % docker compose up -d
...省略

test@test redis-lab % docker ps
CONTAINER ID   IMAGE             COMMAND                  CREATED         STATUS         PORTS                    NAMES
872647ae3b34   redis-lab_nginx   "/bin/sh -c 'nginx -…"   4 minutes ago   Up 4 minutes   0.0.0.0:80->80/tcp                         redis-nginx
1e74b78c7ec1   redis-lab_web     "docker-php-entrypoi…"   4 minutes ago   Up 4 minutes   9000/tcp                 redis-php
a464a43d5349   redis:7.4         "docker-entrypoint.s…"   4 minutes ago   Up 4 minutes   6379/tcp   redis-lab_redis_1
237a4ec54b27   mariadb           "docker-entrypoint.s…"   4 minutes ago   Up 4 minutes   3306/tcp                 redis-mariadb

コンテナの起動には成功しています。続いてLaravelプロジェクトを作成します。

test@test redis-lab % docker-compose exec web composer create-project laravel/laravel .

//コンテナに入ってます。
root@1e74b78c7ec1:/var/www# php artisan --version
Laravel Framework 13.37.0

webコンテナ(Laravelアプリが動いているコンテナ)からRedis や DB に接続できるように.envファイル(srcディレクトリ直下の.envファイル)に追加をします。※envファイルはシステムの機密情報が盛りだくさんなので、リポジトリやブログに載せないようにしましょう。以下はローカル開発環境の.envということで、ブログに載せています。

REDIS_CLIENT=phpredis
REDIS_HOST=redis
REDIS_PORT=6379

CACHE_STORE=redis

DB_CONNECTION=mysql
DB_HOST=mariadb
DB_PORT=3306
DB_DATABASE=development
DB_USERNAME=root
DB_PASSWORD=root

次にマイグレーションを実行します。

test@test redis-lab % docker-compose exec web php artisan migrate

これで、mariadb に初期テーブルが作成されました。

次にwebコンテナからRedisに書き込めるか確認します。

test@test redis-lab % docker-compose exec web php artisan tinker      
Psy Shell v0.12.24 (PHP 8.3.35 — cli) by Justin Hileman
New PHP manual is available (latest: 3.1.4). Update with `doc --update-manual`
> use Illuminate\Support\Facades\Cache;

Cache::put('test_key', 'Hello Redis', 300);
Cache::get('test_key');

= "Hello Redis"

> 

//別コンソール
test@test redis-lab % docker-compose exec redis redis-cli
127.0.0.1:6379> SCAN 0 MATCH '*test_key*' COUNT 100
1) "0"
2) (empty array)
127.0.0.1:6379> SCAN 0 MATCH '*test_key*' COUNT 100
1) "0"
2) (empty array)
127.0.0.1:6379> SCAN 0 MATCH '*test_key*' COUNT 100
1) "0"
2) (empty array)

webコンテナで登録したキーが見つかりませんでした。Laravelの標準設定では、Redisのキャッシュ用接続にDB番号 1が使われことがあるみたいです。

> config('database.redis.cache.database');

= "1"

そのため、 Redis側で以下を実行します。

SELECT 1

再度キーを探して見つけることができました。

127.0.0.1:6379[1]> SCAN 0 MATCH '*test_key*' COUNT 100
1) "0"
2) 1) "laravel-database-laravel-cache-test_key"
127.0.0.1:6379[1]> TTL laravel-database-laravel-cache-test_key
(integer) 206
127.0.0.1:6379[1]> GET laravel-database-laravel-cache-test_key
"s:11:\"Hello Redis\";"
127.0.0.1:6379[1]> 

Redisをキャッシュとして利用してみる

実際にRedisをキャッシュとして利用してみます。商品テーブルを新たに追加します。商品テーブルのカラムは以下です。詳細はリポジトリのマイグレーションファイルを参照してください。

$table->id();
$table->string('name');
$table->integer('price');
$table->timestamps();
docker-compose exec web php artisan make:model Product -m
docker-compose exec web php artisan migrate
docker-compose exec web php artisan make:controller ProductController

コントローラ と ルーティングを修正します。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use App\Models\Product;


class ProductController extends Controller
{
    public function show(int $id)
    {
        $key = "product:{$id}";

        DB::enableQueryLog();

        $product = Cache::remember($key, 60, function () use ($id) {
            return Product::findOrFail($id)->toArray();
        });

        return response()->json([
            'product' => $product,
            'queries' => DB::getQueryLog(),
        ]);
    }
}
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ProductController;

Route::get('/', function () {
    return view('welcome');
});

Route::get('/products/{id}', [ProductController::class, 'show']);

サンプルデータを作成します。

test@test redis-lab % docker-compose exec web php artisan tinker
Psy Shell v0.12.24 (PHP 8.3.35 — cli) by Justin Hileman
New PHP manual is available (latest: 3.1.4). Update with `doc --update-manual`
> use App\Models\Product;

$product = new Product();
$product->name = 'MacBook Pro';
$product->price = 250000;
$product->save();

= true

> Product::all();

= Illuminate\Database\Eloquent\Collection {#7510
    all: [
      App\Models\Product {#7509
        id: 1,
        name: "MacBook Pro",
        price: 250000,
        created_at: "2026-10-17 08:01:19",
        updated_at: "2026-10-17 08:01:19",
      },
    ],
  }

> quit

ブラウザから確認してみます。キャッシュがない場合のレスポンスは以下のようになります。

{"product":{"id":1,"name":"MacBook Pro","price":250000,"created_at":"2026-10-17T08:01:19.000000Z","updated_at":"2026-10-17T08:01:19.000000Z"},"queries":[{"query":"select * from `products` where `products`.`id` = ? limit 1","bindings":[1],"time":2.16,"readWriteType":"write"}]}

コントローラで、クエリログを返すようにしてます。

return response()->json([
    'product' => $product,
    'queries' => DB::getQueryLog(),
]);

次にキャッシュが残っている場合のレスポンスです。

{"product":{"id":1,"name":"MacBook Pro","price":250000,"created_at":"2026-10-17T08:01:19.000000Z","updated_at":"2026-10-17T08:01:19.000000Z"},"queries":[]}

Cache::remember() がRedisに保存された商品データを返し、MariaDBへの問い合わせを省略できています。

項目1回目(キャッシュなし)2回目(キャッシュあり)
Redisのキャッシュなしあり
MariaDBへのSELECT実行実行されない
商品データMariaDBから取得Redisから取得
キャッシュへの保存ありなし

ここまでで、Cache Asideパターンの基本的な動作を確認できました。

もう少し踏み込んで検証してみます。次にDBのデータを変更した時のキャッシュがどうなるか確認してみます。キャッシュの有効期限を一時的に300秒にします。

$product = Cache::remember($key, 300, function () use ($id) {
     return Product::findOrFail($id)->toArray();
 });

Cache::remember() の有効期限を60秒から300秒に変更しても、すでにRedisに存在するキャッシュのTTLが300秒に延長されるわけではありません。

新しいTTLが適用されるのは、キャッシュが存在せず、新たに保存されたときです。

ブラウザから接続して、キャッシュを作り直します。

{"product":{"id":1,"name":"MacBook Pro","price":250000,"created_at":"2026-10-17T08:01:19.000000Z","updated_at":"2026-10-17T08:01:19.000000Z"},"queries":[{"query":"select * from `products` where `products`.`id` = ? limit 1","bindings":[1],"time":2.16,"readWriteType":"write"}]}

商品テーブルの価格を変更します。

test@test redis-lab % docker-compose exec web php artisan tinker
Psy Shell v0.12.24 (PHP 8.3.35 — cli) by Justin Hileman
New PHP manual is available (latest: 3.1.4). Update with `doc --update-manual`

> use App\Models\Product;

Product::where('id', 1)->update(['price' => 200000]);

= 1

> Product::find(1)->price;

= 200000

> 

再度ブラウザからアクセスします。

{"product":{"id":1,"name":"MacBook Pro","price":250000,"created_at":"2026-10-17T08:01:19.000000Z","updated_at":"2026-10-17T08:01:19.000000Z"},"queries":[]}

Redisにキャッシュされた古い価格 250000 が返ってきました。キャッシュを削除して、再度ブラウザからアクセスします。

Cache::forget('product:1');

レスポンス:

{"product":{"id":1,"name":"MacBook Pro","price":200000,"created_at":"2026-10-17T08:01:19.000000Z","updated_at":"2026-10-17T09:16:50.000000Z"},"queries":[{"query":"select * from `products` where `products`.`id` = ? limit 1","bindings":[1],"time":3.18,"readWriteType":"write"}]}

今回は手動で、Cache::forget() を実行しましたが、実際のアプリでは、商品データの更新処理が成功したタイミングで、対応するキャッシュを削除する設計が一般的です。

Cache-Aside パターン – Azure Architecture Center
オンデマンドでデータをキャッシュに読み込む方法について説明します。これにより、パフォーマンスが向上し、キャッシュされたデータと基になるデータ ストア間で整合性を維持できます。

最後に

今回はLaravelとRedisを連携させ、MariaDBの商品データをキャッシュする処理を実装してみました。

実際にSQLログを確認することで、キャッシュが存在する場合はDBへの問い合わせが省略されることを確認できました。一方で、DBのデータを更新してもキャッシュには古いデータが残るため、データの整合性を考慮する必要があることも学びました。

Redisをキャッシュとして利用する際は、単にデータを保存するだけでなく、キャッシュの有効期限や削除するタイミングも重要だと感じました。

最後まで読んでいただき、ありがとうございました。

タイトルとURLをコピーしました