좀비 녹음세션 반복
— 방치된 게스트 페이지 며칠 상주 원인 분석 분석 완료 · 수정 미적용

마지막 업데이트 2026-09-12

쉬운 설명 한 장 보기 · 사전지식 없이 읽는 요약

작성일: 2026-09-01로그 사건: 2026-08-28 ~ 09-01 (진행 중)room: 8f078389…_37 (37회기)서비스: ppi-socket-prod런타임: LiveKit 모드 아동

결론

ppi-socket-prod의 Failed to force-stop zombie session ERROR 반복은 사고가 아니라, 한 아동 기기가 37회기 게스트 페이지를 최소 4일간 켜둔 채 방치하면서 생긴 무한 사이클이다. 8/28 01:28 ~ 9/1 19:33 좀비 감지 27건 중 25건이 같은 방(8f078389…_37)이었고, 그동안 진행자 접속은 0회였다. 강제종료 "실패"는 업로드할 오디오가 애초에 없어서(No valid recording segments) 나는 것이고 세션 메모리 정리는 매번 정상 완료된다 — 실피해 없음.

즉시 대응은 코드가 아니라 연락이다. 양육자 채널톡으로 아동 기기의 핑퐁이 앱/탭 종료를 요청하면 사이클이 멈춘다. 사용자 확인은 roomId 앞부분(= userId)으로 ppi-user-prod(PK id)를 조회하면 된다.

시간순 인과 다이어그램

정상 입장 (전화번호+PIN, 8/28 이전 마지막 실수업) — V2는 진행자 승인 단계 없음
   → 수업 종료 처리 없이 페이지 방치 (진행자 미접속, 회기 '중단' 상태 아님)
   → 기기 절전/네트워크 순단마다 socket.io 재연결 매니저가 자동 재접속 (~16분 주기)
   → reconnect 핸들러가 풀 재동기화: JOIN_ROOM 재수행 + guest-ai-session-active emit
     + mic producer 재생성 (use-guest-page-session.ts:1985)
   → 좀비 정리 직후의 producer가 녹음 자동 재시작 (tryStartPendingRecording)
   → 녹음 ffmpeg는 RTP 0패킷 → SDP "Connection timed out" → 20초 만에 종료, stem 0kB
   → stop 이벤트 없음 (게스트 이탈·수업종료·강퇴 전부 미발생)
   → 2시간 TTL 초과 → "Zombie session detected" WARN → stopRecording 강제 호출
   → 직전에 stale 파일 청소기가 0kB stem 삭제 → "No valid recording segments" throw
   → "Failed to force-stop zombie session" ERROR (세션 메모리 정리는 완료)
   → 다음 reconnect의 producer가 녹음 재시작 → 2.5시간 주기 무한 반복

좀비 감지 이력 (Loki, 8/28~9/1)

8/28 01:28 ~ 8/29 09:58

2.5시간 간격 좀비 감지 14회 — 전부 8f078389…_37. 기기 연속 가동.

8/29 10:00 ~ 8/31 15:00

공백 — 기기가 꺼져 있던 것으로 추정 (주말).

8/31 15:33 ~ 9/1 19:33

재개, 2.5시간 간격 12회. 월요일 정규 수업 시간(18:00)에도 진행자 접속·38회기 방 생성 없음 — 정규 수업이 열리지 않았고, 좀비는 수업 잔재가 아니라 방치 사이클이다.

8/31 15:33

유일한 예외: 다른 사용자 방 decf6aad…_75 1건 (단발, 별건).

감지 주기가 정확히 2.5시간인 이유: TTL 2시간(STALE_FILE_TTL_MS) + 30분 스캔(STALE_FILE_SCAN_INTERVAL_MS) 정렬. 좀비 판정·강제종료는 apps/socket/src/recording/recordingManager.ts:260-268, 상수는 :33-34.

왜 방치된 페이지가 계속 "입장"해 있을 수 있나

게이트이 케이스에서 걸렸나근거
진행자 입장 승인FACT 없음승인 입장은 V1(meet) 방식. V2 client-guest는 전화번호+PIN self-entry (validate-lesson)
재입장 fenceFACT 통과guest-session-admission-store.ts의 fence는 더 오래된 세대만 차단. 같은 기기·같은 lessonSession 재입장은 순단 복구용으로 의도적 허용
중단 회기 차단 (PPI-1250)FACT 미적용진행자가 수업 종료 처리를 한 적이 없어 회기가 '중단' 상태가 아님
게스트 유휴 타임아웃FACT 부재서버에 없음. 좀비 TTL은 녹음 세션만 정리하고 게스트는 내보내지 않음
클라이언트 재연결 차단FACT 미발동terminal 상태(강퇴·종료·리소스 실패)에서만 socket.io.reconnection(false) (use-guest-page-session.ts:2066). 방치는 terminal이 아님

FACT 단말은 Mac OS Chrome, 해외(싱가포르) 접속(LogRocket 최종 접속 8/29 11:42). 웹 서버 로그로 정식 입장(validate-lesson)은 8/29 11:42:07 1회가 마지막이며 이후 9/1까지 0회 — LogRocket 최종 접속(11:42:03)과 초 단위로 일치한다. 즉 이후의 모든 방 상주는 신규 입장이 아니라 그 페이지 인스턴스의 JOIN_ROOM 재수행이다. 또한 좀비 공백기(8/29 10시~8/31 15시)에도 소켓발 세션종료 콜백(end-pending, 시간당 4~5회)이 계속 찍혀 탭은 기간 내내 연결·해제를 반복하며 살아 있었다 — 공백은 기기 꺼짐이 아니라 그 기간엔 disconnect cleanup이 녹음을 2시간 TTL 전에 정리해줬기 때문이다.

추정 ~16분 주기의 AI 세션 재활성(Guest AI session active state changed + producer 재생성)은 Chrome 백그라운드 탭 스로틀링/Mac 절전 주기와 socket.io 자동 재연결 재동기화가 맞물린 흔적으로 보인다. 클라이언트 콘솔 로그 없이 주기의 근원까지는 확정할 수 없다.

에러 문구 해부 — "실패"가 실패가 아닌 이유

트리아지 절차 (재사용)

  1. 좀비 로그의 roomId 앞부분 = userId → ppi-user-prod(PK id) 조회로 사용자 확인 (실아동 vs 테스트).
  2. loki-logs.py --service ppi-socket --search "Zombie"로 며칠치 이력 조회 → 같은 roomId 반복이면 방치 패턴 확정.
  3. 반복 패턴이면 양육자 채널톡으로 앱 종료 요청이 1차 대응. 코드 수정 불필요.
  4. 단발이면 해당 방의 disconnect/kick/세션종료 이벤트 유무를 따라 별도 조사.
근본 개선 후보 (미적용, 제안만) — ① 진행자 없는 방에서 녹음 자동시작 억제, ② 게스트 장기 유휴 타임아웃(AI 발화 없이 N시간이면 세션 종료), ③ stale 파일 삭제와 좀비 stop의 실행 순서 교정(먼저 stop 시도 후 파일 정리 — ERROR 노이즈 제거).

관련 문서