영상 종료 후 자동전환 미발생 세션로그 원인 분류 및 Watchdog 분석 BUG ROOT CAUSE OPEN

마지막 업데이트 2026-07-22

작성일: 2026-07-02 분석 대상: 7개 세션 로그 / 33개 영상 이벤트 관련 코드: MeetVideo, VideoEndWatchdog, guest-video-end

결론 요약

이번 장애의 직접 원인은 대다수 케이스에서 소켓 emit 실패가 아니라, 그 이전 단계인 게스트 영상 종료 감지/onEnd 호출 미발생이다. GUEST_SOCKET] Notifying video ended 로그가 없으면 guest-video-end emit 함수 자체가 호출되지 않은 것으로 판정했다.
7
분석 세션 로그
4
주 원인 그룹
3
최종 영상 종료 emit 누락
0
서버 emit ACK 실패 증거
1. 영상 재생Video play eventguest-video-play-started는 대부분 정상
->
2. 종료 감지native ended / near-end / watchdog 중 하나가 onEnd 호출해야 함
->
3. 실패 지점DECODE_OVERLOAD, step change, force kick, navigation reset으로 onEnd 누락
->
4. 모니터 미전환guest-video-end 미발송으로 모니터 진행자 자동전환 불가

케이스별 원인 분류

세션 핵심 판정 Watchdog 미발화 이유 대표 로그 신호
길주한 21회기
2026-06-19 11:30Z
부분 누락
video_tiki20_movie_05 종료 emit 없음
마지막 확인 시 26.006 / 29.653s. 끝 3.6초 전이라 watchdog 종료 조건 밖. 곧바로 host step change로 mediaType=""가 되어 watchdog 해제. Video waiting [DECODE_OVERLOAD]
Step changed by host
김성운 25회기
2026-06-19 11:00Z
최종 영상 누락
video_dubutiki_bye_2
49.877초 영상이 media clock 기준 끝에 도달하기 전 complete force kick이 먼저 들어와 socket disconnect. Video waiting [DECODE_OVERLOAD]
Guest force kicked complete
이희성 15회기
2026-06-20 00:45Z
중간 영상 누락
2개 영상 emit 없음
video_taeyang_explanation2...42.690 / 48.874s에서 다음 영상으로 소스 변경. video_chulmin_win_0은 끝 0.8초 전이었지만 near-end stall 3초 조건 전에 다음 prompt로 전환. Resetting video element after URL change
GUEST_STEP_UTILS step-update
임주안 38회기_1
2026-06-17 11:30Z
영상 종료 문제 아님
재입장/강제퇴장 흐름
140초 첫 영상이 23~28초만 재생된 상태에서 reason="kick" force kick. 종료 후 자동전환 실패가 아니라 외부 세션 제어 이벤트. Guest force kicked reason=kick
임주안 38회기_2
2026-06-17 11:32Z
종료 감지 루틴 소실
video_newpp02_gacha_01
완전 버퍼링 상태로 재생 시작 후 waiting, pause, ended, watchdog 로그가 모두 없음. 이후 다른 auto-finish/step 진행으로 이전 watchdog이 reset된 패턴. Video play event 이후 장시간 영상 로그 침묵
최연우 21회기
2026-06-19 05:30Z
소켓/미디어 파이프라인 끊김
최종 영상 emit 없음
17.4초 영상 4.4초 지점에서 탭 hidden + websocket transport close. 30초 stall 백스톱 전에 session navigation/reinitialize 발생. Unexpected pause
replaceTrack ... peer connection is closed
track ended
최유리 11회기
2026-06-19 08:32Z
최종 영상 누락
video_dubutiki_bye_2
49.877초 영상인데 마지막 waiting 시점 32.318s. 끝 17.6초 전이라 watchdog 종료 조건 밖. 직후 complete force kick으로 socket 종료. Video waiting [DECODE_OVERLOAD]
Guest force kicked complete

원인 그룹 맵

DECODE_OVERLOAD + media clock 지연길주한 · 김성운 · 이희성 · 최유리

wall-clock으로는 영상 길이만큼 지났지만 video.currentTime은 아직 끝에 도달하지 못한 상태.

step/source 변경으로 watchdog reset길주한 · 이희성 · 임주안_2

현재 step의 mediaType이 비디오가 아니게 되거나 새 videoUrl이 로드되면서 이전 영상 watchdog이 해제됨.

force kick / complete가 종료보다 선행김성운 · 최유리 · 임주안_1

종료 emit 전 세션이 종료 화면 또는 강제퇴장 화면으로 전환되어 socket이 끊김.

탭 hidden + transport close최연우

재생 중 연결이 닫히고 recovery 과정에서 media track이 끝나 watchdog보다 session reinitialize가 먼저 발생.

종료 emit 누락 직접 원인 watchdog 조건 불충족 세션 제어 이벤트 정상 종료 경로

Watchdog이 기대와 다르게 동작한 이유

현재 Watchdog은 wall-clock 기반 강제 종료 타이머가 아니다. VideoEndWatchdogcurrentTime, duration, isPlaying, shouldStop 조건을 읽고, media clock이 끝에 도달했거나 완전히 stall된 경우에만 onEnd를 호출한다.

발화 조건

  • duration - currentTime <= 0.3s
  • 끝 2초 이내에서 3초 이상 진행 없음
  • 한 번 진행한 뒤 30초 이상 완전 stall
  • props.isPlaying이 true이고 videoSrc가 유지되어야 함

이번 케이스에서 빠진 조건

  • DECODE_OVERLOAD 중에도 currentTime이 느리게 증가해 30초 완전 stall로 보이지 않음
  • 끝 근처에 도달하기 전 host step change 또는 force kick 발생
  • 새 영상/새 step 로드 시 watchdog.reset()으로 이전 감지 상태 소멸
  • navigation/reinitialize가 30초 백스톱보다 먼저 발생
// apps/web/shared/lib/video-end-watchdog.ts
const firstDelayMs = hasDuration
  ? Math.max(0, (duration - currentTime) * 1000)
  : pollIntervalMs;

if (remaining <= 0.3) fire();
else if (remaining <= 2.0 && stalledMs >= 3000) fire();
else if (stalledMs >= 30000) fire();

대표 타임라인

1. DECODE_OVERLOAD 후 host step change가 먼저 들어온 경우 - 길주한

11:45:58
video_tiki20_movie_05 play. duration 29.653s.
11:46:04~34
Video waiting [DECODE_OVERLOAD] 반복. 마지막 확인 currentTime=26.006, 아직 3.647초 남음.
11:46:35
Step changed by host, mediaType="". 게스트 isPlaying=false가 되어 watchdog disarm/reset.
결과
Video onEnd, Notifying video ended 모두 없음.

2. complete force kick이 종료보다 먼저 온 경우 - 최유리

08:54:31
video_dubutiki_bye_2 play. duration 49.877s.
08:55:20
마지막 waiting. currentTime=32.318, 아직 17.559초 남음. watchdog 종료 조건 밖.
08:55:21
Guest force kicked - disconnecting socket, reason complete. socket disconnect.
결과
모니터가 기대한 guest-video-end 대신 세션 종료/퇴장 흐름이 선행.

3. 소켓/미디어 파이프라인이 먼저 끊긴 경우 - 최연우

05:52:30
video_bye_jiwoo_kidscafe_new_0 play. duration 17.408s.
05:52:35
탭 hidden 직후 websocket transport close, unexpected pause.
05:52:40~41
replaceTrack 실패, track ended. 이후 session navigation/reinitialize.
결과
30초 stall 백스톱 전에 컴포넌트/세션이 재초기화되어 watchdog이 발화할 수 없음.

currentTime 기준으로 본 발화 가능성

아래 비율은 마지막으로 확인된 currentTime / duration이다. 100%에 가까울수록 watchdog/near-end fallback이 발화할 수 있다.

길주한 movie_0526.006 / 29.653s = 87.7%

남은 3.647초. 끝 2초 window 밖.

최유리 bye_232.318 / 49.877s = 64.8%

남은 17.559초. 종료 조건과 큰 거리.

이희성 explanation242.690 / 48.874s = 87.3%

남은 6.184초. 다음 영상 로드로 reset.

이희성 chulmin_win17.514 / 18.346s = 95.5%

끝 0.832초 전이지만 near-end stall 3초 전에 prompt로 전환.

정상 비교: 이희성 final bye17.786 / 17.984s = 98.9%

watchdog backstop 발화 후 Notifying video ended 전송.

정상 비교: 길주한 final bye49.877 / 49.877s = 100%

지연은 있었지만 media clock이 끝에 도달해 종료 emit 전송.

코드 레벨 근거

onEnd가 socket emit으로 이어지는 유일한 경로

// apps/web/components/pages/guest.tsx
const handleVideoEnded = () => {
  guestVideoActivity.notifyVideoEnd();
  trackEvent("Guest:VideoEnded", ...);
};

// apps/web/entities/guest-socket/model/use-guest-socket.ts
const notifyVideoEnd = useCallback(() => {
  logger.info("Notifying video ended");
  socket.emit("guest-video-end", { roomId: props.roomId });
}, [props.roomId]);

Notifying video ended가 없으면 guest-video-end emit이 호출되지 않은 것으로 볼 수 있다.

watchdog이 멈추는 조건

// apps/web/shared/ui/meet-video.tsx
useEffect(() => {
  isPlayingRef.current = props.isPlaying;
  if (props.isPlaying) watchdog.arm();
  else watchdog.disarm();
}, [props.isPlaying, watchdog]);

useEffect(() => {
  watchdog.reset(); // videoUrl 변경 시 이전 영상 감지 상태 초기화
  ...
}, [videoUrl, watchdog]);

// apps/web/components/pages/guest.tsx
isPlaying={mediaType?.startsWith("resource:video_") || false}

따라서 host step change, auto-finish에 의한 새 step 진입, force kick, lesson-ended 화면 전환이 발생하면 기존 영상의 watchdog은 더 이상 종료 이벤트를 보장하지 않는다.

실무 판단

질문판단근거
소켓 emit 실패인가? 대부분 아님 Notifying video ended 로그가 없어 emit 함수 자체가 호출되지 않았다.
Watchdog이 배포/동작하지 않았나? 아님 같은 로그군에서 Video onEnd triggered via watchdog backstop 정상 사례가 다수 존재한다.
Watchdog 설계가 이 장애를 커버하나? 커버하지 못함 현재는 media clock 기반. wall-clock duration 초과, step 변경 전, force kick 전 강제 emit 보장은 없다.
공통 개선 방향은? 별도 wall-clock 안전장치 영상 시작 시각 + duration + grace 기준으로, 아직 같은 video step이면 guest-video-end를 한 번 더 보장하는 레이어가 필요하다.
권장 후속: 현재 watchdog은 유지하되, MeetVideo 또는 게스트 세션 레벨에 “wall-clock based video end guard”를 별도 추가한다. 조건은 videoPlayStartedAt + duration + grace를 넘고, 같은 activityIndex/stepIndex/videoSrc가 유지 중이며, 아직 onEndCalled가 아니면 notifyVideoEndgoToNextStep을 idempotent하게 호출하는 방식이 적합하다.

관련 문서