8개 서비스 경고, 1가지 원인: 그래프 기반 AI 온콜 에이전트 구축
요약
본 글은 복잡한 시스템 장애 발생 시, 단순히 경고 목록을 보는 것이 아니라 서비스 간의 '연결(connections)'을 파악하는 그래프 기반 AI 온콜 에이전트 구축 과정을 설명합니다. 이 에이전트는 의존성 추적, 변경 사항 분석, 담당자 식별 등 조사관 역할을 수행하며 시스템 장애 대응 방식을 혁신합니다.
핵심 포인트
- 시스템 장애 원인과 피해자를 구분하는 것이 핵심입니다.
- 서비스 간의 복잡한 관계는 그래프 데이터베이스로 모델링해야 합니다.
- 에이전트는 의존성 추적, 변경 이력, 담당자 정보를 통합 분석합니다.
- Cypher 쿼리 기반의 그래프 탐색은 SQL보다 직관적이고 강력합니다.
새벽 2시입니다. 휴대폰이 울립니다. 결제(Checkout)가 실패하고 있습니다. 프론트엔드도, 결제 서비스도, 모든 것 앞에 있는 프록시도, 그리고 여러분이 한 번도 건드려보지 않은 몇몇 서비스들도 모두 실패했습니다. 총 8개의 경고가 발생했지만, 실제 문제는 그중 단 하나입니다.
무언가를 고치기 전에 세 가지 질문에 답해야 합니다:
- 어떤 서비스가 원인이고, 어떤 서비스는 단순히 피해자인가?
- 그것 근처에서 최근에 무엇이 변경되었는지?
- 누구를 깨워야 하는가?
이러한 답변 중 어느 것도 한 곳에 존재하지 않습니다. 그 정보들은 '연결(connections)' 속에 있습니다. 서비스 A는 B를 호출하고, B는 데이터베이스 C에서 읽어오며, C는 2시간 전 풀 리퀘스트(pull request)로 변경되었고, 이 PR의 작성자는 C를 소유한 팀에 속해 있으며, 이번 주 그 팀의 온콜 담당자는 완전히 다른 사람입니다. 이것은 그래프 문제입니다. 그래서 저는 그래프 위에 에이전트를 구축했습니다.
Blast Radius는 FalkorDB를 컨텍스트(context), 메모리, 감사 추적(audit trail)으로 사용하는 소규모 AI 온콜 에이전트 팀입니다. 저는 Graph Hacks 2026 (WeMakeDevs × FalkorDB)을 위해 이것을 구축했습니다. 이 글에서는 그래프 모델, 실제 작업을 수행하는 쿼리들, 에이전트가 과거 인시던트를 어떻게 기억하는지, 그리고 제가 전체 시스템을 무료 티어 LLM으로 유지한 방법을 설명합니다.
코드: https://github.com/AgentAhmed/blast-radius
에이전트 질문은 그래프 질문이다
인시던트 발생 시 조사관 에이전트가 알아야 할 것과 그 뒤에 숨겨진 쿼리 형태는 다음과 같습니다:
| 질문 | 그래프 형태 |
|---|---|
payment가 실패하면 무엇이 깨지는가? | 의존성을 따라 역방향으로 탐색하는 가변 길이 경로 |
| ... | |
| SQL에서는 이들 대부분이 재귀적 CTE(recursive CTE)이거나 여러 번의 왕복 트랜잭션입니다. Cypher에서는 각각 몇 줄에 불과합니다. 이것은 두 가지 면에서 중요합니다. LLM이 이를 도구로 호출할 수 있고, 사람이 에이전트가 작동하는 방식을 볼 때 읽을 수 있기 때문입니다. |
그래프 모델
각 조직은 동일한 FalkorDB 인스턴스 내에 자체 그래프(org_<tenant>)를 가지므로, 회사 간 메모리 누수가 발생하지 않습니다.
- 토폴로지 (Topology):
Component노드들(서비스, 데이터스토어, Kafka 토픽)이CALLS,READS_WRITES,PUBLISHES,CONSUMES엣지를 가지고 있고, 사용 흐름(결제, 탐색, 장바구니)에 전력을 공급하는SERVES엣지도 포함합니다. - 사람 및 소유권 (People and ownership):
Person-MEMBER_OF->Team-OWNS->Component, 그리고 온콜(on-call) 근무 일정입니다. - 변경 사항 (Change):
PullRequest-MODIFIES->Component,Person-AUTHORED->PullRequest가 있습니다. - 지식 (Knowledge): 설계 결정(ADRs), 런북(runbooks) 및 문서들이 해당 구성요소와 연결됩니다.
- 기억 (Memory):
Incident,Alert,Action, 그리고Lesson이 있으며, 이들은ROOT_CAUSE,IMPACTED,RESOLVED_BY엣지를 가집니다. - 에이전트 자체 (The agents themselves): 모든 LLM 호출 및 도구 호출을 위한
AgentRun및Step노드, 핸드오프(hand-offs)를 위한Task노드, 그리고 발견된 내용에서 정확히 인용하는SUPPORTED_BY엣지가 있습니다.
가장 중요했던 모델링 결정은 나머지 것들보다 더 많았습니다.
에이전트의 상태는 데이터와 같은 그래프 내에 존재합니다. 쿼리를 실행한 단계(step)는 읽은 노드들에 TOUCHED 엣지를 가집니다.
- covers: 해당 서비스에 얼마나 많은 경고 서비스가 의존하는지 보여줍니다. 실제 원인은 모든 증상을 설명해야 합니다.
- is_alerting: 이 자체가 증상을 보이고 있다는 의미입니다.
- alerting_deps (적을수록 좋음): 만약 자체 종속성(dependencies)이 경고를 보내고 있다면, 그 역시 피해자일 가능성이 높습니다.
- recent_changes: 최근에 근처에서 병합된 무언가가 의심스럽습니다.
*0..6은 의도적인 설정입니다. 홉 수(hops)가 0이라는 것은 경고를 보내는 서비스 자체가 원인일 수 있다는 의미입니다.
제가 가장 좋아하는 테스트 케이스는 OpenTelemetry Demo의 kafkaQueueProblems 장애 사례입니다. 경고를 보내는 서비스인 accounting과 fraud-detection은 서로 호출하지 않습니다. 이들이 공유하는 유일한 것은 Kafka 토픽인 orders이며, 이 토픽 자체는 절대 경고를 보내지 않습니다. 로그를 검색한다고 해서 절대로 그 지점을 가리킬 수 없을 것입니다. 하지만 두 컨슈머(consumers)가 이 토픽에 의존하기 때문에 쿼리는 이를 지목합니다.
LLM이 그래프를 무시하게 두지 마세요
LLM (무료 티어 오픈 모델)은 어떤 도구를 호출할지 결정하고 최종 결과를 작성합니다. 초반에는 때때로 올바른 쿼리를 실행하고, 올바른 답변을 받아낸 후, 다른 것을 제출하는 경우가 있었습니다. 보통 가장 큰 소음을 내는 경고 서비스가 그 경우였습니다.
그래서 이제 그래프가 답변을 검증합니다. 조사관이 근본 원인(root cause)을 제출하면, 커버리지 확인(coverage check)이 다시 실행됩니다:
- 만약 다른 구성 요소가 경고의 더 많은 부분을 설명한다면, 답변은 숫자와 함께 한 번 되돌아옵니다:
Text similarity만으로는 '결제 오류(payment errors)'와 '결제 지연(payment latency)'을 혼동하기 쉽습니다. 여기에 그래프에서 얻은 '같은 근본 원인 컴포넌트(same root-cause component)'와 '겹치는 영향 서비스(overlapping impacted services)' 정보를 추가하면 매칭이 훨씬 더 정교해집니다. 제가 시간을 좀 낭비한 FalkorDB의 세부 사항 하나: queryNodes는 코사인 _거리값(cosine distance)_을 반환하므로 유사도(similarity)는 1 - score가 됩니다.
임베딩은 로컬(bge-small-en-v1.5를 통해 fastembed 사용)로 처리됩니다. 재현율(Recall)은 비용이 들지 않고 속도 제한에 걸릴 일이 없습니다.
그래프를 통해 작업을 인계하는 에이전트들
총 네 가지 에이전트가 있습니다:
- Triage: 경고(alerts)들을 하나의 사고(incident)로 그룹화하고 심각도(severity)를 설정합니다. LLM을 사용하지 않습니다: 순수한 그래프 로직이기 때문에 즉각적이고 무료입니다.
- Investigator: 원인을 찾아내고 그에 따라 조치를 취합니다.
- Scribe: 사람이 사고를 해결한 후 메모를 작성합니다.
- Company brain: '왜 체크아웃이 Kafka로 게시하는지, 그리고 누가 그렇게 결정했는지?'와 같은 질문에 답합니다.
이들은 서로 직접 호출하지 않습니다. 대신 Task 노드를 통해 조정됩니다:
- Triage는
investigate작업을 생성합니다. - 작업자(worker)가 가장 오래된 미처리 작업(open task)을 가져와서
claimed(요청됨)으로 표시하는 단일 Cypher 쿼리로 이를 확보합니다. FalkorDB는 그래프에 쓰기 작업을 한 번에 하나씩 적용하므로, 두 명의 작업자가 같은 작업을 요청할 수 없습니다. - 사람이 사고를 해결하면,
record작업이 Scribe에게 이 작업을 전달합니다.
만약 작업자가 죽더라도, 해당 작업은 여전히 그래프 안에 남아있고, 누가 무엇을 처리하고 있는지 누구나 확인할 수 있습니다.
무료 티어 속도 제한 극복하기
저는 이것이 무료 LLM 티어에서 실행되기를 원했기 때문에, 속도 제한(rate limits)은 예외 상황이 아니라 일반적인 문제입니다. 두 가지 요소가 이를 가능하게 만듭니다.
폴백 체인(fallback chain). Groq를 먼저 사용하고, 그 다음으로 설정된 다른 모든 제공업체(Gemini, OpenRouter, OpenAI, Claude 또는 OpenAI와 호환되는 모든 엔드포인트)를 순차적으로 사용하며, 마지막으로 로컬 Ollama 모델을 사용합니다. 오류는 분류되며(속도 제한, 잘못된 요청, 잘못된 키, 모델 없음, 제공업체 다운), 각 모델은 재시도 루프에 반복적으로 호출되는 대신 쿨다운 시간을 갖습니다.
그래프 내 체크포인트(Checkpoints in the graph). 모든 LLM 응답과 모든 도구 결과는 Step 노드가 되며, NEXT로 연결됩니다. 모든 제공업체가 소진되면 실행은 paused 상태로 표시됩니다. 나중에 워커가 저장된 단계들을 통해 대화를 재구성하고 멈췄던 지점부터 정확하게 계속 진행합니다. 아무것도 다시 조회되지 않으며 아무것도 손실되지 않습니다.
제가 실제 무료 등급(free tier)으로 테스트하면서 발견한 버그 중 하나는 다음과 같습니다: 긴 시간 동안 일시 정지된 후, 재개된 실행이 때때로 예산이 소진될 때까지 쿼리 도구를 계속 호출하여 아무런 답변 없이 끝나는 경우가 있었습니다. 수정은 간단했습니다. 예산에 도달하면 모델에게 오직 최종 답변(final-answer) 도구만 제공합니다. 그러면 더 이상 방황할 수 없으므로, 이미 찾은 내용을 요약하게 됩니다.
작동하는가?
저는 OpenTelemetry Demo의 8가지 장애 플래그 각각을 새로운 그래프에 주입하고, 실제 조사관(investigator)을 실행한 다음, 숨겨진 정답(ground truth)과 비교하여 점수를 매기는 평가를 작성했습니다. 그런 다음 사고를 해결하고 동일한 장애를 다시 주입하여 메모리를 테스트합니다.
| 첫 발생 시 | 같은 장애 재발 시 | |
|---|---|---|
| 근본 원인 정확도 | 8/8 | 8/8 |
| ... | ||
| 이 실행 과정에서, 무료 등급은 24번의 모델 폴백과 34번의 전체 일시 정지를 강제했습니다. 모든 일시 정지된 실행은 체크포인트부터 재개되어 완료되었습니다. |
이는 솔직하지만 작은 테스트입니다: 현실적인 토폴로지에서 시뮬레이션된 8가지 장애. 실제 사고는 부분적 중단과 여러 가지 문제가 동시에 발생하는 등 더 복잡합니다.
데모가 아닌 실제 데이터
데모 데이터셋만으로는 많은 것을 증명할 수 없기 때문에, 실제 데이터를 위한 두 가지 방법이 있습니다:
- GitHub: 공개 레포지토리의 어느 곳이든 지정할 수 있습니다. 서비스와 의존성은 compose 파일(환경 변수 내
http://payment:8080같은 호스트 이름 포함)에서 가져오고, 소유자는CODEOWNERS에서, 변경 사항과 전문가 정보는 병합된 PR에서 가져오며, 이 모든 것이 파일 경로에 따라 컴포넌트로 매핑됩니다. OpenTelemetry Demo 레포지토리에서는 26개의 컴포넌트와 43개의 의존성을 찾았습니다. GitHub 로그인은 기본적으로 안정적인 가명으로 저장됩니다. - Alerts: Alertmanager와 호환되는 웹훅(webhook)이며, Grafana에서도 여기에 게시할 수 있습니다.
service,app과 같은 레이블이나 파드 이름(checkout-5c9d7f-q2w8r)은 컴포넌트에 매칭됩니다. 일치하지 않는 모든 것은 추측 없이 보고됩니다.
그리고 에이전트는 실제 도구에서 작동합니다:
- 근본 원인, 증거, 권장 수정 사항을 담은 GitHub 이슈를 생성합니다. 토큰은 세분화되어 있어 하나의 레포지토리와 'Issues' 권한만 가집니다.
- 원인이 식별되었을 때(심각도, 근본 원인, 신뢰도, 누가 알림을 받았는지, 이슈 링크) Slack 또는 Discord 메시지를 보내고, 문제가 해결되었을 때 또 다른 메시지를 보냅니다.
모든 작업은 인시던트당 고정 ID를 가지므로, 재시도하거나 다시 실행해도 두 번 알림이 가거나 중복된 이슈가 생성되지 않습니다.
제가 배운 것 (What I learned)
- LLM에게 데이터베이스 대신 쿼리를 제공하세요. 저는 모델이 Cypher를 작성하도록 내버려두는 대신, 이름이 지정되고 테스트된 그래프 도구 16개를 제공했습니다. 모든 쿼리는 제가 테스트하고 설명할 수 있는 것이며, UI에는 각 단계 뒤에 숨겨진 정확한 쿼리가 표시되고, 모델은 제가 작성하지 않은 쿼리를 실행할 수 없습니다.
- 그래프가 모델을 검사하게 하세요. 단순히 정보를 공급하는 것에 그치지 않도록 해야 합니다.
- 에이전트 상태를 데이터 옆에 유지하세요. 체크포인트(Checkpoints), 핸드오프(Hand-offs), 증거 등을 노드와 엣지로 처리하여, 거의 공짜로 재개 기능, 출처 추적(Provenance), 감사 기록(Audit trail)을 얻었습니다.
다음 계획 (What's next)
compose 파일 대신 실시간 OpenTelemetry 트레이스에서 의존성을 구축하고, 해결된 인시던트를 사용하여 랭킹 가중치(ranking weights)를 조정하며, 에이전트가 승인된 수정 사항(기능 플래그 전환 또는 롤백)을 인간의 승인 단계 뒤에서 실행하도록 하는 것입니다.
사용해 보세요 (Try it)
Docker와 무료 Groq 키, 또는 키 없이 Ollama로 로컬에서 실행할 수 있습니다.
git clone https://github.com/AgentAhmed/blast-radius && cd blast-radius
docker compose up -d
then follow the Quick start in the README
paymentFailure를 주입하고, 조사관(investigator)이 이를 추적하고 해결하는 것을 지켜본 다음, 다시 주입하여 기억하는지 확인합니다.
코드, Cypher 쿼리, 설계 문서 및 평가는 모두 GitHub에 있습니다:
https://github.com/AgentAhmed/blast-radius
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기