[프롬프트] 에러 메시지 분석 및 해결책 제안 프롬프트 - 최적 프롬프트 레시피
💡 한 줄 요약: "이 에러 왜 발생해?"라는 단순 질의 시 터져 나오는 '반쪽짜리 try-catch 감싸기'와 '단순 라이브러리 재설치' 조언을 완전히 타파하고, Staff SRE 관점의 근본 원인 분석(5 Whys)과 P0 장애 긴급 격리/핫픽스를 정확하게 도출하는 실전 프롬프트 레시피입니다.
1. 페인포인트 & 작업 목표
- 실제 작업 시나리오: 프로덕션 배포 직후 데이터베이스 커넥션 풀 고갈, 비동기 데드락, Node.js OOM(Out of Memory) 크래시 등 복잡한 장애가 발생했을 때 신속하게 원인을 규명하고 재발을 방지해야 하는 상황.
-
단순 프롬프트 요청 시 발생하는 4대 전형적 문제:
- 표면적 증상 땜질 (Symptom-Level Patching): 에러가 발생한 지점만 단순
try-catch로 덮거나if (obj != null)방어 코드만 제시하여, 내부 상태 불일치와 데이터 오염을 가중함. - 재현 조건 및 런타임 맥락 무시 (Context Blindness): 동시성(Concurrency), 트랜잭션 격리 수준, GC 주기, 네트워크 타임아웃 등 런타임 환경 맥락을 파악하지 않고 단일 스레드 관점의 안이한 답변만 반복함.
- 재발 방지책(5 Whys) 결여: 왜 그런 코드 흐름이 허용되었는지 아키텍처 결함을 파헤치지 않아 동일한 장애가 며칠 뒤 다른 서비스에서 재발함.
- P0 긴급 핫픽스와 영구 수정의 혼동: 서비스가 다운된 긴급 상황에서 즉각 트래픽 격리나 킬스위치 대신 대규모 리팩토링 코드를 제시하여 복구 골든타임을 허비함.
- 표면적 증상 땜질 (Symptom-Level Patching): 에러가 발생한 지점만 단순
-
최종 해결 목표:
- Staff SRE / Principal Troubleshooting Engineer 페르소나 주입: 단순 코드 분석이 아닌 인프라·런타임·비즈니스 로직을 관통하는 입체적 트러블슈팅 수행.
- 근본 원인 분석 5단계 강제: [에러 시그니처 정밀 해체 → 5 Whys 원인 규명 → 10분 즉시 완화 핫픽스 → 영구적 아키텍처 방어책 → 재현 및 검증 스트레스 테스트] 파이프라인 정립.
- 긴급도별 레시피 분리: 비동기 교착/커넥션 누수를 위한 구조적 분석(레시피 B)과 P0 다운타임 전용 핫픽스(레시피 C)의 명확한 이원화.
2. 프롬프트 레시피 비교
3. 실전 적용 및 단계별 실행 결과
❌ 레시피 A 실행 결과: (반쪽짜리 try-catch 비판)
레시피 A에 QueryFailedError: deadlock detected (Process 1824 waits for ShareLock on transaction...) 로그를 입력했을 때의 전형적 결과입니다:
// ❌ AI의 반쪽짜리 해결책: 단순 catch 후 에러 무시 또는 단순 로그 출력
async function transferCredits(senderId: string, receiverId: string, amount: number) {
try {
await db.transaction(async (trx) => {
await trx('users').where({ id: senderId }).decrement('balance', amount);
await trx('users').where({ id: receiverId }).increment('balance', amount);
});
} catch (error) {
console.error('트랜잭션 실패:', error);
// 문제: 클라이언트는 송금 성공 여부를 알 수 없고, 커넥션 타임아웃 시 풀 누수가 지속됨
}
}
catch 블록으로 에러를 숨겼을 뿐, 프로덕션 트래픽 환경에서는 여전히 수백 개의 프로세스가 데드락으로 묶여 커넥션 풀을 모두 잠식해버립니다.
⚡ 레시피 B 실행 결과: [실전 사례 1] PostgreSQL 비동기 데드락 & 커넥션 풀 고갈 해결
동시 송금 및 포인트 정산 트래픽 급증 시 풀 고갈로 504 Gateway Timeout이 연쇄 발생한 인시던트를 5 Whys와 결정론적 락 순서 정렬로 완벽 해결한 사례입니다.
1) 1차 입력 프롬프트 (레시피 B 인시던트 컨텍스트 주입)
- 스택: Node.js v20, PostgreSQL 16 (Knex/TypeORM), Hikari/pg-pool
- 로그:
error: deadlock detected / DETAIL: Process 812 waits for ExclusiveLock on tuple (4,12) of relation "wallets"; blocked by process 819. Process 819 waits for ExclusiveLock on tuple (4,9)... - 상황: 동시 송금 및 포인트 정산 트래픽 급증 시 풀 고갈로 504 Gateway Timeout 연쇄 발생
2) AI 생성 출력 결과 (핵심 발췌)
### 1. Root Cause Analysis (5 Whys) 1. Why did 504 Timeouts occur? -> Database connection pool (max 50) was 100% saturated. 2. Why was the pool saturated? -> Active transactions were blocked waiting for row locks up to statement_timeout. 3. Why were transactions blocked? -> Mutual circular dependency (Deadlock) occurred between transaction A and transaction B. 4. Why did circular dependency happen? -> Transaction A locked User X then User Y; Transaction B locked User Y then User X (Lock Inversion). 5. Root Architectural Cause: Resource acquisition order was nondeterministic and uncoordinated at the application layer.
ALTER SYSTEM SET statement_timeout = '3000ms'; ALTER SYSTEM SET lock_timeout = '2000ms'; SELECT pg_reload_conf();
export async function executeAtomicTransfer(
db: Knex,
userAId: string,
userBId: string,
amount: number
): Promise<void> {
// 1. 데드락 원천 차단: ID 알파벳 오름차순으로 락 획득 순서 강제
const [firstId, secondId] = [userAId, userBId].sort((a, b) => a.localeCompare(b));
const isSenderFirst = userAId === firstId;
await db.transaction(async (trx) => {
// 2. 일관된 순서로 비관적 배타락(FOR UPDATE) 선점
const firstWallet = await trx('wallets')
.where({ user_id: firstId })
.forUpdate()
.first();
const secondWallet = await trx('wallets')
.where({ user_id: secondId })
.forUpdate()
.first();
const senderWallet = isSenderFirst ? firstWallet : secondWallet;
const receiverWallet = isSenderFirst ? secondWallet : firstWallet;
if (!senderWallet || senderWallet.balance < amount) {
throw new InsufficientBalanceError(`Balance insufficient for user ${userAId}`);
}
await trx('wallets').where({ user_id: userAId }).decrement('balance', amount);
await trx('wallets').where({ user_id: userBId }).increment('balance', amount);
});
}
3) 후속 프롬프트 체이닝 (Prompt Chaining)
체이닝 1: 부하 스트레스 테스트 코드 자동 도출
👉 "위 락 순서 정렬 패치가 동시 트래픽 500개에서 데드락을 0건으로 만드는지 검증하는 Jest 동시성 테스트 코드를 작성해줘."
→ AI가 Promise.all과 역방향 교차 송금(A->B, B->A)을 250쌍 동시 발송하는 스트레스 테스트 코드를 반환하여 수정안을 즉시 입증.
체이닝 2: Cursor IDE 즉시 패치 프롬프트
👉 "Cursor Composer에서 src/modules/wallet/wallet.service.ts 파일의 transferCredits 메서드를 위 결정론적 락 정렬 로직으로 교체하는 규칙을 작성해줘."
4) 4단계 장애 진단 & 복구 워크플로우
- 로그 파싱: 스택트레이스에서 블록된 릴레이션 튜플 번호 및 쿼리 추출.
- 5 Whys 진단: 레시피 B로 락 역전 인과 사슬 및 즉시 타임아웃 세팅 확보.
- 결정론적 정렬 패치: ID 기반 정렬로 상호 대기 사이클 제거.
- 회귀 방지 검증: 동시성 시뮬레이션으로 데드락 0건 확인 후 배포.
🚀 레시피 C 실행 결과: [실전 사례 2] Node.js OOM 메모리 누수 핫픽스
대용량 엑셀 다운로드 요청 폭주로 Node.js 파드가 OOM CrashLoopBackOff에 빠진 P0 상황을 2분 격리 / 5분 힙 증설 / 10분 스트림 전환으로 무중단 복구한 사례입니다.
1) 1차 입력 프롬프트 (레시피 C 긴급 텔레메트리 주입)
- 유형:
JavaScript heap out of memory (1420 MB reached), Pod CrashLoopBackOff 반복 - 환경: Kubernetes v1.28, 컨테이너 메모리 제한 2GiB, 트래픽 유입 중
2) AI 생성 출력 결과 (핵심 발췌)
### 1. 피해 반경 격리 (즉시 실행) # 비정상 종료 파드로 들어오는 인그레스 트래픽 일시 차단 kubectl label pod -l app=order-api traffic=isolated --overwrite ### 2. 긴급 드레인 & Node V8 힙 완화 (5분 이내) # 컨테이너 크래시 방지를 위해 V8 최대 힙 크기를 메모리 제한의 75% 수준으로 강제 설정 kubectl set env deployment/order-api-deployment \ NODE_OPTIONS="--max-old-space-size=1536" kubectl scale deployment/order-api-deployment --replicas=18
### 3. 10분 전술적 핫픽스 (Surgical Diff)
// src/services/export.service.ts
// 문제: 대용량 주문 내역을 배열(Array.push)로 메모리에 전량 적재하여 OOM 발생
// 수정: PassThrough Stream 파이프라인으로 전환하여 메모리 점유율을 상수로 고정 (10MB 유지)
- const allOrders = await OrderModel.find({ status: 'completed' }); // 10만 건 적재
- return generateExcel(allOrders);
+ const cursor = OrderModel.find({ status: 'completed' }).cursor();
+ res.setHeader('Content-Type', 'text/csv');
+ return cursor.pipe(new CsvTransformStream()).pipe(res);
3) 후속 체이닝: V8 힙 덤프(Heapdump) 분석 프롬프트 연계
사용자 추가 프롬프트:
👉 "긴급 핫픽스 배포 후 프로덕션에서 수집한 heapdump-001.heapsnapshot에서 메모리를 점유하고 있는 지배적 객체(Retainer tree)를 찾는 Chrome DevTools 분석 단계를 안내해줘."
4) 3단계 긴급 복구 워크플로우
- 트래픽 격리 및 힙 크기 조절: CrashLoop 파드를 격리하고 환경변수로 임시 버퍼 확보.
- 스트림 전환 핫픽스: 배열 전체 적재 로직을 커서 기반 스트리밍으로 즉각 교체.
- 안정화 후 덤프 분석: 복구 후 취득한 힙 덤프로 영구 누수 참조자 제거.
4. 프롬프트 활용 팁 & 주의사항
-
로그 마스킹 필수: 프로덕션 에러 로그에는 고객의 개인정보(PII), JWT 시크릿, 내부 DB 비밀번호가 포함될 수 있습니다. AI 입력 전
sed나 스크립트로Bearer eyJ...또는 비밀번호 필드를 반드시 마스킹하십시오. -
스택 트레이스만 던지지 말 것: 같은
Connection refused라도 "DB 이전 작업 중"인지, "단순 트래픽 스파이크"인지에 따라 해결책이 180도 달라집니다. 시스템 자원 점유 상태와 최근 배포 커밋을 반드시 함께 제공하십시오. - 단순 try-catch 제안은 즉시 기각할 것: AI가 원인을 모호하게 여길 때 가장 자주 내놓는 답변이 단순 예외 삼키기(Swallowing Exceptions)입니다. "원인을 설명하지 않는 try-catch는 거부한다"는 제약을 프롬프트에 유지하십시오.
- Cursor/Copilot 에이전트 모드 연동: 도출된 영구 수정 패치를 복사하여 Cursor IDE Composer의 프롬프트 창에 넣으면, 관련 엔티티와 서비스 레이어 코드 전체를 파일 간 종속성을 고려해 원클릭으로 리팩토링할 수 있습니다.