99% 미스터리: 내 ffmpeg.wasm 앱이 결승선에서 멈추는 이유
Source: Dev.to

저는 ffmpeg.wasm을 사용해 브라우저에서 완전히 비디오를 처리하는 도구 VideoSnap을 만들고 있습니다. 오랫동안 저를 괴롭힌 특정하고 답답한 버그, 일명 “99 % 트랩”이 있었습니다.
사용자가 파일을 업로드하면 진행 바가 부드럽게 올라가 99 %에 도달하고… 그 순간 모든 것이 멈춥니다. UI는 응답하지 않고, 팬은 돌아가며, 몇 분 후에야 다운로드가 나타나 마치 아무 일도 없었던 것처럼 보입니다.
99 %는 FFmpeg가 아니라—핸드오버
ffmpeg.wasm에서 진행 바는 FFmpeg 실행을 추적합니다. 99 %에 도달하면 실제로 트랜스코딩의 무거운 작업은 완료된 것입니다.
“멈춤”은 핸드오버 중에 발생합니다: engine.readFile()을 호출하여 처리된 비디오를 WebAssembly 가상 메모리(MEMFS)에서 JavaScript 힙으로 가져올 때.
“메모리 오버랩” 문제
- 피크: 99 %일 때, WASM 메모리는 500 MB 입력 파일 플러스 새로 생성된 500 MB 출력 파일을 보유합니다 → 1 GB의 WASM 메모리가 사용됩니다.
- 요청:
engine.readFile()을 호출합니다. 이제 JavaScript는 해당 데이터를 복사하기 위해 새로운 500 MBUint8Array를 할당하려고 합니다. - GC 폭풍: 브라우저는 이제 거의 1.5 GB–2 GB에 달하는 거대한 연속 메모리 블록을 관리하려고 합니다. 이는 “Stop‑The‑World” 가비지 컬렉션 이벤트를 일으킵니다. 엔진이 500 MB 크기의 빈 공간을 찾기 위해 메모리를 단편화 해제하려는 동안 메인 스레드가 잠기게 됩니다. 여러분이 보는 UI 정지는 바로 이 GC 스래싱입니다.
“수술적” 해결책: 겹침 끊기
입력 파일과 출력 파일이 MEMFS에 동시에 존재함으로 인해 정체가 발생한다는 것을 이해했을 때, 해결책은 명확했습니다: 큰 상자를 옮기기 전에 책상을 비우는 것이죠.
// Optimized handover logic
// 1. FFmpeg is done. Before reading the output, delete the input file
await engine.deleteFile('input.mp4');
// 2. Now the WASM memory has breathing room; read the result
const data = await engine.readFile('output.mp4');
// 3. Immediately delete the WASM copy of the output
await engine.deleteFile('output.mp4');
// 4. Create a Blob from the JS buffer
const blob = new Blob([data.buffer], { type: 'video/mp4' });
이 삭제 순서를 바꿈으로써, 브라우저가 가장 많은 메모리를 필요로 하는 순간에 발생하던 거대한 메모리 겹침을 없앨 수 있었습니다. 99 % 정지는 마법처럼 사라지지는 않으며—브라우저가 큰 JS 버퍼를 할당하는 데 시간이 여전히 필요하지만—이 정리 작업으로 GC가 뒤죽박죽 되는 중요한 몇 초를 절감하고, 무거운 파일 때문에 탭이 숨 막히는 상황을 방지할 수 있습니다.
왜 나는 WORKERFS 또는 OPFS를 사용하지 않았는가
- WORKERFS: 파일을 복사하지 않고 마운트하지만, 동기 I/O 브리지를 사용해 FFmpeg 실행 속도가 크게 느려진다. 메모리를 절약하기 위해 속도 페널티를 감수했지만 그만한 가치가 없었다.
- OPFS (Origin Private File System): 데이터를 직접 디스크에 스트리밍하며 미래형이지만,
WASMFS지원이 포함된 커스텀‑빌드 FFmpeg 코어가 필요하다—공식@ffmpeg/ffmpeg패키지는 기본적으로 제공하지 않는다.
요점: 핸드오버를 이해하라
고성능 WebAssembly 앱을 만들 때 기억하세요: 파이프라인에서 가장 위험한 부분은 데이터 핸드오버입니다.
WASM “world”와 JS “world” 사이에 대량의 데이터를 이동하면 브라우저가 거대한 연속 메모리 블록을 할당해야 합니다. 그 요청을 하기 전에 내부 상태를 정리하지 않으면 GC 폭풍을 초래하게 됩니다.
VideoSnap은 이제 훨씬 더 안정적이게 되었는데, 수학이 빨라졌기 때문이 아니라 메모리 수명 주기를 정밀하게 관리했기 때문입니다.
저는 VideoSnap의 제작자입니다. 브라우저에서 고성능 도구를 구축하는 혼란스러운 현실에 대해 씁니다. 더 많은 심층 분석을 원한다면 팔로우해주세요.