Skip to content

運用アーキテクチャ設計

本ドキュメントは、TASHIKAプラットフォームの運用面に関わるアーキテクチャ設計を定義します。 Graceful Shutdown、DBコネクションプーリング、RLS適用タイミングなど、プロダクション運用で不可欠な設計を記載します。

→ 満たす要件: AV-001 可用性PE-001 パフォーマンスSR-004 認可要件


Graceful Shutdown 設計

Cloud Run 上で動作するコンテナが SIGTERM を受信した際、データ損失やリクエストエラーを発生させずに安全に停止するためのシーケンスを定義する。

シャットダウンシーケンス

順序アクションタイムアウト説明
1SIGTERM 受信 → /healthunhealthy に変更即時ロードバランサーが新規リクエストのルーティングを停止
2新規リクエスト受付停止5秒ロードバランサーのヘルスチェック反映を待機
3インフライト HTTP リクエストのドレイン30秒処理中のリクエストが完了するまで待機。タイムアウト後は 503 を返却
4非同期ジョブの中断・チェックポイント保存10秒Hangfire ジョブの状態を保存し、再起動後にリジューム可能にする
5DB コネクションプールのドレイン・クローズ5秒アクティブクエリの完了を待ち、ClearAllPools() でプールを破棄
6ログフラッシュ → プロセス終了2秒Serilog CloseAndFlushAsync() でバッファされたログを書き出し

合計最大時間: 52秒(Cloud Run の terminationGracePeriodSeconds: 60 以内)

Cloud Run 設定

設定項目説明
terminationGracePeriodSeconds60シャットダウン猶予期間
min-instances1(通常期)/ 3(繁忙期)コールドスタート回避
max-instances10(通常期)/ 30(繁忙期)スケールアウト上限
concurrency801インスタンスあたりの同時リクエスト数
cpu-throttlingfalseリクエスト外でもCPUを割り当て(バックグラウンドジョブ用)

長時間オペレーション別の振る舞い

オペレーション通常所要時間SIGTERM 時の振る舞い
PDF 一括生成1〜5分処理済み件数をチェックポイントに保存。再起動後に未処理分から再開
一括税額計算30秒〜3分計算中のバッチ単位でロールバック。再起動後にバッチ単位で再実行
メール配信バッチ1〜10分送信済み件数を記録。再起動後に未送信分から再開(二重送信防止キー付き)

シーケンス図

SIGTERM/health → unhealthy新規リクエスト停止インフライト HTTP ドレイン (最大30秒)ジョブ中断・チェックポイント保存 (最大10秒)コネクションプール ドレイン (最大5秒)アクティブクエリ完了待ちClearAllPools()CloseAndFlushAsync() (最大2秒)フラッシュ完了プロセス終了ヘルスチェック反映待機 (5秒)タイムアウト後は 503 返却再起動後にリジューム可能合計最大 52秒 < 60秒Cloud RunロードバランサーApplicationHangfire ServerDB コネクションプールSerilog
SIGTERM/health → unhealthy新規リクエスト停止インフライト HTTP ドレイン (最大30秒)ジョブ中断・チェックポイント保存 (最大10秒)コネクションプール ドレイン (最大5秒)アクティブクエリ完了待ちClearAllPools()CloseAndFlushAsync() (最大2秒)フラッシュ完了プロセス終了ヘルスチェック反映待機 (5秒)タイムアウト後は 503 返却再起動後にリジューム可能合計最大 52秒 < 60秒Cloud RunロードバランサーApplicationHangfire ServerDB コネクションプールSerilog

DB コネクションプーリング & RLS 適用設計

マルチテナント環境において、コネクションプールとRLS(Row Level Security)を安全かつ効率的に運用するための設計を定義する。

コネクションプール構成

パラメータ通常期繁忙期(1月)説明
MinPoolSize510最小プールサイズ
MaxPoolSize50100最大プールサイズ
ConnectionIdleLifetime300秒300秒アイドルコネクションの回収間隔
ConnectionPruningInterval10秒10秒プール内コネクションの刈り込み間隔
Timeout15秒30秒コネクション取得タイムアウト
Keepalive60秒60秒TCP キープアライブ間隔

プール分離: API リクエスト用とバッチジョブ用で別プール(別接続文字列)を使用し、バッチ処理が API の応答性に影響しないようにする。

RLS 適用タイミング

EF Core の DbConnectionInterceptor を使用し、コネクションのライフサイクルに合わせて RLS のテナントコンテキストを設定・クリアする。

コネクション取得ConnectionOpenedAsyncSET app.current_tenant_id = @tenantIdクエリ実行テナント限定の結果コネクション返却ConnectionClosingAsyncRESET ALLコネクションをプールに返却RLS ポリシーがテナント ID で行フィルタテナントコンテキスト クリアApplicationTenantConnectionInterceptorコネクションプールPostgreSQL
コネクション取得ConnectionOpenedAsyncSET app.current_tenant_id = @tenantIdクエリ実行テナント限定の結果コネクション返却ConnectionClosingAsyncRESET ALLコネクションをプールに返却RLS ポリシーがテナント ID で行フィルタテナントコンテキスト クリアApplicationTenantConnectionInterceptorコネクションプールPostgreSQL

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_timeout10秒300秒PostgreSQL セッションレベルで設定
CommandTimeout10秒300秒EF Core / Npgsql レベルで設定
lock_timeout5秒30秒ロック待ちタイムアウト
idle_in_transaction_session_timeout30秒120秒トランザクション内アイドルタイムアウト

スロークエリ監視: log_min_duration_statement = 1000(1秒超のクエリをログ出力)。繁忙期は 500ms に引き下げ。


DBマイグレーション設計

→ 満たす要件: OP-003 データベースマイグレーション

Expand/Contract パターン

破壊的なスキーマ変更は、以下の Expand/Contract パターン で段階的に実施する:

ステップ内容例: カラム名変更 (old_namenew_name)
1. Expand(拡張)新しいスキーマ要素を追加。既存要素は残すnew_name カラムを追加。トリガーで old_namenew_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リクエスト)正常と判断し通常運転へ復帰

状態遷移図

エラー率 ≥ 50%(最低10リクエスト / 30秒)正常レスポンス60秒経過成功率 ≥ 80%(5リクエスト)試行失敗ClosedOpenHalfOpen
エラー率 ≥ 50%(最低10リクエスト / 30秒)正常レスポンス60秒経過成功率 ≥ 80%(5リクエスト)試行失敗ClosedOpenHalfOpen

Retry 実装方針

  • バックオフ戦略: Exponential Backoff + Jitter(1秒 → 2秒 → 4秒 + ランダム揺らぎ)