コンテンツにスキップ

スケーラビリティ設計

概要

EC-Spokeシステムは、Laravel標準機能を活用してスケーラビリティを確保する。以下の4つの観点から設計する:

  1. アプリケーション水平分散
  2. リードレプリカ
  3. キャッシュ階層化
  4. CQRS的読み分離

1. アプリケーション水平分散

概要

複数のアプリケーションインスタンスを並列に配置し、負荷を分散させる。Laravelはステートレス設計のため、セッションをデータベースに保存することで水平分散に対応可能。

実現方法

セッションをデータベースに保存:

// config/session.php
'driver' => env('SESSION_DRIVER', 'database'),

環境変数設定:

SESSION_DRIVER=database

マイグレーション実行:

php artisan session:table
php artisan migrate

メリット

  • 複数のアプリケーションインスタンス間でセッションデータを共有可能
  • ロードバランサー経由でリクエストを分散可能
  • インスタンスの追加・削除が容易
  • 単一インスタンスの障害が全体に影響しない

注意点

  • セッションテーブルへのアクセスがボトルネックになる可能性があるため、適切なインデックス設定が必要
  • セッションデータのクリーンアップを定期的に実行する必要がある(php artisan session:gc

インフラ構成例

ロードバランサー(ALB)
    ├── アプリケーションインスタンス1(ECS Fargate/EC2)
    ├── アプリケーションインスタンス2(ECS Fargate/EC2)
    └── アプリケーションインスタンスN(ECS Fargate/EC2)
    データベース(RDS MySQL)

2. リードレプリカ

概要

データベースの読み取り専用レプリカを配置し、読み取りクエリを分散させる。Laravel標準機能でリードレプリカの設定が可能。

実現方法

データベース設定:

// config/database.php
'mysql' => [
    'read' => [
        'host' => [
            env('DB_READ_HOST_1', '192.168.1.1'),
            env('DB_READ_HOST_2', '192.168.1.2'),
        ],
    ],
    'write' => [
        'host' => [
            env('DB_WRITE_HOST', '192.168.1.3'),
        ],
    ],
    'port' => env('DB_PORT', '3306'),
    'database' => env('DB_DATABASE', 'laravel'),
    'username' => env('DB_USERNAME', 'root'),
    'password' => env('DB_PASSWORD', ''),
    'charset' => env('DB_CHARSET', 'utf8mb4'),
    'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
],

環境変数設定:

DB_READ_HOST_1=read-replica-1.example.com
DB_READ_HOST_2=read-replica-2.example.com
DB_WRITE_HOST=master.example.com

動作

  • 読み取りクエリ: SELECT文は自動的にread接続から選択されたホストに送信される
  • 書き込みクエリ: INSERTUPDATEDELETE文は自動的にwrite接続に送信される
  • トランザクション: トランザクション内のクエリはすべてwrite接続を使用

メリット

  • 読み取り負荷を複数のレプリカに分散可能
  • マスターデータベースの負荷を軽減
  • 読み取り専用レプリカの追加が容易
  • レプリカの障害が全体に影響しない(自動的に他のレプリカにフォールバック)

注意点

  • レプリケーション遅延(レプリケーションラグ)を考慮する必要がある
  • 読み取り直後の書き込みで、最新データが反映されていない可能性がある
  • トランザクション内では常にマスターデータベースを使用する

インフラ構成例

アプリケーション
    ├── 読み取りクエリ → リードレプリカ1(RDS Read Replica)
    ├── 読み取りクエリ → リードレプリカ2(RDS Read Replica)
    └── 書き込みクエリ → マスターデータベース(RDS Primary)
        レプリケーション

3. キャッシュ階層化

概要

複数のキャッシュストアを階層的に配置し、用途に応じて最適なキャッシュを使用する。Laravel標準機能で複数のキャッシュストアを設定可能。

実現方法

キャッシュ設定:

// config/cache.php
'default' => env('CACHE_DRIVER', 'redis'),

'stores' => [
    'redis' => [
        'driver' => 'redis',
        'connection' => 'cache',
    ],

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

    'file' => [
        'driver' => 'file',
        'path' => storage_path('framework/cache/data'),
    ],

    'array' => [
        'driver' => 'array',
    ],
],

使用例:

// デフォルトキャッシュ(Redis)
Cache::put('key', 'value', 3600);

// 特定のキャッシュストアを指定
Cache::store('memcached')->put('key', 'value', 3600);
Cache::store('file')->put('key', 'value', 3600);

キャッシュ階層の使い分け

キャッシュストア 用途 特徴
Redis セッション、頻繁にアクセスするデータ 高速、永続化可能、データ構造が豊富
Memcached 一時的なデータ、大量のデータ 高速、メモリ効率が良い
ファイル 開発環境、小規模データ シンプル、永続化
配列 テスト環境 メモリ内のみ、リクエスト終了時に破棄

メリット

  • 用途に応じて最適なキャッシュストアを選択可能
  • 複数のキャッシュストアを併用してパフォーマンスを最適化
  • キャッシュストアの障害時にもフォールバック可能

注意点

  • キャッシュの無効化(キャッシュバスティング)を適切に実装する必要がある
  • キャッシュの有効期限を適切に設定する必要がある
  • キャッシュストアのメモリ使用量を監視する必要がある

インフラ構成例

アプリケーション
    ├── Redis(ElastiCache)→ セッション、頻繁にアクセスするデータ
    ├── Memcached(ElastiCache)→ 一時的なデータ
    └── ファイルキャッシュ → 開発環境、小規模データ

4. CQRS的読み分離

概要

読み取り(Query)と書き込み(Command)を分離し、それぞれに最適なデータソースを使用する。Laravel標準機能でデータベース接続の読み書き分離が可能。

実現方法

データベース接続の読み書き分離:

リードレプリカの設定(上記「2. リードレプリカ」参照)により、自動的に読み書きが分離される。

明示的な読み書き分離:

// 読み取り専用接続を明示的に指定
$users = DB::connection('mysql')->read()->table('users')->get();

// 書き込み専用接続を明示的に指定
DB::connection('mysql')->write()->table('users')->insert([...]);

モデルでの読み書き分離:

// 読み取り専用モデル
class ReadOnlyUser extends Model
{
    protected $connection = 'mysql_read';

    public function newQuery()
    {
        return parent::newQuery()->useReadPdo();
    }
}

CQRSパターンの実装

コマンド(書き込み):

// app/Domains/Order/Actions/CreateOrderAction.php
class CreateOrderAction
{
    public function execute(array $data): Order
    {
        return DB::transaction(function () use ($data) {
            // 書き込みは自動的にマスターデータベースに送信される
            return Order::create($data);
        });
    }
}

クエリ(読み取り):

// app/Domains/Order/Services/OrderQueryService.php
class OrderQueryService
{
    public function getOrder(int $id): ?Order
    {
        // 読み取りは自動的にリードレプリカに送信される
        return Order::find($id);
    }

    public function searchOrders(array $filters): Collection
    {
        // 読み取りは自動的にリードレプリカに送信される
        return Order::where($filters)->get();
    }
}

メリット

  • 読み取りと書き込みを最適化されたデータソースに分離可能
  • 読み取り専用の最適化(インデックス、パーティショニング等)が可能
  • 書き込み負荷が読み取りに影響しない
  • 将来的に読み取り専用の検索エンジン(Meilisearch/Elasticsearch)への移行が容易

注意点

  • レプリケーション遅延を考慮する必要がある
  • 読み取り直後の書き込みで、最新データが反映されていない可能性がある
  • トランザクション内では常にマスターデータベースを使用する

将来の拡張

  • 検索エンジンへの移行: 読み取り専用の検索エンジン(Meilisearch/Elasticsearch)を使用して、より高速な検索を実現
  • イベントソーシング: 書き込みをイベントとして記録し、読み取りはイベントストアから再構築

インフラ構成例

アプリケーション
    ├── コマンド(書き込み)→ マスターデータベース(RDS Primary)
    │                           ↓
    │                       レプリケーション
    │                           ↓
    └── クエリ(読み取り)→ リードレプリカ(RDS Read Replica)
                            ↓(将来)
                        検索エンジン(Meilisearch/Elasticsearch)

まとめ

EC-Spokeシステムのスケーラビリティは、Laravel標準機能を活用して実現する:

  1. アプリケーション水平分散: セッションをデータベースに保存することで実現
  2. リードレプリカ: Laravel標準機能で自動的に読み書きを分離
  3. キャッシュ階層化: 複数のキャッシュストアを設定可能
  4. CQRS的読み分離: リードレプリカ設定により自動的に実現

これらすべてがLaravel標準機能で実現可能であり、追加のパッケージや複雑な実装は不要である。