ERP-node/PHASE3.11_DDL_AUDIT_LOGGER_...

8.8 KiB

📋 Phase 3.11: DDLAuditLogger Raw Query 전환 계획

📋 개요

DDLAuditLogger는 8개의 Prisma 호출이 있으며, DDL 실행 감사 로그 관리를 담당하는 서비스입니다.

📊 기본 정보

항목 내용
파일 위치 backend-node/src/services/ddlAuditLogger.ts
파일 크기 368 라인
Prisma 호출 8개
현재 진행률 0/8 (0%) 🔄 진행 예정
복잡도 중간 (통계 쿼리, $executeRaw)
우선순위 🟡 중간 (Phase 3.11)
상태 대기 중

🎯 전환 목표

  • 8개 모든 Prisma 호출을 db.tsquery(), queryOne() 함수로 교체
  • DDL 감사 로그 기능 정상 동작
  • 통계 쿼리 전환 (GROUP BY, COUNT, ORDER BY)
  • $executeRaw → query 전환
  • $queryRawUnsafe → query 전환
  • 동적 WHERE 조건 생성
  • TypeScript 컴파일 성공
  • Prisma import 완전 제거

🔍 Prisma 사용 현황 분석

주요 Prisma 호출 (8개)

1. logDDLStart() - DDL 시작 로그 (INSERT)

// Line 27
const logEntry = await prisma.$executeRaw`
  INSERT INTO ddl_audit_logs (
    execution_id, ddl_type, table_name, status, 
    executed_by, company_code, started_at, metadata
  ) VALUES (
    ${executionId}, ${ddlType}, ${tableName}, 'in_progress',
    ${executedBy}, ${companyCode}, NOW(), ${JSON.stringify(metadata)}::jsonb
  )
`;

2. getAuditLogs() - 감사 로그 목록 조회 (SELECT with filters)

// Line 162
const logs = await prisma.$queryRawUnsafe(query, ...params);
  • 동적 WHERE 조건 생성
  • 페이징 (OFFSET, LIMIT)
  • 정렬 (ORDER BY)

3. getAuditStats() - 통계 조회 (복합 쿼리)

// Line 199 - 총 통계
const totalStats = (await prisma.$queryRawUnsafe(
  `SELECT 
    COUNT(*) as total_executions,
    COUNT(CASE WHEN status = 'success' THEN 1 END) as successful,
    COUNT(CASE WHEN status = 'failed' THEN 1 END) as failed,
    AVG(EXTRACT(EPOCH FROM (completed_at - started_at))) as avg_duration
   FROM ddl_audit_logs 
   WHERE ${whereClause}`
)) as any[];

// Line 212 - DDL 타입별 통계
const ddlTypeStats = (await prisma.$queryRawUnsafe(
  `SELECT ddl_type, COUNT(*) as count
   FROM ddl_audit_logs 
   WHERE ${whereClause}
   GROUP BY ddl_type
   ORDER BY count DESC`
)) as any[];

// Line 224 - 사용자별 통계
const userStats = (await prisma.$queryRawUnsafe(
  `SELECT executed_by, COUNT(*) as count
   FROM ddl_audit_logs 
   WHERE ${whereClause}
   GROUP BY executed_by
   ORDER BY count DESC 
   LIMIT 10`
)) as any[];

// Line 237 - 최근 실패 로그
const recentFailures = (await prisma.$queryRawUnsafe(
  `SELECT * FROM ddl_audit_logs
   WHERE status = 'failed' AND ${whereClause}
   ORDER BY started_at DESC
   LIMIT 5`
)) as any[];

4. getExecutionHistory() - 실행 이력 조회

// Line 287
const history = await prisma.$queryRawUnsafe(
  `SELECT * FROM ddl_audit_logs
   WHERE table_name = $1 AND company_code = $2
   ORDER BY started_at DESC
   LIMIT $3`,
  tableName,
  companyCode,
  limit
);

5. cleanupOldLogs() - 오래된 로그 삭제

// Line 320
const result = await prisma.$executeRaw`
  DELETE FROM ddl_audit_logs
  WHERE started_at < NOW() - INTERVAL '${retentionDays} days'
  AND company_code = ${companyCode}
`;

💡 전환 전략

1단계: $executeRaw 전환 (2개)

  • logDDLStart() - INSERT
  • cleanupOldLogs() - DELETE

2단계: 단순 $queryRawUnsafe 전환 (1개)

  • getExecutionHistory() - 파라미터 바인딩 있음

3단계: 복잡한 $queryRawUnsafe 전환 (1개)

  • getAuditLogs() - 동적 WHERE 조건

4단계: 통계 쿼리 전환 (4개)

  • getAuditStats() 내부의 4개 쿼리
  • GROUP BY, CASE WHEN, AVG, EXTRACT

💻 전환 예시

예시 1: $executeRaw → query (INSERT)

변경 전:

const logEntry = await prisma.$executeRaw`
  INSERT INTO ddl_audit_logs (
    execution_id, ddl_type, table_name, status, 
    executed_by, company_code, started_at, metadata
  ) VALUES (
    ${executionId}, ${ddlType}, ${tableName}, 'in_progress',
    ${executedBy}, ${companyCode}, NOW(), ${JSON.stringify(metadata)}::jsonb
  )
`;

변경 후:

await query(
  `INSERT INTO ddl_audit_logs (
    execution_id, ddl_type, table_name, status, 
    executed_by, company_code, started_at, metadata
  ) VALUES ($1, $2, $3, $4, $5, $6, NOW(), $7::jsonb)`,
  [
    executionId,
    ddlType,
    tableName,
    'in_progress',
    executedBy,
    companyCode,
    JSON.stringify(metadata),
  ]
);

예시 2: 동적 WHERE 조건

변경 전:

let query = `SELECT * FROM ddl_audit_logs WHERE 1=1`;
const params: any[] = [];

if (filters.ddlType) {
  query += ` AND ddl_type = ?`;
  params.push(filters.ddlType);
}

const logs = await prisma.$queryRawUnsafe(query, ...params);

변경 후:

const conditions: string[] = [];
const params: any[] = [];
let paramIndex = 1;

if (filters.ddlType) {
  conditions.push(`ddl_type = $${paramIndex++}`);
  params.push(filters.ddlType);
}

const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
const sql = `SELECT * FROM ddl_audit_logs ${whereClause}`;

const logs = await query<any>(sql, params);

예시 3: 통계 쿼리 (GROUP BY)

변경 전:

const ddlTypeStats = (await prisma.$queryRawUnsafe(
  `SELECT ddl_type, COUNT(*) as count
   FROM ddl_audit_logs 
   WHERE ${whereClause}
   GROUP BY ddl_type
   ORDER BY count DESC`
)) as any[];

변경 후:

const ddlTypeStats = await query<{ ddl_type: string; count: string }>(
  `SELECT ddl_type, COUNT(*) as count
   FROM ddl_audit_logs 
   WHERE ${whereClause}
   GROUP BY ddl_type
   ORDER BY count DESC`,
  params
);

🔧 기술적 고려사항

1. JSON 필드 처리

metadata 필드는 JSONB 타입으로, INSERT 시 ::jsonb 캐스팅 필요:

JSON.stringify(metadata) + '::jsonb'

2. 날짜/시간 함수

  • NOW() - 현재 시간
  • INTERVAL '30 days' - 날짜 간격
  • EXTRACT(EPOCH FROM ...) - 초 단위 변환

3. CASE WHEN 집계

COUNT(CASE WHEN status = 'success' THEN 1 END) as successful

4. 동적 WHERE 조건

여러 필터를 조합하여 WHERE 절 생성:

  • ddlType
  • tableName
  • status
  • executedBy
  • dateRange (startDate, endDate)

📝 전환 체크리스트

1단계: Prisma 호출 전환

  • logDDLStart() - INSERT ($executeRaw → query)
  • logDDLComplete() - UPDATE (이미 query 사용 중일 가능성)
  • logDDLError() - UPDATE (이미 query 사용 중일 가능성)
  • getAuditLogs() - SELECT with filters ($queryRawUnsafe → query)
  • getAuditStats() 내 4개 쿼리:
    • totalStats (집계 쿼리)
    • ddlTypeStats (GROUP BY)
    • userStats (GROUP BY + LIMIT)
    • recentFailures (필터 + ORDER BY + LIMIT)
  • getExecutionHistory() - SELECT with params ($queryRawUnsafe → query)
  • cleanupOldLogs() - DELETE ($executeRaw → query)

2단계: 코드 정리

  • import 문 수정 (prismaquery, queryOne)
  • Prisma import 완전 제거
  • 타입 정의 확인

3단계: 테스트

  • 단위 테스트 작성 (8개)
    • DDL 시작 로그 테스트
    • DDL 완료 로그 테스트
    • 감사 로그 목록 조회 테스트
    • 통계 조회 테스트
    • 실행 이력 조회 테스트
    • 오래된 로그 삭제 테스트
  • 통합 테스트 작성 (3개)
    • 전체 DDL 실행 플로우 테스트
    • 필터링 및 페이징 테스트
    • 통계 정확성 테스트
  • 성능 테스트
    • 대량 로그 조회 성능
    • 통계 쿼리 성능

4단계: 문서화

  • 전환 완료 문서 업데이트
  • 주요 변경사항 기록
  • 성능 벤치마크 결과

🎯 예상 난이도 및 소요 시간

  • 난이도: (중간)

    • 복잡한 통계 쿼리 (GROUP BY, CASE WHEN)
    • 동적 WHERE 조건 생성
    • JSON 필드 처리
  • 예상 소요 시간: 1~1.5시간

    • Prisma 호출 전환: 30분
    • 테스트: 20분
    • 문서화: 10분

📌 참고사항

관련 서비스

  • DDLExecutionService - DDL 실행 (이미 전환 완료)
  • DDLSafetyValidator - DDL 안전성 검증

의존성

  • ../database/db - query, queryOne 함수
  • ../types/ddl - DDL 관련 타입
  • ../utils/logger - 로깅

상태: 대기 중
특이사항: 통계 쿼리, JSON 필드, 동적 WHERE 조건 포함