
프로세스는 살아있는데 기능만 죽는다 — 멀티 에이전트 운용에서 겪은 장애 카테고리 분류학
요약
멀티 에이전트 시스템 운용 중 프로세스는 정상이나 기능만 작동하지 않는 '가짜 정상(healthy-but-stuck)' 장애 사례를 분석합니다. 툴 호출 시 발생하는 파싱 오류 등 에이전트 시스템의 특수한 장애 카테고리를 분류하고 진단법을 제시합니다.
핵심 포인트
- 프로세스 생존 지표(Liveness)와 실제 기능 수행 여부의 괴리 발생
- 툴 호출 직전 불필요한 토큰 혼입으로 인한 API 파싱 실패 문제
- 간접 지표(Proxy)에 의존하는 모니터링의 한계와 진단 필요성
- 분산 에이전트 환경에서의 장애 카테고리 분류 및 대응 전략
우리는 생선 도매 회사로, 여러 AI 에이전트(Claude Code 인스턴스 군)를 로컬 메시지 브로커(Message Broker)를 통해 연계시켜 일상 업무를 수행하고 있다. 사령탑 역할을 하는 에이전트가 지시를 내리고, 하위 에이전트들이 각각 담당 영역을 처리하며, 그 결과를 브로커를 통해 보고하는—식의 분산 구성이다.
이 운용 과정에서 우리를 여러 번 괴롭혔던 것이 바로 「OS 프로세스는 살아있고, 헬스 체크(Health Check)도 초록색인데, 기능만 죽어 있는」 중간 단계의 장애였다. 프로세스가 떨어지면 모니터링 시스템이 즉시 알아차린다. 하지만 「살아있는 것처럼 보이면서 실제로는 일을 하지 않는」 상태는 생존 지표가 모두 정상(Healthy)을 가리키기 때문에, 탐지가 결정적으로 늦어진다.
본 기사에서는 우리가 실제로 겪었던 이 종류의 장애를 4가지 카테고리로 분류하고, 각각의 증상·진인(True Cause)·진단 절차·대처법을 분류 매트릭스로 정리한다. 실패를 구조로서 남기는 것 자체가 우리의 방침(퇴적 전략)이며, 같은 실수를 반복하는 사람들에게 지도가 되기를 바란다.
왜 「살아있는데 죽는가」
분산 시스템의 생사 확인(Liveness Check)은 대개 대리 신호(Proxy)에 의존한다. 프로세스의 가동 여부, last_seen 타임스탬프, 커맨드의 exit 0, 에이전트 자신의 「done」이라는 자기 신고—이것들은 모두 「실제로 기능하고 있는가」를 대신하여 관측되는 간접 지표다.
문제는 이러한 대리 신호가 정상임을 나타내더라도, 다른 레이어(Layer)에서 기능이 멈춰 있을 수 있다는 점에 있다. 전송은 성공으로 처리되는데 수신 측에 도달하지 않는다. 프로세스는 응답하는데 전송만 무시된다. 우리는 이를 「가짜 정상(healthy-but-stuck)」이라고 부른다.
이하, 4가지 카테고리를 살펴본다. 카테고리 1은 이해하기 쉬운 기지의 장애이며, 카테고리 2~4가 「가짜 정상」의 핵심이다.
카테고리 1: 진정한 세션 사망
가장 이해하기 쉬운 장애. 에이전트의 프로세스(세션) 자체가 죽는 경우다.
- 생존 지표:
last_seen이 오래됨(stale). 폴링(Polling)이 멈추므로 한눈에 알 수 있다. - 증상: 아무것도 움직이지 않음. 지시를 보내도 무반응.
- 진인: 프로세스 크래시(Crash), 강제 종료, PC 재부팅 등.
- 대처: 세션을 재개(
--resume에 상당하는 복구 작업)하면 돌아온다.
이것은 모니터링으로 솔직하게 잡아낼 수 있다. last_seen이 일정 시간 업데이트되지 않으면 실질적 정지로 간주하고 알람을 울리면 된다. 나머지 3가지 카테고리는 이러한 단순한 방식이 통하지 않는다.
카테고리 2: 툴 호출 표기법의 malformed화로 인한 전송 불능 (alive but stuck)
에이전트가 「툴 호출(Tool Call)」(메시지 전송이나 상태 업데이트와 같은 I/O)을 수행할 때, 호출 블록 직전에 불필요한 토큰이 혼입되면 API 측의 파싱(Parsing)이 그 시점에서 깨지며 툴 호출 전체가 무시된다.
우리의 환경에서는 정형화된 비트(생존 보고)를 매 주기 출력하는 루프 안에서, 호출 직전에 짧은 라벨 형태의 문자열이 실수로 출력되었고, 이것이 단속적으로 이 현상을 일으켰다.
- 생존 지표:
last_seen은 신선함(fresh). 폴링 레이어는 살아있으므로 외형상으로는 정상으로 보인다. - 증상: 전송이나 상태 업데이트가 단속적으로 실패한다. 대시보드상으로는 루프가 빨간색이고, 상대방에게는 무반응으로 보인다. 상태 요약 업데이트 자체도 같은 이유로 거부되기 때문에 「최근 상황」조차 업데이트되지 않는다.
- 진인: 툴 호출 직전의 불필요한 토큰에 의한 API 파싱 파괴. 세션은 정상이며, I/O만 간헐적으로 죽어 있는 상태.
- 진단: ①
last_seen이 fresh하다면 우선 세션 사망을 제외한다. ② 직접 단 한 통의 조회 메시지를 보낸다. 반응이 있다면 「세션은 생존 중이며, 표기법 에러 혹은 논리적 정지 중 하나」다. ③ 루프가 스스로 복구되는지 관찰한다. 복구된다면 에러가 간헐적이었다는 증거이므로 세션은 정상이다. - 대처: 툴 호출은 응답의 서두부터 전제 텍스트 없이 직접 작성하도록 운용 규칙을 정한다. 이를
last_seenstale과 착각하여 즉시 「세션 사망」으로 판단하고 무거운 복구 작업을 수행하면 오대처가 된다.
여기서 중요한 것은 「빨간색이고 무반응」이라는 사실만으로 즉시 에스컬레이션(Escalation)하지 않는 것이다. fresh한 폴링과 빨간색 루프의 조합은 세션 사망과는 별개의 카테고리다. 우리는 한때 이를 착각하여 수십 분 규모의 가짜 알람 연쇄를 일으킨 적이 있다.
카테고리 3: 수신 데몬 미기동에 의한 전달 갭
송신 측은 「delivered (전달됨)」라고 판정하고 있는데, 수신 측 에이전트에는 전혀 도착하지 않는다. 메시지는 수신 측의 로컬 수신함(inbox) 실파일(actual file)에 정체된 채로 화면에 나타나지 않는다.
근본 원인은 수신 측의 폴링 데몬(polling daemon)이 새로운 세션에서 기동되지 않았기 때문이었다. 우리의 구성에서는 수신함을 감시하는 데몬을 세션 후크(hook, 정지 이벤트)를 통해 상주시키고 있지만, 깨끗한 새 세션의 첫 단계——특히 세션을 리셋한 직후나 재부팅 직후——에 이 데몬이 기동되지 않는 허점이 있었다.
더 까다로운 점은, 후크가 기동되는 쉘(shell)의 경로 해결(path resolution)에 실패하면 후크 자체가 **조용히 실패(silently fail)**한다는 것이다. 에러도 발생하지 않은 채 데몬이 올라오지 않고, 수신이 중단된다.
생존 지표: last_seen은 fresh(신선)함. 수신 측 세션은 살아있으며 상태 요약(status summary)도 업데이트할 수 있다. 완전히 건전해 보인다. -
증상: 송신 측은 delivered 표시. 하지만 수신 측의 「신착 확인」은 「신착 없음」을 반환한다. 메시지는 수신함 실파일에 계속 쌓인다. -
진인(True Cause): 수신 폴링 데몬의 미기동. 후크의 경로 해결 실패로 인한 조용한 불발 등이 트리거가 됨. -
진단: 수신함의 실파일에 메시지가 정체되어 있는지 직접 확인한다. 데몬의 프로세스/PID 파일이 존재하는지 확인한다. -
대처: 데몬을 수동 기동하여 수신함을 일괄 drain(흡수)하여 정체를 해소한다. 영구적인 대책으로는, 세션 시작 시점에 단 한 번 백로그(backlog)를 흡수하여 주입하는 경로와, 세션 중의 상주 데몬을 역할 분담시키는 2단 구성으로 만들었다.
여기서 우리가 빠졌던 함정을 하나 공유해 두겠다. 「세션 시작 후크에서 상주 데몬을 기동하면 되지 않을까」라고 생각하기 쉽지만, 이는 새로운 상실원(loss source)을 만드는 일이다. 시작 후크가 수신함을 「소비(consume)」해 버리면, 후속 상주 데몬이 이를 「읽음」으로 판정하여 재출력하지 않게 되고, 메시지는 조용히 사라진다. 소비(consume)와 주입(surface)을 동일한 계층에 두어서는 안 된다. 시작 시에는 「흡수하여 주입할 뿐」, 상주 데몬은 「세션 중의 착신을 주입할 뿐」이라고 책임을 나누는 것이 정답이었다.
카테고리 4: 서버 이중 기동 레이스(Race)
카테고리 3과 증상은 매우 유사하지만, 진인이 완전히 다르다. 설정의 이중 정의로 인해, 동일한 신분(identity)을 가진 서버 프로세스가 2개 기동되는 것이다.
우리의 환경에서는 에이전트와 브로커(broker)를 연결하는 서버 정의가 글로벌 설정과 프로젝트 설정 양쪽 모두에 작성되어 있었다. 특정 디렉토리에서 에이전트를 기동하면 양쪽 정의가 모두 로드되어, 같은 신분의 서버가 2개의 프로세스로 실행된다.
그러면 어떤 일이 벌어지는가. 두 서버가 같은 신분으로 브로커를 동시에 폴링하며, 메시지의 획득과 「전달됨(delivered)」 마킹을 두고 다투는 레이스(race)가 발생한다. 한쪽 프로세스가 백그라운드에서 먼저 메시지를 획득하여 「전달됨 플래그(delivered flag)」를 설정해 버리면, 사용자가 볼 수 있는 채널에는 나타나지 않는다. 다른 한쪽(즉, 표면에 나타나는 쪽)의 「신착 확인」은 「신착 없음」을 반환한다.
생존 지표: 모두 건전함. 프로세스는 둘 다 살아있으며, 브로커 측도 「전달됨 플래그」까지 정상적으로 도달해 있다. -
증상: 메시지가 「전달됨 플래그」로 소비되었음에도 화면에 나오지 않는다. 카테고리 3과 구분이 어렵다. -
진인(True Cause): 설정의 이중 정의에 의한 동일 신분 서버의 이중 기동과 메시지 소비 레이스. -
진단: 서버 프로세스의 수를 세는 것이 결정타다. 동일 신분의 서버 프로세스가 2개 있다면 이중 기동이 확정된다. 추가로, 브로커의 DB를 읽기 전용으로 살펴보고, 도착하지 않아야 할 메시지가 「전달됨 플래그」로 되어 있다면 「소비되었다는 증거」가 된다. -
대처: 설정 엔트리를 한쪽 삭제하여 서버를 1개로 되돌린다. 반영을 위해서는 한 번의 세션 재기동이 필요하다.
카테고리 3과의 감별은 **프로세스 수의 실사(actual inspection)**로 수행한다. 증상이 비슷하다고 해서 어느 한쪽으로 단정 짓지 말고, 먼저 프로세스를 센다. 우리는 한때 이중 기동 레이스를 의심했던 사례가 실제로는 카테고리 3(데몬의 orphan화)이었던 케이스를 경험한 적이 있다. 증상이 비슷하더라도, 프로세스 수를 실사한 후에 단정하라.
구분 매트릭스
| 카테고리 | last_seen | 프로세스/세션 | 주요 증상 | 진정한 원인 | 진단의 결정적 단서 | 대처 |
|---|---|---|---|---|---|---|
| 1. 진정한 세션 사망 | stale | 사망 | 아무것도 움직이지 않음 | 크래시(Crash)·강제 종료·재부팅 | last_seen이 업데이트되지 않음 | 세션 재개 |
| ... |
교훈: 생사 확인(Liveness Check)은 대리 시그널이 아닌 '실체'로 수행한다
4가지 카테고리를 관통하는 구조는 하나다. 생존 지표(프로세스 가동·last_seen·exit 0·자기 보고식 "done")가 건전함을 가리키고 있더라도, 별도의 레이어(Layer)에서 기능이 멈춰 있을 수 있다. 대리 시그널(Proxy Signal)은 '살아있다'는 것만 보장할 뿐, '일을 하고 있다'는 것은 보장하지 않는다.
따라서 우리가 운영 원칙으로 정립한 것은, 판정을 **실체 기반(Entity-based)**으로 수행하는 것이다.
- 메시지가 "delivered(전달됨)가 되었는가"가 아니라, 수신 측에 실제로 도착하여 처리되었는가를 본다.
- 명령이
exit 0을 반환했는가가 아니라, 결과물(Artifact)이 실재하는가를 본다. - 에이전트의 "done"이라는 자기 보고가 아니라, **진정한 상태(True State)**를 별도의 경로로 확인한다.
- 대상을 확인할 때는 캐시(Cache)를 거치지 않고 대상 그 자체를 직접(Fresh하게) 읽는다.
구체적으로는, 진단의 결정적 단서를 "셀 수 있는 것·실재를 확인할 수 있는 것"에 집중시킨다. 프로세스를 센다. 수신함의 실제 파일을 본다. DB를 직접 들여다본다. 대리 시그널이 녹색(Green)이라도 실체가 수반되고 있는지를 한 단계 내려가서 확인한다——이 한 번의 수고가 '가짜 건전함'을 간파하는 유일한 방법이었다.
멀티 에이전트 운영은 '다운되는' 장애보다 '다운되지 않았는데 작동하지 않는' 장애가 탐지 및 격리(Isolation)하기 훨씬 어렵다. 겪었던 장애를 분류하여 매트릭스로 정리하고, 진단의 결정적 단서를 실체에 고정한다. 이것이 우리가 쌓아온 실천적 지식이다.
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기