AI 장애 분석 포스트모템 시리즈 1/9
Datadog에서 GET /heartbeat trace 하나가 30초 timeout 경계에 붙어 있는 화면을 보고 있었습니다. 같은 시점에 JVM Old Gen이 올라와 있었습니다.

초기 단서: 일부 GET /heartbeat span이 30초 timeout 경계에 붙어 있었습니다.
이 스크린샷 한 장을 Claude Code에 붙여두고 원인을 물어보려고 했습니다. 그런데 프롬프트를 어떻게 써야 하나 잠깐 고민하다가 이상한 점이 보였습니다. AI가 볼 수 있는 건 이 화면 한 장뿐이었습니다.
프롬프트보다 먼저 막힌 지점
먼저 Datadog API Key와 Application Key를 환경변수로 붙였습니다. /api/v1/validate는 성공했습니다. metric query와 hosts API는 403을 돌려줬습니다.
이 상태로 원인을 분석하라고 시키면 AI는 trace와 log 안에서만 그럴듯한 설명을 만들 것 같았습니다. 특히 /heartbeat 30초와 Old Gen 증가 두 단서에 끌려 GC나 DB ping을 원인으로 못박는 문서가 나올 가능성이 컸습니다. 실제로 /heartbeat는 단순 생존 확인이 아니라 DB ping()까지 호출하고 있었습니다.
@GetMapping("/heartbeat")
suspend fun heartbeat(): String {
systemService.ping()
return "OK"
}
Repository는 R2DBC를 사용했습니다.
class AccountRepository(
private val accountR2dbcRepository: AccountR2dbcRepository,
) {
suspend fun ping() {
accountR2dbcRepository.ping().awaitSingle()
}
}
이름은 heartbeat인데 동작은 readiness에 가까웠습니다. 그래서 코드만 보면 "DB pool 문제겠지"라고 결론짓기 쉬웠습니다. 그리고 그 결론을 반박할 metric 권한이 아직 열려 있지 않았습니다.
데이터 범위를 넓히자 처음 결론이 흔들렸습니다
그래서 Datadog scope를 보완했습니다. Timeseries query, Hosts read, Monitors read를 열자 JVM metric, request count, task-level tag까지 조회됐습니다. major GC count와 시간이 거의 0이었습니다.
그러자 Old Gen 증가는 그 순간 원인 후보에서 증상 쪽으로 내려갔습니다. 같은 시간대에 GET /v3/auth 요청이 6~10배 늘어 있었고, R2DBC write pool 대기와 ECS task replacement가 그 뒤에 있었습니다. 처음 눈에 들어왔던 /heartbeat와 Old Gen은 분석의 결론이 아니라 출발점이었습니다.
프롬프트가 아니라 조회 범위가 결정했습니다
처음에는 프롬프트를 잘 쓰는 문제인 줄 알았습니다. 하지만 실제로는 AI에게 어디까지 열어주는지의 문제였습니다. Claude Code에 아무리 잘 정리한 지시를 넣어도, 볼 수 있는 데이터가 좁으면 결론은 그 좁은 범위 안에서만 나옵니다.
연결 방식은 감당 가능한 범위로 나눴습니다
이번 조사에서 실제로 고려한 방식은 세 가지였습니다. 각각 붙이는 부담과 조회 범위가 달랐습니다.
MCP로 외부 시스템에 붙이기. Cursor에서 Datadog MCP를 이메일 인증으로 연결해봤습니다. 붙는 건 빨랐습니다. 다만 어떤 API가 실제로 열려 있는지, timeseries와 task tag까지 접근되는지는 따로 확인해야 했습니다. 붙는 게 쉬웠다고 근거가 자동으로 단단해지는 것은 아니었습니다. RDS와 ECS Fargate 사용량을 비용 관점으로 정리해달라고 했을 때 보고서 형식 문서가 바로 나온 것은 이 방식의 장점이었습니다.
에이전트에서 직접 API와 CLI를 호출. 이번 조사는 여기서 시작했습니다. Datadog API Key와 Application Key는 환경변수로 두고, AWS CLI profile은 read-only로 제한했습니다. AI가 실행한 명령이 그대로 보이니 실패했을 때 같은 명령을 사람이 다시 돌려 확인할 수 있었습니다. 앞서 metric query 403이 났던 것도 이 방식이었기에 눈에 띄었습니다.
AWS Bedrock Agent로 내부 자동화. 이번 조사에는 넣지 않았습니다. IAM, 네트워크, 감사 로그, 알림 채널 권한을 같이 설계해야 해서 처음부터 붙이기에는 무거웠습니다. read-only workbench 하나를 사람이 여러 번 안정적으로 돌려본 뒤로 미뤘습니다.
ReadOnly는 좁게 시작해 필요한 만큼만 열었습니다
권한을 넓게 열어두면 조사할 때는 편하지만 사고 반경도 같이 커집니다. 그래서 시작은 아래 정도로 좁혔습니다.
AWS:
- CloudWatchReadOnlyAccess
- AmazonRDSReadOnlyAccess
- AWSXRayReadOnlyAccess
- 필요 시 ECS 또는 EC2 ReadOnly
Datadog:
- Logs read
- APM read
- Metrics read
- Events read
- Monitors read
- Timeseries query
- Hosts read
GitLab 또는 사내 Git:
- Repository read
- Merge requests read
- Pipelines read
- Deployments read
내부 알림 채널:
- 장애 채널 read
- 자동 전송은 별도 Bot 또는 webhook으로 분리
이번 조사에서 결정적이었던 건 Datadog metric 계열 scope였습니다. Timeseries query와 Hosts read가 없었으면 major GC count가 0이라는 걸 확인할 방법이 없었습니다. 첫 결론을 그대로 문서로 만들었을 것입니다.
요청할 때 고정해서 넣는 정보
권한을 넓히고 나서도 매번 프롬프트를 새로 쓰지 않았습니다. 대신 조사할 때마다 아래 정보는 형식을 고정해서 같이 넣었습니다.
| 항목 | 예시 |
|---|---|
| 시간 범위 | 2026-06-12 13:00~14:00 KST |
| 서비스 | account-service |
| 초기 단서 | GET /heartbeat trace 30초, Old Gen 증가 |
| 조회 대상 | Datadog APM/logs/metrics, AWS ECS/ALB/RDS/CloudWatch, GitLab deploy |
| 출력 형식 | Timeline, Evidence, Hypotheses, Actions |
| 제약 | 사실과 추정을 분리한다. 초기 단서를 결론으로 못박지 않는다. |
프롬프트 자체보다 이 형식이 결과를 안정시켰습니다.
다음 조사부터 미리 하는 것
두 가지를 미리 합니다.
- 프롬프트를 짜기 전에 metric, host, task tag까지 API가 실제로 응답을 주는지 한 번 호출해서 확인합니다.
- 조회 가능한 범위를 문서로 남기고, 그 범위 안에서만 AI에게 결론을 맡깁니다.
권한이 좁은 상태에서 나온 결론은 근거가 좁다는 뜻이지, 결론이 맞다는 뜻이 아닙니다. 이 기준을 바탕으로 다음 편에서는 Datadog을 Claude Code에 붙이고 실제 evidence summary를 어디까지 뽑을 수 있었는지 기록합니다.
'성장과 기술 > 개발과 자동화' 카테고리의 다른 글
| 자동화가 실패했을 때 사람이 복구할 수 있게 만들기 (0) | 2026.06.15 |
|---|---|
| 공개 데이터와 내부 데이터를 분리하는 법 (0) | 2026.06.14 |
| 최신 데이터, 월별 스냅샷, 감사 백업을 나눠 저장하기 (0) | 2026.06.13 |
| Apps Script에서 S3로 직접 쓰지 않은 이유 (0) | 2026.06.12 |
| Apps Script를 비즈니스 로직 엔진으로 쓰기 (0) | 2026.06.11 |
글에서 정리한 생각은 GitHub의 코드와 포트폴리오로 이어지고, 일부는 FamBlend 같은 제품 실험으로 확장됩니다.