happy_various AI 실전 기록

LLM 중계기 개발기 5부 · 21편 / 전체 33편

2026년 10월 1일 기준

이 글에는 제휴 링크가 없고 OpenAI·앤트로픽과 아무 관계가 없습니다. 제가 직접 만든 중계기의 개발 기록을 바탕으로 썼습니다. 삽화는 GPT 이미지 생성, 도식은 직접 제작했습니다.

끄는 것도 기술이다 — 퇴근했는데 사무실 불이 켜져 있다 (LLM 중계기 개발기)

퇴근길에 건물을 올려다봤는데, 내가 쓰던 방 창문 하나에 아직 불이 켜져 있다고 해 볼게요. 나오면서 분명히 다 끄고 왔다고 생각했는데 말이죠. 그 방에서는 퇴근 소식을 듣지 못한 누군가가 혼자 일을 계속하고 있어요. 다음 날 아침 그 방을 쓰려던 사람은 문 앞의 ‘사용 중’ 팻말을 보고 돌아서야 하고요.

제가 만든 LLM 중계기에서 꼭 이런 일이 있었어요. 중계기를 껐는데, 중계기가 띄워 둔 AI 프로그램이 뒤에서 혼자 살아남아 자리를 차지하고 있었습니다. 그래서 다음에 중계기를 켜면 실패했어요.

이 글은 LLM 중계기 개발기의 21편입니다. 지난 20편 「맥에서 되는데 윈도에서 안 되는 이유」에서는 만드는 컴퓨터와 쓰는 컴퓨터가 다를 때 생기는 고장을 이야기했어요. 오늘은 켜는 것만큼 까다로웠던 끄는 일의 이야기입니다.

밤의 건물에서 창문 하나에만 불이 켜져 있고, 그 안에서 작은 로봇이 아직 일하고 있다 (GPT 이미지 생성)

결론 요약

🏢 1. 껐는데, 꺼지지 않았다

6편의 셔틀버스를 기억하시나요? 중계기는 Codex를 쓰려고 앱 서버라는 프로그램을 하나 켜 두고 계속 씁니다. 이 앱 서버는 중계기와 이야기를 주고받으려고 내 컴퓨터 안에 문을 하나 열어 둬요.

여기서 문이란 포트(port)를 말해요. 한 컴퓨터 안에서 프로그램끼리 서로 찾아가는 문 번호예요. 중요한 규칙이 하나 있는데, 한 문 번호는 한 번에 한 프로그램만 쓸 수 있습니다. 이미 누가 쓰는 번호를 다른 프로그램이 열려고 하면 “이미 사용 중”이라는 오류가 나요.

개발 첫날 밤, 저장 기록(커밋)에 이런 증상이 남았어요.

그때 중계기가 띄우던 안내 창에는 “작업 관리자에서 중계기 프로세스를 종료한 뒤 다시 실행하세요”라는 뜻의 문장이 적혀 있었어요. 저장 기록은 이 안내를 두고 개발자가 아닌 사람에게 요구할 수 없는 일이라고 적었습니다.

👨‍👧 2. 부모 프로세스, 자식 프로세스, 그리고 고아

6편에서 실행 중인 프로그램 하나를 프로세스라고 부른다고 했죠. 프로그램이 다른 프로그램을 띄우면, 띄운 쪽을 부모 프로세스, 띄워진 쪽을 자식 프로세스라고 불러요.

윈도에서 중계기가 Codex 앱 서버를 띄우는 모양은 이랬어요.

할머니, 엄마, 아이처럼 줄줄이 이어지죠. 위에서 아래로 가지를 치는 모양이라 개발자들은 이걸 프로세스 나무(process tree)라고 불러요.

그런데 저장 기록에 적힌 핵심은 이 한 줄이었어요. 부모가 끝나도 자식은 저절로 끝나지 않는다. 부모가 먼저 떠나 혼자 남은 자식을 흔히 고아 프로세스라고 부릅니다. 개발용 컴퓨터에서 직접 확인했을 때, 남아 있던 앱 서버의 부모 칸에는 중계기 대신 1번이 적혀 있었어요. 원래 부모가 사라져서 운영체제의 맨 처음 프로세스가 대신 떠맡았다는 표시예요.

이 글의 제목에 쓴 좀비는 ‘꺼졌어야 하는데 뒤에서 계속 살아 있는 프로세스’를 편하게 부른 말이에요. 엄밀한 개발 용어로는 좀비와 고아가 조금 다른 것을 가리키지만, 저장 기록에서는 둘을 묶어 좀비라고 불렀고 이 글도 그렇게 부를게요.

퇴근하는 관리자가 방마다 들르지 않고 혼자 나가 버린 셈이에요. 퇴근 소식을 못 들은 직원은 불을 켠 채 계속 일하고, 다음 날 아침 그 방은 ‘사용 중’입니다.

🔧 3. 첫 수정 — 정리 담당을 세우다

첫날 밤 10시 4분, v0.1.15에서 이 문제를 고쳤어요(4편에서 예고했던 바로 그 판입니다). 저장 기록은 원인을 두 가지로 적었어요.

그래서 네 가지를 넣었습니다.

① 자식 명부 — 중계기가 띄우는 자식 프로세스를 모두 명부에 적어요. 자식이 스스로 끝나면 명부에서 지우고요 ② 나무째 끄기, 꺼질 때까지 기다리기 — 끌 때는 자식의 자식까지 한 번에 끄고, 정말 꺼질 때까지 기다렸다가 다음으로 넘어가요. 기록에 따르면 앱이 곧 사라지는 순간에는 “꺼 줘”라고 부탁만 해 두면 그 부탁이 실행되기 전에 앱이 먼저 사라질 수 있거든요 ③ 정리 담당 하나 — 중계기가 연 창구 두 개를 닫고, 연결 부품을 정리하고, 명부의 자식을 모두 끄는 일을 한 곳에 모았어요. 창구를 닫을 때는 열려 있는 연결부터 먼저 끊어요. 그렇지 않으면 문 닫기가 끝나지 않았거든요. 앱은 이 정리가 끝날 때까지 종료를 미루되, 3초가 지나면 강제로 꺼요 ④ 켤 때의 뒷정리 — 켤 때 문 번호가 이미 차 있으면, 그 자리를 차지한 프로세스가 누구인지 이름으로 확인해요. 우리 중계기가 남긴 좀비면 끄고 문을 되찾아요. 남의 프로그램이면 절대 끄지 않고, 비어 있는 다른 문 번호로 옮겨서 켜요. 작업 관리자를 열라는 안내는 이때 지웠습니다

사무실 복도를 걸으며 체크리스트를 들고 방마다 불을 끄는 로봇 (GPT 이미지 생성)

그리고 4편의 ‘고장 하나에 검사 하나’ 습관대로 검사를 붙였어요. 이름은 종료 정리 검사(check-shutdown)예요.

검사가 진짜로 무는지도 봤어요. 일부러 끄기 기능을 아무것도 하지 않는 빈 껍데기로 바꿔 보니 검사가 “자식이 아직 살아 있음”으로 실패했습니다. 마지막으로 앱을 켜고 Codex를 연결한 뒤 ‘꺼라’ 신호를 보내 꺼 봤더니, 앱 서버가 사라졌고 문 번호 세 개가 모두 비었고, 곧바로 다시 켜도 아무 문제가 없었어요. 저장 기록의 제목은 ‘좀비 Codex 앱 서버를 끝낸다’였습니다.

🙇 4. “지난번 보고는 틀렸다” — 창 X는 다른 길이었다

다음 날 저녁 8시 17분, v0.1.29의 저장 기록은 이렇게 시작해요.

정리 코드는 이미 잘 갖춰져 있었다. 문제는 트리거였다.

트리거(trigger)는 방아쇠, 그러니까 정리 코드를 부르는 쪽이에요. 그리고 같은 기록에 이 문장이 이어집니다.

v0.1.15의 “좀비 고침” 보고는 트레이 경로만 고친 것으로, 틀렸다.

무슨 일이었을까요? 중계기를 끄는 길은 하나가 아니었어요.

앞의 두 길은 전날 세운 정리 담당을 불렀어요. 그런데 X는 창을 숨기기만 했고, 창이 모두 닫혔을 때 무엇을 할지 정해 둔 처리도 아예 없었어요. 기록 그대로 옮기면 트레이 메뉴의 ‘종료’로 끝낸 사람만 정상 종료였고, X로 닫은 사람의 컴퓨터에서는 중계기가 뒤에 그대로 남아 있었습니다. 정리는 작동했지만, 불리지 않았던 거예요.

왜 전날 확인에서 놓쳤는지도 기록을 보면 보여요. 전날 끝까지 확인할 때는 ‘꺼라’ 신호로 껐고, 종료 정리 검사는 정리 기능을 직접 불러서 확인했어요. 정리가 제대로 작동하는지는 봤지만, 사람이 실제로 끄는 길 하나하나가 정리를 부르는지는 보지 않은 거예요.

이번에는 이렇게 고쳤어요.

끄는 길 네 갈래가 정리 담당 한 곳으로 모이기 전과 후 (직접 제작)

기록은 두 검사를 짝으로 설명해요. 종료 정리 검사는 정리가 ‘작동’하는가를, 종료 경로 검사는 정리가 ‘불리는’가를 본다고요.

그리고 한계도 솔직하게 적어 두었어요. 맥에서는 운영체제가 남은 도우미 프로세스를 알아서 치워 주기 때문에, 윈도에서 생기는 이 좀비를 맥에서는 아예 재현할 수 없었어요. 20편에서 본 ‘여기선 되는데 저기선 안 되는’ 문제의 끄기 버전이죠. 그래서 기록에는 “맥에선 창 닫기가 종료 함수를 실제로 호출하는가까지만 검증. 잔존 0개는 Windows 사용자 확인 필요.”라는 문장이 남아 있습니다.

🛡️ 5. 그 뒤에 더한 안전망

끄기 문제는 그 뒤에도 모양을 바꿔 세 번 더 찾아왔어요.

① 껍데기를 바꾸자 같은 구멍이 다시 보였다 (8월 6일) 중계기의 무거운 앱 껍데기를 걷어내는 작업(25편에서 다룹니다)을 하면서, 관리 화면은 평범한 브라우저 창이 됐어요. 그러자 창이 닫혀도 중계기가 그 사실을 알 수 없게 됐습니다. 기록에는 창만 닫고 자식 프로세스는 남기는 일이 “이 저장소가 반복해서 겪은 좀비 사고의 재발 경로”라고 적혀 있어요.

그래서 끄는 길을 새로 정했어요. 지금은 화면을 닫아도 중계기는 트레이에서 계속 일하고, 끌 때는 트레이 메뉴의 ‘종료’나 관리 화면 위쪽의 ⏻ 버튼을 써요. 두 길 모두 중계기의 같은 종료 창구를 불러요. 중계기는 “종료합니다”라는 응답부터 보낸 뒤에 정리를 시작해요. 정리부터 하느라 연결을 먼저 끊어 버리면, 누른 사람 눈에는 “눌렀는데 아무 일도 없었다”로 보이거든요. 정리가 어딘가에서 멈춰도 11초가 지나면 강제로 끝내고, 트레이 쪽도 11초를 기다렸는데 서버가 아직 살아 있으면 나무째 꺼요.

② 설치 프로그램이 ‘실행 중인 앱 닫는 중’에서 멈췄다 (8월 20일) 중계기를 켜 둔 채 새 판 설치 프로그램을 돌렸더니, 설치가 ‘실행 중인 앱 닫는 중’ 단계에서 멈췄어요. 기록에 따르면 윈도의 ‘실행 중인 앱 닫기’ 도우미가 창이 없는 중계기 서버 프로그램을 닫지 못했어요. 다시 시도하자 트레이만 꺼지고 서버는 고아로 남아 설치 파일을 계속 붙들었고, 설치는 몇 번을 해도 실패했습니다. 부모가 끝나도 자식은 남는다는 2절의 규칙이 그대로 다시 나타난 거예요.

여기서 두 가지를 더했어요.

큰 로봇이 책상에서 졸던 작은 로봇들을 깨워 함께 퇴근하려 한다 (GPT 이미지 생성)

③ 끄는 순간에도 경주가 있다 (9월 29일) 설치 프로그램을 만들 때 앱이 제대로 켜지는지 확인하는 검사가 있어요. 앱을 짧은 점검 모드로 켰다가 바로 끄는 검사인데, 여기서 앱이 6번 중 6번 끄는 순간에 비정상 종료했어요. 응답마다 버전 정보를 덧붙이는 한 줄이 들어간 뒤부터 나타났고, 그 줄을 빼면 사라졌어요. 그렇다고 그 줄이 잘못된 건 아니었어요.

원인은 끄는 순간의 경주였어요. 점검 모드의 앱은 서버가 켜졌는지 “살아 있니?” 하고 물어본 직후 바로 꺼져요. 그런데 이때 쓴 도구가 받은 답을 읽는 부품을 더 빠르게 만드는 작업을 뒤에서 이어 가고 있었어요. 답에 실린 정보가 늘어나자 그 작업이 하필 끄는 순간과 겹쳤고, 작업을 마친 쪽이 “다 했어요”라고 알리러 오다가 이미 닫히고 있는 문에 부딪혀 넘어진 셈이에요. 기록에는 코드 결함이 아니라 중계기 서버를 돌리는 실행 환경(Node.js)의 윈도판에서 생기는 종료 경쟁이라고 적혀 있어요.

해결은 뒤에 일을 남기지 않는 더 단순한 방법으로 묻는 것이었어요. 같은 재현 시험에서 6번 중 0번이 됐고, 켜서 끄기까지 걸린 시간도 1.6초에서 0.85초로 줄었습니다.

📋 6. 남긴 규칙 — 끄기도 켜기처럼 설계한다

켜는 쪽에는 워밍업, 연결 확인, 준비 상태 표시까지 단계가 많았어요. 이 사고들은 끄는 쪽에도 그만큼의 설계가 필요하다는 걸 보여 줬습니다. 그래서 남은 규칙은 이래요.

정리

① 부모 프로세스가 끝나도 자식은 저절로 끝나지 않아요. 중계기를 꺼도 Codex 앱 서버가 고아로 남아 문 번호를 붙들었고, 다음 실행이 실패했어요 ② 첫날 밤 정리 담당을 세우고 “고쳤다”고 적었지만, 창 X는 정리를 부르지 않는 다른 길이었어요. 다음 날 “틀렸다”고 고쳐 적고 모든 길을 한 곳으로 모았고, 정리가 ‘작동하는가’와 ‘불리는가’를 따로 검사해요 ③ 그 뒤로 종료 버튼, 설치 프로그램의 직접 정리, 부모가 사라지면 자식까지 함께 끄는 윈도의 묶음 기능, 끄는 순간의 경주 해결까지 안전망을 더했어요

다음 22편은 「‘준비 중’이 끝나지 않는 화면」이에요. 실제로는 연결이 끝났는데 화면은 ‘워밍업 중’에서 영영 넘어가지 않던 이야기를 할게요.


2026년 10월 1일 기준입니다. 제가 만든 중계기의 개발 기록(저장 기록의 시각과 메모)을 바탕으로 썼고, 특정 기업·서비스의 공식 입장이 아닙니다.