Skip to content

migration-runnerにAurora Serverless v2コールドスタート対応のリトライを追加する #104

@konokenj

Description

@konokenj

問題

webapp/src/jobs/migration-runner.ts はリトライなしで prisma db push を実行している。Aurora Serverless v2が0 ACU(または最小ACU)からスケーリングする際、データベースが到達可能になるまで最大30秒かかる。この間に prisma db push がエラーコード P1001("Can't reach database server")で失敗し、CDK Triggerの失敗を経てデプロイ全体が中断する。

問題の背景:

  • migration runnerはCDK Triggerにより cdk deploy 中に自動実行される
  • Aurora Serverless v2のコールドスタート遅延は想定される挙動であり、永続的な障害ではない
  • 一時的な接続エラーでデプロイ全体の再実行が必要になるべきではない

現在のコード

// webapp/src/jobs/migration-runner.ts
const exitCode = await new Promise((resolve, _) => {
  execFile(
    path.resolve('./node_modules/prisma/build/index.js'),
    ['db', 'push', '--skip-generate'].concat(options),
    (error, stdout, stderr) => {
      console.log(stdout);
      if (error != null) {
        console.log(`prisma db push exited with error ${error.message}`);
        resolve(error.code ?? 1);
      } else {
        resolve(0);
      }
    },
  );
});

if (exitCode != 0) throw Error(`db push failed with exit code ${exitCode}`);

リトライがなく、1回の接続失敗で即座にエラーとなる。

修正案

指数バックオフによるリトライ(最大5回、初期遅延3秒)を追加し、一時的な接続エラーのみリトライする:

async function runPrismaDbPush(options: string[], maxRetries = 5, baseDelay = 3000): Promise<void> {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    const { exitCode, stdout, stderr } = await new Promise<{
      exitCode: number;
      stdout: string;
      stderr: string;
    }>((resolve) => {
      execFile(
        path.resolve('./node_modules/prisma/build/index.js'),
        ['db', 'push', '--skip-generate'].concat(options),
        {},
        (error, stdout, stderr) => {
          resolve({
            exitCode: typeof error?.code === 'number' ? error.code : error ? 1 : 0,
            stdout,
            stderr,
          });
        },
      );
    });

    console.log('prisma db push output', { attempt, stdout, stderr });

    if (exitCode === 0) return;

    const isRetryable =
      stderr.includes('P1001') ||
      stderr.includes("Can't reach database") ||
      stderr.includes('Connection refused');

    if (!isRetryable || attempt === maxRetries) {
      throw new Error(`db push failed after ${attempt} attempts: ${stderr}`);
    }

    const delay = baseDelay * Math.pow(2, attempt - 1) + Math.random() * 1000;
    console.log('db_push_retry', { attempt, maxRetries, delayMs: Math.round(delay) });
    await new Promise((r) => setTimeout(r, delay));
  }
}

最悪ケースのリトライ所要時間は約100秒(3 + 6 + 12 + 24 + 48 + ジッター)で、Lambdaの5分タイムアウト内に十分収まる。

リトライ対象の判定

エラー種別 リトライ
P1001(データベースに到達不可) ✅ する Auroraコールドスタート
Connection refused ✅ する データベース未起動
スキーマ不整合・バリデーションエラー ❌ しない 永続的エラー、即座に失敗

検証方法

  • Aurora Serverless v2が最小ACUからスケーリング中に cdk deploy が成功すること
  • リトライログ(試行回数・遅延時間)が出力されること
  • 永続的エラー(スキーマ不整合等)ではリトライせず即座に失敗すること

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions