Skip to content
게으른 엔지니어의 기술 블로그
Go back

Claude Code한테도 메모리를 붙여주려다가 3편 — 판단 기준을 만들다가 장애부터 찾았다

지난 글 끝에 답을 못 낸 질문이 하나 남아 있었다 — Claude Code한테도 Honcho를 붙일 만한가. 이번엔 짐작으로 넘어가지 않고, 판단 기준 세 가지를 정해서 순서대로 확인해보기로 했다.

  1. 놓친 것 찾기: 이번 세션에서 나온 사실 중, Claude Code가 직접 관리하는 메모리 파일엔 저장 안 했지만 나중에 다시 물어볼 법한 것들을 Honcho가 정확히 기억해내는지.
  2. A/B 격리 비교: 같은 정보를, Honcho 없이 grep/git만으로 찾을 수 있는지와 비교.
  3. 비용/효과: 붙여두는 데 드는 실제 비용(인프라, 지연, 안정성)과 이득을 맞춰보기.

1번을 돌리기도 전에 이상한 걸 봤다

테스트용 workspace를 하나 파고, 이번 세션에서 있었던 사실 4개(Fire Pass 신청서 내용, Cloudflare catch-all 설정 이유, 이메일 dedup 오탐 진단, Levenshtein 임계값 결정 근거)를 메시지로 넣었다. 그런데 넣고 나서 바로 물어보러 가기 전에, 습관적으로 큐 상태부터 확인해봤다.

representation 타입 큐가 비어 있지 않았다. 처리 대기 중인 항목이 여러 개 쌓여 있는데, 가장 오래된 것의 생성 시각이 전날 오후였다. Honcho 컨테이너는 멀쩡히 떠 있고 docker compose ps로 봐도 다 정상, hermes memory status도 “정상 작동 중”이라고 보고하는데, 정작 실제 메모리 추출 작업은 멈춰 있었다.

왜 멀쩡해 보였나 — 두 종류의 큐가 섞여 있었다

Postgres 큐 테이블을 직접 까보니 task_type이 두 가지였다.

디라이버 컨테이너가 죽지 않고 로그도 계속 찍히던 이유가 이거였다 — reconciler는 정상 작동 중이었고, 진짜 문제였던 representation 처리만 조용히 멈춰 있었다. docker ps나 헬스 상태 표시는 컨테이너가 살아있는지만 보지, 큐 안에서 어떤 종류의 작업이 실제로 진행되는지는 안 본다. 그래서 겉보기엔 아무 문제 없어 보였던 거다.

멈춘 시각을 특정해보니 전날 오후 4시 40분경이었다. 재현 가능한 에러 로그는 없었다 — 그냥 그 시점부터 representation 타입만 조용히 안 돌기 시작했다.

재시작하고, 진짜 고쳐졌는지 지켜봤다

docker compose restart deriver로 디라이버를 재시작했다. 재시작했다고 바로 “고쳐졌다”고 믿진 않았다 — 밀려 있던 항목들이 실제로 처리되는지, 그리고 그 이후에 새로 들어간 항목도 정상적으로 처리되는지 직접 확인해야 했다.

밀려 있던 항목들은 재시작 직후 바로 처리됐다. 그런데 그 다음에 새로 넣은 테스트 메시지 하나는 한동안 안 줄어들길래 순간 다시 걸렸나 싶었는데, 알고 보니 설정값 때문이었다. 배치 처리 정책이 “최대 30분까지는 모아뒀다가 한 번에 처리”하는 방식이라, 토큰 수가 적은 작업은 이 30분 창을 다 채울 때까지 정상적으로 대기하는 거였다. 30분이 지나자 그 항목도 처리됐다. 재시작이 확실히 효과가 있었다는 뜻이다.

다시 1번으로 — 이번엔 실제로 물어봤다

디라이버가 정상 작동하는 걸 확인한 뒤, 원래 하려던 테스트를 진행했다. 아까 넣어둔 4개 사실을 자연어로 다시 물어봤다.

질문결과
Fire Pass 신청서 제목/본문에 뭐라고 썼나정확히 재현
catch-all을 왜 안 켰나”존재하지 않는 주소로 오는 메일을 차단하기 위해서”까지 정확
Missing email 알림이 왜 왔나라우팅 고장이 아니라 발신/수신 계정 동일로 인한 Gmail dedup이라고 정확히 설명
Levenshtein 임계값을 왜 그렇게 정했나”긴 쪽 길이의 15%(최소 1)”, 그 이유까지 정확

4개 다 정확했다. 이 정보들은 이번 세션에서 나온 것 중 Claude Code가 직접 관리하는 메모리 파일엔 하나도 저장하지 않은 것들이었다 — 코드 패턴도, 디버깅 해법도, 재사용 가능한 사실도 아니라서 저장 기준에 안 맞았기 때문이다. 근데 Honcho는 grep 없이도 다 recall했다.

2번 — 진짜 grep으로는 못 찾나 확인해봤다

recall이 잘 됐다고 바로 “Honcho가 유용하다”고 결론 내리긴 일렀다. 어차피 코드나 문서 어딘가에 다 적어놨던 내용이면, Honcho 없이 grep만으로도 찾을 수 있는 정보일 수도 있으니까. 그래서 같은 4개 질문을 이번엔 Honcho 없이, 순수하게 저장소/문서 검색만으로 답을 찾을 수 있는지 확인했다.

4개 중 3개(75%)는 Honcho가 아니었으면 이 세션이 끝나는 순간 완전히 사라졌을 정보였다. 반대로 나머지 1개처럼 “코드/설계 결정”급으로 판단되는 건 애초에 문서에 남기는 습관이 있어서 Honcho가 없어도 문제없었다. 정리하면 Honcho가 채우는 빈틈은 정확히 “문서화할 만큼 중요하다고는 판단 안 되는, 그런데 나중엔 다시 궁금해질 수 있는 과정성 대화”였다.

3번 — 비용은 얼마나 드나

마지막으로 비용을 확인했다. 리소스는 컨테이너 네 개(api/deriver/redis/database) 합쳐서 메모리 650MB 미만, CPU는 idle 상태에서 1% 미만이었다. 내부적으로 쓰는 LLM도 representation 추출부터 dialectic 응답까지 전부 저가/경량 모델 하나로 통일돼 있었고(2편에서 이미 검증하고 바꿔둔 설정), 실사용량도 최근 24시간 기준 representation 처리가 5건뿐이라 사실상 비용은 무시할 수준이었다.

문제는 비용이 아니라 신뢰성이었다. 이번에 직접 겪었듯, 대화를 저장하는 작업이 16시간 넘게 조용히 멈춰 있어도 기본적인 상태 확인으로는 전혀 안 잡혔다. 우연히 큐 상태를 먼저 확인해보지 않았다면, 이번 테스트 결과 자체가 “Honcho는 아무것도 기억 못 한다”는 잘못된 결론으로 이어졌을 수도 있다.

결론 — 붙이되, 감시 장치를 먼저 달았다

세 가지를 종합하면 이렇다. 원가는 거의 무시할 수준이고, 실질적인 이득(75%의 recall 성공률)은 A/B로 확인됐다. 다만 조용히 멈춰도 아무도 모른다는 게 진짜 리스크였다.

그래서 Claude Code에 Honcho를 붙이기로 하면서, 동시에 이 장애를 재발 방지하는 장치도 같이 만들었다. representation 큐에서 가장 오래된 미처리 항목의 나이를 30분마다 확인해서, 정상 배치 지연(최대 30분)의 두 배인 1시간을 넘기면 메일로 알려주는 cron 작업이다. 복구되면 복구됐다고 한 번 더 알려주고, 같은 장애가 계속되는 동안엔 중복으로 메일을 보내지 않는다.

# 30분마다 실행
OLDEST_AGE=$(psql ... "SELECT EXTRACT(EPOCH FROM (now() - min(created_at)))
             FROM queue WHERE processed = false AND task_type = 'representation'")

if [ "$OLDEST_AGE" -gt 3600 ]; then
  # 1시간 넘게 밀려 있으면 알림, 이미 보낸 상태면 중복 발송 안 함
fi

이번 케이스에서 남는 생각은, “메모리 시스템을 붙일까 말까”라는 질문에 답하려다가 정작 그 시스템이 멈춰 있다는 걸 먼저 찾았다는 점이다. 판단 기준을 세우고 실험을 설계하는 것도 중요하지만, 그 실험이 딛고 서 있는 인프라 자체가 멀쩡한지부터 확인하지 않으면 실험 결과 자체를 잘못 해석할 수 있다는 걸 새삼 느꼈다.


Share this post:

Previous Part
Claude Code한테도 메모리를 붙여주려다가 2편 — 모델이 아니라 반복 횟수였다
Next Part
Claude Code한테도 메모리를 붙여주려다가 4편 — 틀리는 게 아니라 지어내고 있었다, 그래서 답변 모델만 되돌렸다