파일 처리 시 브라우저 메모리 한계
파일이 겨우 15 MB인데 탭 메모리가 부족하다고 나옵니다. 왜 그런가요?
디스크에 있는 파일은 압축된 상태입니다
오해의 전부를 한 문장으로 정리하면 이렇습니다. 파일 탐색기에서 보는 숫자는 압축된 데이터의 크기이고, 압축된 상태로는 아무것도 처리할 수 없습니다. 이미지를 크기 조정하거나 재압축하거나 변환하거나 분석하려면 먼저 원시 픽셀로 디코딩해야 하는데, 원시 픽셀은 어마어마하게 큽니다.
계산은 간단합니다. 메모리 안의 픽셀 하나는 보통 4바이트, 즉 빨강, 초록, 파랑, 알파입니다. 4000 × 3000 사진은 1200만 픽셀이므로 디코딩된 비트맵은 약 48 MB입니다. 그 사진이 나온 JPEG는 4 MB였을 수도 있습니다. 작업이 시작되기도 전에 파일이 12배로 불어난 것입니다.
| 이미지 크기 | 픽셀 수 | 디코딩된 비트맵 |
|---|---|---|
| 1920 × 1080 | 210만 | 약 8 MB |
| 4000 × 3000 | 1200만 | 약 48 MB |
| 6000 × 4000 | 2400만 | 약 96 MB |
| 10000 × 10000 | 1억 | 약 400 MB |
게다가 사본 하나로는 절대 끝나지 않습니다
실제 처리 과정은 여러 사본을 동시에 들고 있습니다. 압축된 입력 바이트, 디코딩된 원본 비트맵, 새 크기의 출력 비트맵, 인코더의 내부 버퍼, 그리고 압축된 결과물입니다. 미리 보기까지 보여 주는 도구라면 하나를 더 들고 있습니다. 앞의 4000 × 3000 사진이라면 최대 작업 집합이 200~300 MB에 이르는 것이 지극히 평범한 일입니다. 목록에서는 4 MB로 보이던 파일인데도 말이지요.
PDF 래스터화도 페이지 단위로 똑같이 동작합니다. 300 DPI로 렌더링한 A4 한 쪽은 대략 2480 × 3508픽셀이고 디코딩하면 약 35 MB이며, 여러 페이지를 한꺼번에 렌더링하는 도구라면 그만큼 곱해집니다. 동영상은 한 술 더 뜹니다. 디코더가 여러 참조 프레임을 동시에 메모리에 두어야 하기 때문입니다.
실제 상한이 있는 곳
- 탭의 메모리 예산은 사이트가 아니라 브라우저가 정하며, 컴퓨터에 설치된 메모리보다 작습니다. 다른 탭이 바쁘게 돌아가면 이 예산은 더 줄어듭니다.
- 널리 쓰이는 32비트 메모리 모델로 빌드된 WebAssembly 모듈은 최대 4기비바이트까지만 주소를 지정할 수 있고, 브라우저는 인스턴스당 그보다 훨씬 적게 허용하는 경우가 많습니다.
- 개별 버퍼에는 자체 최대 길이가 있으며, 이는 페이지가 쓸 수 있는 전체 메모리보다 한참 낮습니다.
- 모바일 운영체제는 너무 커진 탭을 종료해 버립니다. 경고도 없고, 페이지가 잡아낼 수 있는 오류도 없습니다.
- 그래픽 메모리는 별도의, 더 작은 풀입니다. 플랫폼의 최대 크기를 넘는 Canvas는 시스템 메모리가 아무리 남아 있어도 그냥 실패합니다.
이 중 어느 것도 찾아볼 수 있는 하나의 숫자로 공개되어 있지 않습니다. 브라우저, 버전, 기기, 그리고 그 밖에 무엇이 돌아가고 있는지에 따라 달라지기 때문입니다. 진짜 어려움은 여기에 있습니다. 도구는 자신이 메모리를 얼마나 써도 되는지 물어볼 방법이 없습니다.
휴대폰이 가장 빡빡한 경우인 이유
휴대폰은 물리 메모리가 적고, 데스크톱과 같은 의미의 스왑이 없으며, 백그라운드 프로세스에서 메모리를 회수하는 데 공격적인 운영체제를 씁니다. 브라우저 탭은 여러분이 메시지에 답하는 순간 백그라운드 프로세스가 됩니다. 그래서 모바일 브라우저는 예산을 더 빡빡하게 잡고 탭을 더 빨리 버리는데, 그 폐기는 사용자에게 오류라기보다 페이지가 새로 고쳐지면서 작업물이 사라지는 모습으로 보입니다.
현실적인 결과는 이렇습니다. 같은 파일이라도 노트북에서는 끝나는 작업이 휴대폰에서는 불가능할 수 있습니다. 이는 도구의 결함이 아닙니다. 하드웨어의 경계이며, 정직한 대응은 작업 도중에 멈춰 버리는 것이 아니라 시작하기 전에 그 사실을 알리는 것입니다.
신중한 도구가 하는 일
- 아무것도 할당하기 전에 디코딩된 크기로부터 작업 집합을 추정하고, 명백히 들어가지 않을 작업은 거부합니다.
- 여유가 빠듯한 기기에서는 여러 파일을 병렬로 돌리지 않고 한 번에 하나씩 처리합니다.
- 페이지와 워커 사이에서 버퍼를 복사하지 않고 이전하므로, 큰 파일이 두 번 존재하지 않습니다.
- 각 결과물은 기록되는 즉시 해제하고, 그대로 두면 데이터를 계속 살려 두었을 임시 URL도 무효화합니다.
- 포맷이 허용하는 경우 문서 전체를 메모리에 올리지 않고 페이지 단위나 프레임 단위로 흘려보냅니다.
- 받아들일 픽셀 수에 상한을 둡니다. 작은 파일이 거대한 Canvas로 펼쳐지면서 탭을 통째로 쓰러뜨리는 일을 막아 주는 것도 바로 이 상한입니다.
작업이 실패했을 때 할 수 있는 일
- 다른 탭을 닫으십시오. 같은 예산을 두고 경쟁하고 있으며, 브라우저는 그 예산을 공평하게 나눠 주지 않습니다.
- 한 번에 처리하는 파일 수를 줄이십시오. 하나씩 처리하는 대기열은 느리지만 끝날 확률이 훨씬 높습니다.
- 크기를 먼저 줄이십시오. 긴 변을 절반으로 줄이면 작업 집합은 4분의 1이 되고, 어차피 원하던 결과인 경우도 많습니다.
- 큰 PDF는 나눈 뒤 부분별로 처리하십시오.
- 가장 큰 작업은 데스크톱 컴퓨터에서 하십시오. 이는 로컬 처리의 실패가 아니라, 가진 하드웨어를 올바르게 쓰는 방법입니다.
- 탭을 며칠째 열어 두었다면 브라우저를 다시 시작하십시오. 오래 열려 있던 탭에는 메모리가 쌓이고, 새로 고침이 그것을 놓아줍니다.
모든 추정치의 한계
브라우저는 사용 가능한 메모리에 대해 대략적인 힌트만 제공하고, 아무것도 제공하지 않는 브라우저도 있습니다. 그러므로 로컬 도구가 보여 주는 수치는 운영체제에서 읽어 온 값이 아니라, 파일 자체의 크기와 처리 과정에 대한 보수적인 모형으로 계산한 추정치입니다. 이 값은 작업을 시도할지 말지 정하는 데는 쓸모가 있습니다. 하지만 작업이 완료된다는 약속은 아닙니다. 결정적인 요인, 즉 앞으로 30초 동안 기기의 나머지 부분이 무엇을 할지는 웹 페이지가 볼 수 없기 때문입니다.
관련 도구
출처
- W3C — WebAssembly Core Specification, memory model
- W3C — Device Memory API, and why the value is coarse
- WHATWG — HTML Standard, transferable objects