무슨 일이 있었나
2026-09-09 23시 20분부터 23시 26분 사이, 6분 안에 헤르메스 게이트웨이 3개가 차례로 죽었다. 순서는 이랬다.
| 시각 | 대상 프로필 | 비고 |
|---|---|---|
| 23:20:00 | blog-manager | CRITICAL 로그 후 종료 |
| 23:24:28 | watchpick-designer | 같은 CRITICAL 로그, 4분 뒤 |
| 23:26:04 | watchpick-dev | telegram.error.TimedOut 동반 |
세 프로필의 로그에 남은 원문은 이것이다.
blog-manager 2026-09-09 23:20:00 CRITICAL gateway.shutdown_watchdog:
Gateway event loop missed 3 consecutive liveness probes; dumping all thread
stacks and exiting with code 75 so the service supervisor can restart it.
watchpick-designer 2026-09-09 23:24:28 CRITICAL gateway.shutdown_watchdog:
Gateway event loop missed 3 consecutive liveness probes; ... exiting with code 75
watchpick-dev 2026-09-09 23:26:04 CRITICAL gateway.shutdown_watchdog:
telegram.error.TimedOut: Timed out
Gateway event loop missed 3 consecutive liveness probes; ... exiting with code 75
다음 날 hermes gateway list로 확인하니 셋 다 not running 상태였다. 그동안 각 프로필에 걸려 있던 크론 — 블로그 발행·자가학습, 디자인 스캔, 자동 커밋·아침 보고 등 — 이 전부 멈춰 있었다. 게이트웨이 하나가 죽으면 그 프로필이 맡은 자동화가 통째로 정지한다는 뜻이다. 티스토리 자동 발행 워커를 크롬 자동화로 구축한 과정을 다룬 티스토리 Open API 종료 후 자동 발행 구축기도 결국 이런 게이트웨이 위에서 돌아가는 크론에 의존한다.
로그를 어떻게 읽었나
세 로그 모두 같은 문구로 끝난다. "Gateway event loop missed 3 consecutive liveness probes; dumping all thread stacks and exiting with code 75 so the service supervisor can restart it." 이 문장 자체가 실마리였다. 이벤트 루프가 3회 연속 생존 확인(liveness probe)에 응답하지 못했다는 것, 그리고 종료하는 이유가 "so the service supervisor can restart it" — 즉 관리자가 다시 띄워 줄 것을 기대하고 스스로 죽는다는 것이다. watchpick-dev 로그에는 telegram.error.TimedOut: Timed out이 함께 찍혀 있어, 텔레그램 쪽 통신이 막힌 구간과 겹친다는 것도 알 수 있었다.
그래서 다음 확인 순서는: (1) 정말 아무도 다시 띄우지 않았는지 프로세스 상태를 본다, (2) "service supervisor"라는 표현이 기대하는 관리 체계가 실제로 있는지 본다. 각 프로필의 관리 방식을 hermes -p <profile> status로 확인한 결과는 다음과 같았다.
--- 복구 다음 날 확인한 상태 (hermes gateway list) ---
x blog-manager - not running
x watchpick-designer - not running
x watchpick-dev - not running
--- 각 프로필의 관리 방식 (hermes -p status) ---
Manager: manual process <- 종료 코드 75가 기대하는 "service supervisor" 가 없다
원인
텔레그램 네트워크가 끊기며 어댑터의 updater.stop()이 제때 끝나지 않아 이벤트 루프가 막혔고, 감시자가 3회 연속 응답 없음을 보고 프로세스를 끝냈다. 종료 코드 75는 "관리자가 다시 띄워 달라"는 신호인데, 세 프로필 모두 관리 방식이 manual process라 그 관리자가 존재하지 않았다. 즉 죽는 것 자체는 설계대로였고, 살아나지 못한 것이 구멍이었다.
다만 왜 하필 그 순간 updater.stop()이 지연됐는지, 그 안쪽 병목이 정확히 무엇이었는지는 이 로그만으로는 단정할 수 없다. 텔레그램 통신이 불안정했던 구간과 겹친다는 것까지만 확인됐다.
조치
세 프로필을 각각 수동으로 되살렸다.
hermes -p blog-manager gateway start → PID 33656
hermes -p watchpick-designer gateway start → PID 40876
hermes -p watchpick-dev gateway start → PID 40132
세 명령 모두 정상적으로 게이트웨이를 재기동했고, 이후 gateway list에서 다시 running 상태를 확인했다. 이 부분은 사고를 멈춘 것이지 사고를 막은 것은 아니다.
같은 일을 막으려면
근본 대책은 두 갈래로 보인다.
- 관리 방식을 manual process가 아니라 실제 서비스로 등록한다 (
hermes gateway install). - 바깥에서
gateway list를 주기적으로 확인해 not running이면 start하는 감시를 따로 둔다.
지금은 이 중 아무것도 적용하지 않았다. 되살리기만 했고, 다음에 같은 조합(텔레그램 네트워크 불안정 + 이벤트 루프 정지)이 겹치면 똑같이 6분 안에 여러 프로필이 조용히 멎을 수 있다. 예약 발행이 실제로 걸리는 시점에 이런 정지가 겹치면 어떤 상태 불일치가 생기는지는 워드프레스 REST 예약 발행이 9시간 밀리는 이유 글에서 다룬 타임존 문제와는 결이 다르지만, "자동화가 자기도 모르게 멈춰 있었다"는 지점은 같다. 종료 코드 75는 아무 값이나 고른 것이 아니라 BSD 계열 유닉스가 오래전부터 써 온 표준 종료 코드 표(sysexits.h)의 EX_TEMPFAIL과 같은 값이다 — "일시적 실패, 재시도해 달라"는 뜻으로 정의돼 있다. 자세한 정의는 sysexits.h 매뉴얼 페이지에 있다.