運用アーキテクチャ設計
本ドキュメントは、TASHIKAプラットフォームの運用面に関わるアーキテクチャ設計を定義します。 Graceful Shutdown、DBコネクションプーリング、RLS適用タイミングなど、プロダクション運用で不可欠な設計を記載します。
→ 満たす要件: AV-001 可用性、PE-001 パフォーマンス、SR-004 認可要件
Graceful Shutdown 設計
Cloud Run 上で動作するコンテナが SIGTERM を受信した際、データ損失やリクエストエラーを発生させずに安全に停止するためのシーケンスを定義する。
シャットダウンシーケンス
| 順序 | アクション | タイムアウト | 説明 |
|---|---|---|---|
| 1 | SIGTERM 受信 → /health を unhealthy に変更 | 即時 | ロードバランサーが新規リクエストのルーティングを停止 |
| 2 | 新規リクエスト受付停止 | 5秒 | ロードバランサーのヘルスチェック反映を待機 |
| 3 | インフライト HTTP リクエストのドレイン | 30秒 | 処理中のリクエストが完了するまで待機。タイムアウト後は 503 を返却 |
| 4 | 非同期ジョブの中断・チェックポイント保存 | 10秒 | Hangfire ジョブの状態を保存し、再起動後にリジューム可能にする |
| 5 | DB コネクションプールのドレイン・クローズ | 5秒 | アクティブクエリの完了を待ち、ClearAllPools() でプールを破棄 |
| 6 | ログフラッシュ → プロセス終了 | 2秒 | Serilog CloseAndFlushAsync() でバッファされたログを書き出し |
合計最大時間: 52秒(Cloud Run の terminationGracePeriodSeconds: 60 以内)
Cloud Run 設定
| 設定項目 | 値 | 説明 |
|---|---|---|
terminationGracePeriodSeconds | 60 | シャットダウン猶予期間 |
min-instances | 1(通常期)/ 3(繁忙期) | コールドスタート回避 |
max-instances | 10(通常期)/ 30(繁忙期) | スケールアウト上限 |
concurrency | 80 | 1インスタンスあたりの同時リクエスト数 |
cpu-throttling | false | リクエスト外でもCPUを割り当て(バックグラウンドジョブ用) |
長時間オペレーション別の振る舞い
| オペレーション | 通常所要時間 | SIGTERM 時の振る舞い |
|---|---|---|
| PDF 一括生成 | 1〜5分 | 処理済み件数をチェックポイントに保存。再起動後に未処理分から再開 |
| 一括税額計算 | 30秒〜3分 | 計算中のバッチ単位でロールバック。再起動後にバッチ単位で再実行 |
| メール配信バッチ | 1〜10分 | 送信済み件数を記録。再起動後に未送信分から再開(二重送信防止キー付き) |
シーケンス図
DB コネクションプーリング & RLS 適用設計
マルチテナント環境において、コネクションプールとRLS(Row Level Security)を安全かつ効率的に運用するための設計を定義する。
コネクションプール構成
| パラメータ | 通常期 | 繁忙期(1月) | 説明 |
|---|---|---|---|
MinPoolSize | 5 | 10 | 最小プールサイズ |
MaxPoolSize | 50 | 100 | 最大プールサイズ |
ConnectionIdleLifetime | 300秒 | 300秒 | アイドルコネクションの回収間隔 |
ConnectionPruningInterval | 10秒 | 10秒 | プール内コネクションの刈り込み間隔 |
Timeout | 15秒 | 30秒 | コネクション取得タイムアウト |
Keepalive | 60秒 | 60秒 | TCP キープアライブ間隔 |
プール分離: API リクエスト用とバッチジョブ用で別プール(別接続文字列)を使用し、バッチ処理が API の応答性に影響しないようにする。
RLS 適用タイミング
EF Core の DbConnectionInterceptor を使用し、コネクションのライフサイクルに合わせて RLS のテナントコンテキストを設定・クリアする。
C# 擬似コード
csharp
public class TenantConnectionInterceptor : DbConnectionInterceptor
{
private readonly ITenantContext _tenantContext;
public TenantConnectionInterceptor(ITenantContext tenantContext)
{
_tenantContext = tenantContext;
}
public override async Task ConnectionOpenedAsync(
DbConnection connection,
ConnectionEndEventData eventData,
CancellationToken cancellationToken = default)
{
if (_tenantContext.TenantId is { } tenantId)
{
await using var cmd = connection.CreateCommand();
cmd.CommandText = "SET app.current_tenant_id = @tenantId";
var param = cmd.CreateParameter();
param.ParameterName = "tenantId";
param.Value = tenantId.ToString();
cmd.Parameters.Add(param);
await cmd.ExecuteNonQueryAsync(cancellationToken);
}
}
public override async Task ConnectionClosingAsync(
DbConnection connection,
ConnectionEventData eventData,
CancellationToken cancellationToken = default)
{
await using var cmd = connection.CreateCommand();
cmd.CommandText = "RESET ALL";
await cmd.ExecuteNonQueryAsync(cancellationToken);
}
}プール監視メトリクスとアラート閾値
| メトリクス | 説明 | Sev4 閾値 | Sev3 閾値 |
|---|---|---|---|
db.pool.busy_connections | 使用中コネクション数 | MaxPoolSize × 80% | MaxPoolSize × 95% |
db.pool.idle_connections | アイドルコネクション数 | — | 0(全コネクション使用中) |
db.pool.wait_count | プール待ちリクエスト数 | > 5 | > 20 |
db.pool.wait_time_p99 | プール待ち時間 (P99) | > 1秒 | > 5秒 |
db.pool.timeout_count | タイムアウト発生回数 | > 0/min | > 5/min |
長時間クエリ対策
| 設定 | API クエリ | バッチクエリ | 説明 |
|---|---|---|---|
statement_timeout | 10秒 | 300秒 | PostgreSQL セッションレベルで設定 |
CommandTimeout | 10秒 | 300秒 | EF Core / Npgsql レベルで設定 |
lock_timeout | 5秒 | 30秒 | ロック待ちタイムアウト |
idle_in_transaction_session_timeout | 30秒 | 120秒 | トランザクション内アイドルタイムアウト |
スロークエリ監視: log_min_duration_statement = 1000(1秒超のクエリをログ出力)。繁忙期は 500ms に引き下げ。
DBマイグレーション設計
→ 満たす要件: OP-003 データベースマイグレーション
Expand/Contract パターン
破壊的なスキーマ変更は、以下の Expand/Contract パターン で段階的に実施する:
| ステップ | 内容 | 例: カラム名変更 (old_name → new_name) |
|---|---|---|
| 1. Expand(拡張) | 新しいスキーマ要素を追加。既存要素は残す | new_name カラムを追加。トリガーで old_name ↔ new_name を同期 |
| 2. Migrate(移行) | アプリケーションコードを新スキーマに対応 | 新バージョンのアプリが new_name を使用するようデプロイ |
| 3. Contract(収縮) | 旧スキーマ要素を削除 | old_name カラムとトリガーを削除 |
CI 統合の実装方針
- マイグレーションスクリプトは
dotnet ef migrations scriptで生成し、テスト環境への適用で自動検証する - 長時間ロックを伴う操作(大テーブルへのインデックス追加等)は
CONCURRENTLYオプション等で非ブロッキングに実行する
弾力性設計 (Resilience)
→ 満たす要件: AV-003 弾力性
Circuit Breaker パラメーター
| パラメーター | 値 | 説明 |
|---|---|---|
| Open 判定 | エラー率 ≥ 50%(最低 10 リクエスト / 30秒ウィンドウ) | この条件を超えると Closed → Open に遷移 |
| Half-Open 試行 | Open 後 60秒経過 | 1リクエストを試行し、成否を判定 |
| Closed 復旧 | Half-Open で成功率 ≥ 80%(5リクエスト) | 正常と判断し通常運転へ復帰 |
状態遷移図
Retry 実装方針
- バックオフ戦略: Exponential Backoff + Jitter(1秒 → 2秒 → 4秒 + ランダム揺らぎ)