8.8 KiB
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.ts의query(),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()- INSERTcleanupOldLogs()- 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 문 수정 (
prisma→query, 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 조건 포함