スケーラビリティ設計
概要
EC-Spokeシステムは、Laravel標準機能を活用してスケーラビリティを確保する。以下の4つの観点から設計する:
- アプリケーション水平分散
- リードレプリカ
- キャッシュ階層化
- CQRS的読み分離
1. アプリケーション水平分散
概要
複数のアプリケーションインスタンスを並列に配置し、負荷を分散させる。Laravelはステートレス設計のため、セッションをデータベースに保存することで水平分散に対応可能。
実現方法
セッションをデータベースに保存:
環境変数設定:
マイグレーション実行:
メリット
- 複数のアプリケーションインスタンス間でセッションデータを共有可能
- ロードバランサー経由でリクエストを分散可能
- インスタンスの追加・削除が容易
- 単一インスタンスの障害が全体に影響しない
注意点
- セッションテーブルへのアクセスがボトルネックになる可能性があるため、適切なインデックス設定が必要
- セッションデータのクリーンアップを定期的に実行する必要がある(
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接続から選択されたホストに送信される - 書き込みクエリ:
INSERT、UPDATE、DELETE文は自動的に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標準機能を活用して実現する:
- アプリケーション水平分散: セッションをデータベースに保存することで実現
- リードレプリカ: Laravel標準機能で自動的に読み書きを分離
- キャッシュ階層化: 複数のキャッシュストアを設定可能
- CQRS的読み分離: リードレプリカ設定により自動的に実現
これらすべてがLaravel標準機能で実現可能であり、追加のパッケージや複雑な実装は不要である。