
Hindsight Memory가 어떻게 내 챗봇을 사고 대응 지휘관(Incident Commander)으로 만들었나
요약
SRE 팀의 반복되는 장애 대응 문제를 해결하기 위해 Hindsight Memory를 활용한 'On-Call Copilot' 구축 사례를 소개합니다. 과거의 사고 기록을 회상하고 분석하여 근본 원인을 도출하는 에이전트 시스템의 아키텍처를 다룹니다.
핵심 포인트
- Hindsight Memory를 통한 조직 내 지식의 자산화
- 과거 사고 회상, 분석, 원인 생성, 초안 작성의 4단계 워크플로우
- FastAPI와 React 기반의 단순하고 효율적인 에이전트 아키텍처
- LLM을 활용한 온콜 엔지니어의 인지 부하 감소 및 대응 속도 향상
목요일 밤 11시 45분, 우리의 결제 서비스(checkout service)에서 503 에러가 발생하기 시작했습니다. 당시 온콜(on-call) 담당자는 저였습니다. 저는 로그를 확인하고, Slack으로 팀원 세 명에게 알림을 보내고, 유사한 사고 이후 누군가 절반만 작성해 두었던 일주일 전의 Notion 문서를 뒤졌습니다. 그리고 다음 52분 동안 조사를 거듭한 끝에, 근본 원인이 부하 상황에서 세션 토큰을 조용히 삭제하는 Redis 캐시 제거 정책(cache eviction policy)이라는 것을 알아냈습니다. 이는 우리 팀이 이미 4개월 전에 진단하고 해결했던 문제였습니다. 하지만 아무도 기억하지 못했습니다. 해결책은 아무도 검색할 생각을 하지 못한 채 닫힌 Jira 티켓 안에 잠들어 있었습니다.
그날 밤, 저는 다음 온콜 담당자가 처음부터 다시 시작하지 않도록 무언가를 만들기로 결심했습니다. 그것은 바로 메모리(memory)의 문제입니다.
아무도 말하지 않는 패턴
모든 SRE 팀은 똑같은 비밀을 공유하고 있습니다. 우리는 똑같은 사고를 반복해서 해결한다는 것입니다. 증상은 약간씩 변하고, 런북(runbooks)은 낡아지며, 자정에 온콜 중인 엔지니어는 동료가 이미 6주 전에 구축해 놓은 것과 동일한 추론 체인을 다시 재구성합니다.
지식은 존재합니다. 다만 에이전트(agent)가 찾을 수 있는 곳에 머물러 있지 않을 뿐입니다.
On-Call Copilot을 만들기 시작했을 때, 저는 단순히 사고 설명(incident description)을 LLM(Large Language Model)으로 감싸는 또 다른 챗봇을 원하지 않았습니다. 저는 팀이 장애를 해결할 때마다 더 빠르고 정확해지며 실제로 학습하는 무언가를 원했습니다. 그 핵심은 Hindsight agent memory였습니다.
시스템의 역할
On-Call Copilot은 FastAPI 백엔드와 React/TypeScript 프론트엔드가 결합된 시스템으로, 온콜 엔지니어에게 분류(triage)를 위한 단일 인터페이스를 제공합니다. 사고가 발생하면 엔지니어는 현재 상황을 설명하거나 일반적인 사고 프리셋(incident presets) 중에서 선택하며, 시스템은 다음 네 가지 작업을 순차적으로 수행합니다:
- Hindsight를 통해 조직의 메모리(organizational memory)로부터 유사한 과거 사고(historical incidents)를 회상 (Recalls)
- Groq LLM을 사용하여 현재 사고 설명을 분석 (Analyzes)
- 근거를 포함한 개연성 있는 근본 원인(root cause)을 생성 (Generates)
- 고객 대상 상태 업데이트 초안을 작성 (Drafts)
5개의 라이브러리를 엮어 만든 RAG 파이프라인도 필요 없고, 직접 호스팅할 벡터 데이터베이스(vector database)도 필요 없습니다. Hindsight가 메모리 계층(memory layer)을 완전히 처리해주기 때문에, 저는 인프라보다는 추론 로직(reasoning logic)에 집중할 수 있었습니다.
핵심 기술 이야기: 메모리를 운영 가능하게 만들기
여기서 흥미로운 엔지니어링 과제는 LLM 통합이 아니었습니다. Groq의 API는 직관적이며, 잘 구조화된 프롬프트(prompt)로부터 근본 원인 분석(root cause analysis)을 생성하는 것은 이제 기본(table stakes) 수준입니다. 진짜 어려운 부분은 과거의 사고 지식을 쿼리 시점(query time)에 실제로 유용하게 만드는 것이었습니다.
backend/memory.py의 메모리 통합 핵심 구조는 다음과 같습니다:
from hindsight import HindsightClient
client = HindsightClient(
...
단 두 개의 함수입니다. 이것이 메모리 계층의 전부입니다. retain은 해결된 사고를 Hindsight의 벡터 스토어(vector store)에 기록합니다. recall은 사고 발생 시 이를 의미론적(semantically)으로 쿼리합니다. bank_id는 메모리의 범위를 귀하의 조직으로 제한합니다. 즉, 공유된 글로벌 풀에서 가져오는 것이 아니라, 귀하의 팀이 가진 특정 사고 이력을 쿼리하게 됩니다.
제가 놀란 점은 Hindsight가 비정형화된 사고 설명(unstructured incident descriptions)에서 얼마나 많은 신호(signal)를 추출해내는가 하는 것이었습니다. 엔지니어가 "피크 로드 중 결제 시간 초과(payments timing out during peak load)"라고 입력할 때, 리콜(recall)은 단순히 "payments"나 "timeout"이라는 키워드만 일치시키는 데 그치지 않습니다. 데이터베이스 지연으로 인해 다운스트림 웹훅 실패가 발생했던 사고, 트래픽 급증 시 연결 풀 제한에 도달했던 사고, 그리고 비동기 작업 큐(async job queues)가 백업되었던 사고 등을 찾아냅니다. 여기서 의미론적 계층(semantic layer)이 실제로 작동하는 것입니다.
에이전트 추론 루프 (The Agent Reasoning Loop)
backend/agent.py 파일은 역사적 메모리와 실시간 LLM 추론이 결합되는 곳입니다. /analyze 엔드포인트를 통해 사고가 접수되면, 에이전트는 다음 순서를 실행합니다:
async def analyze_incident(description: str) -> IncidentAnalysis:
# Step 1: Hindsight에서 관련 과거 사고를 가져오기
historical = recall_similar_incidents(description, top_k=3)
...
여기서 핵심 설계 결정은 역사적 사고 이력을 LLM이 현재 사고에 대해 추론하기 전에 프롬프트에 주입하는 것입니다. Hindsight가 없다면, 에이전트는 일반적인 훈련 데이터(general training data)를 기반으로 추론하게 됩니다. 이는 유용하지만, 너무 일반적입니다. 하지만 프롬프트 컨텍스트에 Hindsight 리콜을 포함하면, LLM은 귀하의 팀 실제 해결 이력(actual resolution history)을 기반으로 추론합니다. 즉, 지난 세 번의 Stripe 웹훅 시간 초과 사례에서 근본 원인은 데이터베이스 지연이었고, 해결책은 연결 풀 제한 늘리기와 인보이스 처리를 비동기 워커로 옮기는 것이었다는 것을 알고 있습니다.
이것은 의미 있게 다른 결과물입니다.
Before/After 비교
구체적인 예시를 들어보겠습니다. 사고 설명은 다음과 같습니다:
"Stripe 웹훅 처리가 인보이스 생성 중에 시간 초과되고 있습니다. 결제가 지연되며 구독이 활성화되지 않고 있습니다."
Hindsight 메모리 없이 (일반 LLM 응답):
- 근본 원인 (Root cause): "가능한 네트워크 문제, 제3자 API 다운타임, 또는 잘못 설정된 웹훅 (webhook) 엔드포인트"
- 해결책 (Fix): "Stripe 대시보드 확인, 웹훅 로그 검토, 엔드포인트 가용성 확인"
- 유용성? 거의 없음. 어떤 엔지니어라도 Stripe 대시보드를 확인해야 한다는 점은 이미 알고 있습니다.
Hindsight 메모리 사용 시 (6개월간의 장애 이력 이후):
- 근본 원인 (Root cause): "데이터베이스 지연 (latency)으로 인해 웹훅 처리 시간이 30초 타임아웃 제한을 초과했습니다. 2024-09-14 및 2024-11-02에 발생한 유사한 장애들도 동일한 징후를 보였습니다 — 구독 갱신 배치 작업 중
invoices테이블에서 높은 읽기 지연 발생." - 해결책 (Fix): "
(subscription_id, status)에 인덱스를 추가하여invoices쿼리를 최적화하십시오. Stripe 대시보드에서 웹훅 타임아웃을 60초로 늘리십시오. 인보이스 생성을 비동기 Celery 태스크로 이동하십시오 — 11월 장애 관련 PR #847을 참조하십시오." - 고객 업데이트 (Customer update): "결제 처리 및 구독 활성화에 영향을 미치는 지연 문제를 인지하고 있습니다. 저희 팀은 근본 원인을 파악했으며 수정 사항을 적용 중입니다. 서비스는 30분 이내에 완전히 복구될 것입니다."
두 번째 응답은 단순히 더 구체적일 뿐만 아니라, 당신의 과거 작업, 당신의 특정 테이블 이름, 당신의 이전 PR을 참조하고 있습니다. 이것이 조직의 메모리 (organizational memory)가 추론 루프 (reasoning loop)에 실제로 연결되었을 때 나타나는 모습입니다.
대규모 메모리 시딩 (Seeding Memory at Scale)
시스템의 성능은 당신이 보유한 장애 이력의 질에 달려 있습니다. 새로 시작하는 팀을 위해, 저는 연결 풀 고갈 (connection pool exhaustion), 포드 OOMKill 사이클, 결제 프로세서 타임아웃과 같은 대표적인 장애 패턴을 Hindsight에 미리 채워 넣을 수 있도록 backend/seed_data.py를 구축했습니다. 이를 통해 실제 장애 이력이 축적되어 점진적으로 주도권을 잡기 전까지, 첫날부터 유용한 회상 (recall) 기능을 제공할 수 있습니다.
프론트엔드에서 노출되는 항목
제가 구축한 React 프론트엔드는 사고 대응 (incident response)의 네 가지 단계인 사고 입력 (incident input), 메모리 회상 (memory recall), 분석 결과 (analysis results), 그리고 실시간 텔레메트리 콘솔 (live telemetry console)에 직접적으로 매핑됩니다. 가장 중요한 부분은 메모리 회상 뷰였습니다. 엔지니어들에게는 단순히 블랙박스 형태의 출력값이 아니라, 어떤 과거 사고들이 추천을 이끌어내고 있는지 보여줄 필요가 있었습니다. 검색 (retrieval)의 투명성은 신뢰를 구축합니다. 근본 원인 (root cause)이 자신의 이력에 있는 세 가지 실제 사고에 근거하고 있다는 것을 확인할 수 있을 때, 더 빠르게 조치를 취할 수 있습니다.

다음 시스템을 위해 얻은 교훈
1. 모델 크기보다 메모리 품질이 더 중요하다. Hindsight 회상 (recall) 기능을 갖춘 Llama 3 8B는 이 기능이 없는 GPT-4o보다 도메인 특화 사고 분석에서 더 나은 성능을 보였습니다. 컨텍스트 (context)가 파라미터 (parameters)를 이깁니다.
2. 생성 시점이 아닌 해결 시점에 저장하라. 사고가 시작되는 시점에는 근본 원인을 알 수 없습니다. 전체 그림이 그려지는 사고 종료 후에 저장하십시오.
3. 메타데이터 필터링 (Metadata filtering)이 회상을 정밀하게 만든다. 서비스, 심각도 (severity), 또는 날짜 범위를 기준으로 회상 범위를 제한하십시오. P1 데이터베이스 사고와 P3 CSS 버그가 서로 검색되어서는 안 됩니다.
4. 작업 과정을 보여주라. 메모리 검색 (memory retrieval)의 투명성은 신뢰를 구축합니다. 엔지니어들은 어떤 과거 사고들이 추천을 이끌어내고 있는지 볼 수 있을 때 추천 사항에 대해 더 빠르게 행동합니다.
5. 시드 데이터 (Seed data)는 강제적인 동기 부여 요소다. 현실적인 시드 사고를 작성하는 과정은 실제 데이터가 축적되기 전에 메모리 스키마 (memory schema)를 정의하도록 강제합니다. 설령 즉시 덮어쓰게 되더라도 그럴 만한 가치가 있습니다.
향후 방향
다음 단계는 선제적 회상 (proactive recall)입니다. 관측 가능성 도구 (observability tooling)로부터 이상 징후 신호가 들어오면, 에이전트는 경고가 담당자에게 전달되기도 전에 Hindsight에서 일치하는 과거 패턴이 있는지 확인합니다. Hindsight documentation에서는 이를 간단하게 구현할 수 있는 웹훅 (webhook) 기반의 유지 흐름 (retain flows)을 다룹니다. Hindsight GitHub repo에는 시작하는 데 필요한 모든 것이 있습니다.
팀이 해결하는 모든 장애 (incident)는 조직적 기억 (institutional memory)의 한 조각입니다. 문제는 이 기억이 누군가의 머릿속에 있는지, 아무도 읽지 않는 런북 (runbook)에 있는지, 아니면 새벽 2시에 이를 표면화해 주는 시스템에 있는지입니다. 제가 이 프론트엔드를 직접 만든 이유는 온콜 (on-call) 근무자가 밤 11시 45분에 로그 벽을 멍하니 바라보는 상황의 반대인, 차분하고 명확한 상태를 느끼길 원했기 때문입니다. 실제 장애 세트 (incident suite)를 통해 테스트해 본 결과, 저는 언제든 Notion 문서와 기도에 의존하는 것보다 이 시스템을 선택할 것입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기

