좀비 녹음세션 반복
— 방치된 게스트 페이지 며칠 상주 원인 분석 분석 완료 · 수정 미적용
마지막 업데이트 2026-09-12
결론
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)
2.5시간 간격 좀비 감지 14회 — 전부 8f078389…_37. 기기 연속 가동.
공백 — 기기가 꺼져 있던 것으로 추정 (주말).
재개, 2.5시간 간격 12회. 월요일 정규 수업 시간(18:00)에도 진행자 접속·38회기 방 생성 없음 — 정규 수업이 열리지 않았고, 좀비는 수업 잔재가 아니라 방치 사이클이다.
유일한 예외: 다른 사용자 방 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) |
| 재입장 fence | FACT 통과 | 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 자동 재연결 재동기화가 맞물린 흔적으로 보인다. 클라이언트 콘솔 로그 없이 주기의 근원까지는 확정할 수 없다.
에러 문구 해부 — "실패"가 실패가 아닌 이유
stopRecording은 진입 즉시sessions.delete(roomId)하므로, 이후 어떤 throw가 나도 좀비 자체는 제거된다 (recordingManager.ts:465이하).- throw 지점은
No valid recording segments— stem이 전부 0kB인 데다, 같은 청소 사이클에서 stale 파일 삭제가 stop 시도보다 먼저 실행돼 stem 파일이 이미 사라진 상태. - ffmpeg
stderrTail의Connection timed out+size= 0kB가 "RTP 미수신" 확진 시그니처.lastFfmpegExit.code=0이라 프로세스 이상도 아니다. - 따라서 이 ERROR는 "건질 오디오가 없었다"는 뜻이고, 곧바로
Recording session cleaned upINFO가 따라온다.
트리아지 절차 (재사용)
- 좀비 로그의 roomId 앞부분 = userId →
ppi-user-prod(PKid) 조회로 사용자 확인 (실아동 vs 테스트). loki-logs.py --service ppi-socket --search "Zombie"로 며칠치 이력 조회 → 같은 roomId 반복이면 방치 패턴 확정.- 반복 패턴이면 양육자 채널톡으로 앱 종료 요청이 1차 대응. 코드 수정 불필요.
- 단발이면 해당 방의 disconnect/kick/세션종료 이벤트 유무를 따라 별도 조사.
관련 문서
- 녹음 파이프라인 mediasoup→ffmpeg→OGG→S3 코드레벨 동작흐름 — 좀비 TTL이 지키는 파이프라인의 전체 구조
- 수업 중단 후 아동 재입장 — terminal status 서버 게이트 전환 설계 — 이 문서의 재입장 게이트 논의와 직결 (PPI-1250)