
AI 에이전트가 실제로 진단할 수 있는지 확인하기 위해 4개 서비스의 연쇄 장애(Cascade Failure)를 구축해 보았습니다
요약
AI 에이전트의 진단 능력을 테스트하기 위해 4개의 서비스가 연결된 연쇄 장애(Cascade Failure) 환경을 구축했습니다. 단순 임계값 반응을 넘어, 복잡한 분산 시스템의 트레이스 데이터를 분석하여 근본 원인을 파악하는 에이전트의 성능을 검증합니다.
핵심 포인트
- 단순 자가 치유를 넘어선 고도화된 에이전트 진단 환경 구축
- FastAPI와 OpenTelemetry를 활용한 분산 서비스 체인 구현
- 비동기 컨텍스트 단절로 인한 트레이스 유실 문제 지적
- SigNoz와 Gemini를 연동한 에이전트 기반 장애 분석 시도
AI 에이전트가 실제로 진단할 수 있는지 확인하기 위해 4개 서비스의 연쇄 장애(Cascade Failure)를 구축해 보았습니다
이번 해커톤에 나열된 예시 프로젝트 중 하나는 기본적으로 "자가 치유 인프라 (self healing infra)"였습니다. 알람이 발생하면 에이전트가 컨테이너를 재시작하고, 끝. 저는 제출물의 절반 정도가 그런 형태일 것이라고 생각했기에, 그것을 만들고 싶지 않았습니다. 또한, 솔직히 말해서 그것이 무엇을 증명할 수 있을 것 같지도 않았습니다. 임계값 (threshold)에 반응하는 에이전트는 실제로 무슨 일이 일어났는지 이해하는 에이전트와는 다릅니다.
그래서 하나의 알람이 있는 하나의 서비스 대신, 저는 네 개의 서비스를 구축하여 서로 연결한 다음, 트레이스 (trace)를 제대로 살펴보지 않는 사람(또는 무엇이든)을 속일 수 있는 방식으로 그중 하나를 의도적으로 고장 냈습니다.
체인 (The chain)
Order가 Payment를 호출하고, Payment가 Inventory를 호출하며, Inventory가 Notification을 호출합니다. 네 개의 FastAPI 서비스가 있으며, 하나의 요청이 httpx를 통해 이 모든 서비스를 거치며 확산됩니다.
그다음 저는 제 자신에게 잔인한 짓을 했습니다. Payment에 카오스 플래그 (chaos flag)를 추가하여 약 30%의 요청에 무작위로 2~5초의 sleep을 추가함으로써, 불안정한 다운스트림 의존성 (downstream dependency)을 시뮬레이션했습니다.
이 프로젝트를 실제로 구축할 가치가 있게 만든 부분은 바로 여기입니다. Payment가 느려지면 Order도 느려진 것처럼 보입니다. 왜냐하면 Order는 단순히 그곳에서 기다리고 있기 때문입니다. 만약
저는 이전 이벤트 블로그에서와 마찬가지로 Foundry를 통해 SigNoz를 셀프 호스팅(self-hosted)한 다음, 각 서비스에 opentelemetry-instrumentation-fastapi와 opentelemetry-instrumentation-httpx를 추가하고 OTLP 익스포터(exporter)를 기존 배포 환경으로 지정했습니다. 아무도 경고해주지 않는 사실은, 만약 인스트루멘테이션(instrumentation)이 연결되기 전에 httpx.AsyncClient를 생성하거나, 체인의 어느 지점에서든 비동기 컨텍스트(async context)가 끊기게 되면, 하나의 트레이스(trace) 대신 연결되지 않은 네 개의 트레이스를 조용히 받게 된다는 점입니다. 에러도 없고, 명백히 잘못된 것도 없습니다. SigNoz에 들어가서 서비스 간에 트레이스 ID(trace ID)가 실제로 일치하는지 확인해야만 비로소 이를 알아챌 수 있습니다.
나의 근본 원인(root cause) 에이전트가 한 번은 자신 있게 틀렸던 이유
트레이스가 흐르기 시작하고, 연쇄 장애(cascade)의 형태를 보여주는 알림(alert)과 대시보드 패널이 갖춰진 후, 저는 실제 에이전트를 구축했습니다. 이 에이전트는 SigNoz MCP 서버를 통해 트레이스를 가져와 스팬(span) 데이터를 Gemini 2.5 Flash에 전달하고, 근본 원인(root cause)이 무엇인지 묻습니다.
첫 번째 버전은 단순히 각 스팬의 전체 지속 시간(total duration)을 Gemini에 입력했습니다. Payment의 지연이 트레이스에서 명백히 가장 큰 수치일 때는 잘 작동했습니다. 그러다 트레이스가 아주 약간 느려졌던(정상적인 네트워크 지터(jitter)였고, 카오스(chaos)가 실제로 트리거되지는 않았던) 한 실행 과정에서, Gemini는 자신 있게 Notification을 범인으로 지목했습니다. 데이터를 잘못 읽었기 때문이 아니었습니다. Notification 자체의 스팬이 실제로 페이지에서 가장 긴 수치였기 때문입니다. 단지 Gemini는 "가장 긴 스팬"과 "근본 원인"이 같은 것이 아니라는 점을 알지 못했습니다.
이것이 제가 이번 실험을 통해 얻은 실제 교훈이며, SigNoz 문서나 OpenTelemetry 문서에서도 직접적으로 알려주지 않는 내용입니다. 스팬의 전체 지속 시간에는 하위(downstream)로 호출한 대상이 기다리는 데 소비된 시간이 포함됩니다. 따라서 어떤 서비스는 단순히 그 아래에 있는 무언가가 느리기 때문에 느린 것처럼 보일 수 있습니다. 그것은 해당 서비스가 유죄라는 증거가 아닙니다. 오히려 무죄라는 증거에 가깝습니다.
Self time이 문제를 해결했습니다
실제로 중요한 숫자는 self time(자체 시간)입니다. 이는 자식(children) 노드에서 대기하며 소비한 시간을 제외하고, 해당 span이 자신의 작업을 수행하는 데 걸린 시간입니다. 저는 트레이스(trace) 데이터에서 이를 직접 계산했습니다:
self_time_ms = span.duration_ms - sum(child.duration_ms for child in span.children)
Gemini로 데이터를 보내기 전에 span들을 self time 기준으로 정렬했고, Gemini에게 원시 지속 시간(raw duration)이 아닌 self time이 신뢰해야 할 신호(signal)라고 명시적으로 알려주도록 프롬프트를 다시 작성했습니다. 또한 트레이스 검색 필터를 1초에서 2초로 상향 조정했습니다. 1초 미만의 수치는 제가 주입한 실제 혼돈(chaos)이 아니라 단순한 노이즈였기 때문입니다.
두 가지 변경을 적용한 후 진단 결과가 신뢰할 수 있게 되었습니다. Payment의 자체 span은 일관되게 가장 높은 self time을 기록했으며, 에이전트는 부하 테스트(load test)를 재실행할 때마다 혼돈 지연(chaos delay)이 단순히 가장 큰 수치였던 경우뿐만 아니라 매번 Payment를 정확하게 지목했습니다.
재시작 및 실제 작동 여부 증명
에이전트가 문제에 대해 아무런 조치도 취하지 않는다면, 문제를 진단하는 것은 흥미로운 부분이 아닙니다. Payment가 식별되면, 에이전트는 Payment의 관리자 엔드포인트(admin endpoint)를 호출하여 혼돈 플래그(chaos flag)를 비활성화합니다. 이는 실제 장애가 발생한 의존성(dependency)을 재시작하는 역할을 대신합니다. 그 후, 에이전트는 전체 체인을 통해 10개의 새로운 요청을 보내고 스스로 그 시간을 측정합니다.
수정 전에는 연쇄 장애(cascade)로 인해 요청에 2000~5000ms가 소요되었습니다. 수정 후, 새로운 요청들은 평균 46.12ms, 최대 53.91ms를 기록했습니다. 정상 상태로 돌아온 것입니다.
한 가지 인정할 점이 있습니다. 저 또한 두 번째 확인을 위해 Payment의 P95 값을 SigNoz에 쿼리했는데, 그 수치는 제가 새로 측정한 값보다 높게 나왔습니다. 이는 SigNoz의 기본 윈도우(default window)에 이전의 혼란스러운 영향을 받은 트래픽이 깨끗한 요청들과 여전히 섞여 있었기 때문입니다. 제가 직접 시간을 측정하며 보낸 요청들이 진짜 증거였습니다. SigNoz의 수치는 오래된 데이터에 의해 희석된 보조적인 증거일 뿐이었습니다. 이 프로젝트 전체에서 두 가지 "증거" 수치 중 실제로 어떤 것을 신뢰할지 판단하는 것은 그 어떤 코드 한 줄보다 더 중요했습니다.
이것이 실제로 증명하는 것
누구나 알람이 재시작을 트리거하도록 만들 수 있고, 인상적으로 보이는 데모를 녹화할 수 있습니다. 제가 보여주고 싶었던 것은 에이전트의 진단이 단순히 어떤 숫자가 가장 큰지 운 좋게 맞히는 것이 아니라, 실제로 트레이스(trace) 데이터에 근거하고 있다는 점입니다. Self time 버그를 발견하고 이를 수정해야 했던 과정은 솔직히 이 프로젝트에서 가장 다행스러운 부분이었습니다. 왜냐하면 그것이 바로 그저 맞아 보이는 에이전트와 실제로 올바르게 추론(reasoning)하는 에이전트 사이의 차이점이기 때문입니다.
스택 (Stack)
FastAPI, httpx, OpenTelemetry Python SDK, Foundry를 통해 셀프 호스팅된 SigNoz, Docker Compose, SigNoz MCP 서버, 무료 티어의 Gemini 2.5 Flash, 그리고 복구(remediation) 단계를 위한 Docker.
AI 도구 공개: 에디터 내 코드 작성을 위해 GitHub Copilot을 사용하였고, 구축 과정 중 아키텍처 및 디버깅 도움을 받기 위해 Claude를 사용했습니다. Gemini 2.5 Flash는 코드 작성이 아닌, 실제 프로젝트 내부에서 근본 원인 추론(root cause reasoning)을 위해 사용된 모델이며, 실행 중인 시스템의 실제 구성 요소입니다.
casting.yaml 및 casting.yaml.lock이 포함된 리포지토리: https://github.com/shiv-codez/cascade-detective
(해커톤 규칙에 따라 공개한 바와 같이, 디버깅 가이드, 포스트 구조화 및 명확성을 위한 편집 과정에서 Claude의 도움을 받았습니다. 여기에 기술된 모든 설정, 탐색 및 기술적 결정은 제가 직접 수행했습니다.)
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기

