업링크 stall 발생 시 웹 대처방안
마지막 업데이트 2026-09-29
아동 단말에서 SFU로 올라가는 실시간 미디어 업로드가 멈추거나 급격히 나빠졌을 때, 웹 클라이언트가 취할 수 있는 감지·완화·복구·운영 대응을 정리한다. 핵심은 “네트워크 자체를 고치는 것”이 아니라 오디오 우선 정책으로 수업 지속성을 확보하는 것이다.
1. 업링크 stall의 의미
여기서 업링크 stall은 transport가 반드시 끊긴 상태만 의미하지 않는다. transport는 connected이고 producer/track도 live인데 outbound bytes가 증가하지 않는 상태까지 포함한다.
RTT, 송신 대역폭, 패킷 손실 지표가 임계치를 넘는다. 현재는 네트워크 팝업·LogRocket 이벤트·진행자 relay 중심으로 동작한다.
미디어 track 또는 socket이 7초 이상 live가 아니면 critical로 올라간다. 화면 멈춤, 아동/AI 소리 끊김, 재접속으로 체감된다.
producer가 살아 있는데 1초 샘플에서 bytesSent가 늘지 않는다. 현재 코드는 로그와 15초 재확인만 수행한다.
2. 현재 코드의 대응 범위
| 상황 | 현재 대응 | 근거 코드 | 한계 |
|---|---|---|---|
| RTT ≥ 400ms, bitrate < 150kbps, packet loss > 5% | 게스트 네트워크 경고, Guest:NetworkAlertTriggered, monitor relay | apps/web/hooks/use-guest-network-alert.ts:148-257apps/web/hooks/network-alert-config.ts:2-15 | 자동 복구 액션은 없음 |
send transport disconnected | 3초 후에도 disconnected이면 transport cleanup 후 재생성 | apps/web/hooks/mediasoup/use-mediasoup-producer.ts:356-369 | connected 상태의 실송신 stall은 못 잡음 |
send transport failed | 즉시 reconnect 시도, 최대 3회 | apps/web/hooks/mediasoup/use-mediasoup-producer.ts:370-374:460-483 | 3회 실패 후 새로고침 안내 |
| audio producer outbound stall | Audio producer outbound stalled 로그, 15초 후 재확인 | apps/web/hooks/mediasoup/use-mediasoup-producer.ts:179-205 | producer 재생성·track 재획득·transport 재생성 없음 |
요약: transport 레벨 장애에는 자동 reconnect가 있지만, producer/track은 live인 connected-but-stalled 상태에는 진단만 있다.
3. 권장 복구 원칙
수업 진행자는 아동 음성이 들려야 개입과 수업 지속이 가능하다. uplink가 부족하면 camera-video를 먼저 낮추고, mic producer 복구를 우선한다.
큰 비용의 socket 재접속부터 하지 말고, producer 재생성·track 재획득·transport 재생성 순으로 blast radius를 키운다.
4. 웹에서 가능한 자동 대응 루프
감지
producer별 outbound stats를 2포인트 샘플링한다. bytesSentDelta === 0, track.readyState === "live", !producer.paused이면 stall 후보로 본다. camera-video에도 동일한 stats sampling을 확장한다.
즉시 완화
lowBitrate 또는 camera stall이면 camera-video bitrate/framerate를 낮추거나 producer를 일시 pause한다. mic-audio는 유지한다.
producer 재생성
해당 producer를 close하고 기존 live track을 clone해 produce()를 다시 수행한다. mic-audio는 동일 track clone 우선, 실패 시 getUserMedia({ audio: true }) 재획득으로 넘어간다.
track 재획득
track 자체가 mute/ended이거나 clone reproduce 후에도 stall이면 카메라/마이크를 재획득한다. 재획득 중에는 진행자에게 “복구 시도 중” 상태를 relay한다.
send transport 재생성
producer 복구 후에도 10~15초 이상 connected-but-stalled가 지속되면 기존 attemptReconnect() 경로를 재사용해 send transport를 cleanup/recreate한다.
fallback UX
복구 실패 시 아동에게는 재접속 안내, 진행자에게는 원인 channel과 추천 액션을 표시한다. 수업은 가능한 한 음성 우선 모드로 유지한다.
5. channel별 대응안
| reason/channel | 1차 대응 | 2차 대응 | 진행자 표시 |
|---|---|---|---|
lowBitrate | camera bitrate/framerate 강등 | camera producer pause, mic 유지 | 아동 업로드 대역폭 부족 · 음성 우선 전환 |
packetLoss | camera 강등, 복구 대기 | producer 재생성 후 transport 재생성 | 패킷 손실로 음성/영상 끊김 가능 |
cameraVideo | camera producer close/reproduce | 카메라 track 재획득, 저화질 produce | 아동 영상 업로드 복구 중 |
cameraAudio 또는 mic stall | mic producer close/reproduce | 마이크 getUserMedia 재획득 | 아동 마이크 업로드 복구 중 |
aiAudio | ai-audio producer reproduce | remote track clone 대신 AudioContext destination track 사용 | AI 음성 relay 복구 중 |
socket | socket reconnect/rejoin 확인 | room rejoin 후 transport 재생성 | 수업 신호 연결 재접속 중 |
6. 구현 스케치
// pseudo
if (producerStalled(type)) {
log("Audio/Video producer outbound stalled", detail);
if (type === "camera-video") {
degradeCameraQuality(); // 360p/10fps 또는 pause
reproduceCameraProducer();
}
if (type === "mic-audio") {
reproduceMicProducerWithClonedTrack();
if (stillStalled()) reacquireMicTrackAndProduce();
}
if (type === "ai-audio") {
reproduceAiAudioProducer();
if (stillStalled()) produceAudioContextDestinationTrack();
}
if (allRecoveryFailedFor(10_000)) {
recreateSendTransport(); // existing attemptReconnect path 재사용
}
}
7. 운영 판정 기준
Guest:NetworkAlertTriggered의reasonsAudio producer outbound stalledSend transport disconnected/failedguest-transport-state의 RTT/bitrate/packetLoss
- RTT/bitrate/packetLoss 악화 동반: 네트워크성 uplink 문제
- stats 정상인데 element/RMS만 0: 재생·출력 문제
- producer live + bytes 0: connected-but-stalled 복구 후보
권장 결론: 현재 PPI 웹은 stall을 관찰할 수 있는 기반이 있으므로, 다음 개선은 “오디오 우선 + producer 재생성 + camera 품질 강등 + 필요 시 transport 재생성”의 자동 복구 루프를 추가하는 것이다.