Skip to content

Prisma + Aurora接続管理の改善: セキュリティ修正・リトライ・接続オプション最適化 #105

@konokenj

Description

@konokenj

概要

webapp/src/lib/prisma.tscdk/lib/constructs/database.ts に複数の問題があります。

  1. セキュリティ問題: console.log(process.env.DATABASE_URL) でDB接続文字列(パスワード含む)がCloudWatch Logsに出力される
  2. リトライ機構なし: Aurora Serverless v2の idle_session_timeout(60秒)後の接続切断でクエリが失敗し、自動復旧しない
  3. 接続オプション不十分: pool_timeout=20 はLambda長時間実行(最大15分)に不適、connect_timeout=20 はAurora Serverless v2のcold start(auto-pauseからの復帰、最大30秒)に不十分

現在のコード

prisma.ts:

console.log(process.env.DATABASE_URL); // パスワードがログに出力される
export const prisma = globalForPrisma.prisma || new PrismaClient({ log: ['query', 'info', 'warn', 'error'] });

database.ts:

const option = '?pool_timeout=20&connect_timeout=20';

修正案

1. prisma.ts — console.log削除 + リトライ拡張追加

import { Prisma, PrismaClient } from '@prisma/client';

const globalForPrisma = global as unknown as {
  prisma: PrismaClient;
};

function isRetryableError(error: unknown): boolean {
  if (!(error instanceof Error)) return false;
  const msg = error.message;
  const code = (error as { code?: string }).code;
  return (
    code === 'P2024' || // Connection pool timeout
    code === 'P1001' || // Can't reach database
    code === 'P1017' || // Server has closed the connection
    code === 'P2034' || // Transaction conflict
    msg.includes('idle-session timeout') ||
    msg.includes('terminating connection') ||
    msg.includes('57P05') ||
    msg.includes('ECONNRESET') ||
    msg.includes('Connection reset') ||
    msg.includes('Connection refused') ||
    msg.includes('Connection terminated') ||
    msg.includes("Can't reach database server")
  );
}
// 注意: PrismaClientInitializationErrorを名前だけで判定しない。
// 認証エラー等の永続的失敗もこの型で返るため、メッセージベースで判定する。

const basePrisma = new PrismaClient();

async function withRetry<T>(fn: () => Promise<T>, maxRetries = 5, baseDelay = 500): Promise<T> {
  let lastError: unknown;
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error;
      if (attempt === maxRetries || !isRetryableError(error)) throw error;
      await basePrisma.$disconnect();
      const delay = Math.round(baseDelay * Math.pow(2, attempt) + Math.random() * 100);
      await new Promise((r) => setTimeout(r, delay));
    }
  }
  throw lastError;
}

const retryExtension = Prisma.defineExtension({
  name: 'retry-on-connection-error',
  query: {
    $allModels: {
      async $allOperations({ args, query }) {
        return withRetry(() => query(args));
      },
    },
  },
});

export const prisma = basePrisma.$extends(retryExtension) as unknown as PrismaClient;

if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;

設計上の注意点:

  • $connect() を即時実行しないこと。PrismaClientは最初のクエリ時に自動接続する。即時接続するとLambdaコールドスタートのたびにAuroraがresumeされ、DBクエリを必要としないリクエスト(sign-inページ等)でもコストが発生する
  • PrismaClientInitializationError を型名だけで判定すると、認証エラー(永続的失敗)もリトライされ、80秒の遅延が発生する

2. database.ts — 接続オプション最適化 + Data API有効化 + 接続ログ

// 接続オプション変更
const option = '?connection_limit=1&pool_timeout=0&connect_timeout=30';
// connection_limit=1: Lambda並列実行時のDB接続枯渇を防止
// pool_timeout=0: 長時間実行Lambda(最大15分)でタイムアウトしない
// connect_timeout=30: Aurora Serverless v2 cold start考慮(最大30秒)

DatabaseClusterに以下を追加:

enableDataApi: true,
cloudwatchLogsExports: ['postgresql'],
cloudwatchLogsRetention: logs.RetentionDays.ONE_WEEK,
parameterGroup: new rds.ParameterGroup(this, 'ParameterGroup', {
  engine,
  parameters: {
    idle_session_timeout: '60000',
    log_connections: '1',
    log_disconnections: '1',
  },
}),
  • Data API: CLIから直接SQLを実行でき、運用時のデバッグ・データ修正が容易になる
  • 接続ログ: Aurora auto-pauseからの意図しないresumeの原因調査に必須

検証方法

  • console.log(process.env.DATABASE_URL) が削除されていること
  • Aurora auto-pause後の最初のリクエストがリトライで自動復旧すること
  • 認証エラー(永続的失敗)がリトライされず即座にエラーを返すこと
  • RDS Data APIでCLIからSQLを実行できること

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpriority: highNeeds prompt attention

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions