![[실전 미디어 04] WASM 인코더로 갈아탄 이유](https://img.thenullpage.com/posts/5777/5777_1_a8a7ac.webp)
지난 편에서 브라우저 기본 인코더가 조용히 다른 포맷으로 대체되는 함정을 막는 1차 방어선을 세웠다. 하지만 그건 나쁜 결과를 감지해 걸러낸 것이지, 변환 자체를 믿을 수 있게 만든 건 아니었다. 감지에 걸려 실패로 처리되는 이미지가 여전히 있었다. 나는 브라우저 기본 인코더에 대한 신뢰를 접고, 별도의 WASM 인코더를 주력으로 삼는 쪽으로 방향을 틀었다. 이번 편은 그 전환의 이유와 방법이다.
1. 기본 인코더의 근본 한계
브라우저 기본 인코더의 문제는 구현이 브라우저마다 다르고, 실패를 조용히 대체로 숨긴다는 데 있었다. 같은 코드가 크롬에서는 webp를 잘 만들고 다른 브라우저에서는 PNG로 떨어질 수 있었다. 내가 결과를 통제할 수 없다는 뜻이었다.
실패의 조건도 예측하기 어려웠다. 면적 한도, 메모리 상태, 브라우저 버전이 얽혀 있어서 어떤 이미지가 실패할지 미리 알 수 없었다. 1차 방어선은 실패를 감지해 재시도하게 했지만, 재시도해도 같은 인코더면 같은 실패가 반복될 수 있었다. 잠깐 쉬었다 다시 하면 메모리 압박이 풀려 우연히 성공하기도 했지만, 그건 운에 기대는 것이지 근본 대책이 아니었다. 운영을 운에 맡길 수는 없었다.
결국 문제는 인코더 자체를 내가 고를 수 없다는 것이었다. 브라우저가 주는 인코더를 그대로 쓰는 한, 그 인코더의 변덕에 내 파이프라인 전체가 휘둘렸다. 그렇다면 브라우저에 의존하지 않고 내가 통제하는 인코더를 브라우저 안에서 돌리면 되지 않을까 하는 생각에 이르렀다.
마침 이 발상을 실현할 수단이 있었다. 웹어셈블리, 즉 WASM으로 컴파일된 인코더를 브라우저 안에서 실행하는 방법이었다. 네이티브 인코더의 코드를 그대로 브라우저에서 돌리니, 브라우저의 기본 구현과 무관하게 어디서나 동일한 결과를 낼 수 있었다.
2. WASM 인코더라는 대안
내가 택한 것은 구글이 만든 webp 표준 인코더를 WASM으로 포팅한 라이브러리였다. 이것은 브라우저의 webp 지원 여부나 구현 편차와 무관하게, 라이브러리 자체의 코드로 webp를 만든다. 어느 브라우저든 같은 입력에 같은 출력을 내는 일관성이 가장 큰 장점이었다. 브라우저가 webp를 얼마나 잘 지원하느냐가 더는 변수가 아니게 되니, 나는 사용자 기기의 편차를 걱정하지 않고 압축 로직을 짤 수 있었다. 통제권이 브라우저에서 내 코드로 넘어온 셈이었다.
동작 방식은 이랬다. 이미지를 원시 픽셀 데이터로 디코딩한 뒤 그 픽셀 배열을 WASM 인코더에 넘기면, 인코더가 표준 알고리즘으로 webp 바이트를 돌려준다. 코드로는 const webp = await encode(imageData, {quality: 85}) 같은 형태로, 결과가 진짜 webp임이 보장됐다. 조용한 대체 같은 건 없었다. 디코딩과 인코딩을 내가 명시적으로 분리해 다루니, 중간에 어디서 실패했는지도 정확히 짚을 수 있었다. 한 덩어리로 뭉쳐 있던 변환 과정이 관찰 가능한 단계들로 풀어진 것이다.
압축 품질도 기대 이상이었다. 표준 인코더 기반이라 같은 품질 설정에서 상용 압축 도구와 거의 동등한 크기가 나왔다. 브라우저 기본 인코더가 들쭉날쭉했던 것과 달리, 품질 값을 정하면 그 값에 맞는 예측 가능한 결과가 일관되게 나왔다.
무엇보다 실패가 명확했다. WASM 인코더는 실패하면 실패라고 알려줬다. 조용히 다른 포맷으로 떨어지는 대신 오류를 던지니, 나는 성공과 실패를 확실히 구분하고 그에 맞게 대응할 수 있었다. 이 예측 가능성 자체가 파이프라인 안정성의 핵심이었다. 실패가 실패로 드러나면 재시도든 안내든 대안을 프로그램으로 짤 수 있지만, 실패가 다른 결과로 위장하면 대응 자체가 불가능하다. 나쁜 성공보다 정직한 실패가 언제나 다루기 쉬웠다. 이건 이미지 처리에만 국한된 교훈이 아니라 시스템 설계 전반에 통하는 원칙이었다.
3. Web Worker에서 돌리기
WASM 인코더는 좋았지만 무거웠다. 이미지 인코딩은 CPU를 많이 쓰는 작업이라, 이걸 화면을 그리는 메인 스레드에서 돌리면 인코딩하는 동안 화면이 얼어붙었다. 여러 장을 연속으로 처리하면 사용자는 몇 초씩 멈춘 화면을 봐야 했다.
해법은 Web Worker였다. 인코딩을 별도의 백그라운드 스레드에서 돌리면 메인 스레드는 자유로워져서, 인코딩이 진행되는 동안에도 화면이 부드럽게 반응했다. 사용자는 여러 장을 올리면서도 스크롤하거나 다른 입력을 계속할 수 있었다. 화면이 얼어붙는 순간은 사용자에게 앱이 고장 났다는 인상을 주기 때문에, 무거운 작업일수록 백그라운드로 밀어내는 것이 신뢰를 지키는 길이었다. 성능은 절대 속도만의 문제가 아니라 반응성의 문제였다.
WASM 모듈 로딩도 신경 써야 했다. 인코더 모듈은 용량이 있어서 페이지 로드마다 무조건 받으면 낭비였다. 그래서 실제로 이미지를 처음 변환하는 순간에만 모듈을 동적으로 불러오고, 한 번 불러온 뒤에는 재사용하는 지연 로딩 방식을 썼다. 이미지를 안 올리는 사용자는 이 무게를 전혀 지지 않았다. 한 번 불러온 모듈은 세션 동안 메모리에 살아 있게 해서, 두 번째 이미지부터는 로딩 없이 곧바로 인코딩에 들어갔다. 첫 변환만 살짝 느리고 이후는 빠른, 사용자가 체감하기 좋은 곡선이었다.
이 구성으로 무거운 인코더를 쓰면서도 업로드 경험은 오히려 더 매끄러워졌다. 백그라운드에서 착실히 변환되는 동안 사용자는 멈춤 없이 작업을 이어갔고, 변환이 끝나는 대로 결과가 반영됐다. 무거움을 감추는 것이 성능 설계의 절반이었다.
4. 하이브리드로 안전망을 남기다
그렇다고 브라우저 기본 인코더를 완전히 버리진 않았다. WASM 모듈 로딩이 실패하거나 특정 환경에서 초기화가 안 되는 경우를 대비해, 기본 경로는 WASM 인코더로 하되 그게 안 되면 브라우저 기본 인코더로 떨어지는 하이브리드 구조를 택했다.
이 순서가 중요했다. 예전에는 브라우저 기본 인코더가 유일한 경로였고 그게 실패하면 답이 없었다. 이제는 신뢰도 높은 WASM 인코더가 먼저고, 그게 어떤 이유로 안 될 때만 예전 경로가 보조로 나선다. 주력과 보조의 자리가 뒤바뀐 것이다.
보조 경로에도 앞 편에서 만든 방어선은 그대로 적용됐다. 즉 기본 인코더로 떨어졌을 때도 결과물의 실제 타입을 검증하고 메모리를 회수했다. 어느 경로로 가든 최종 결과가 진짜 webp임을 보장하는 검증은 공통으로 유지했다. 인코더가 무엇이든 파이프라인의 출구에서 한 번 더 확인하는 이 원칙은, 특정 구성 요소를 통째로 갈아치워도 시스템 전체의 계약이 깨지지 않게 해주는 안전장치였다.
이 하이브리드 덕분에 변환 성공률이 크게 올랐고, 실패하더라도 그 사실이 명확히 드러났다. 브라우저마다 다른 변덕에 휘둘리던 파이프라인이 비로소 예측 가능한 시스템이 됐다. 통제할 수 없는 것을 통제할 수 있는 것으로 감싸는 방식이었다.
5. 클라이언트와 서버가 같은 인코더를
예상 못 한 부수 효과가 하나 있었다. 서버측에서 썸네일 등을 만들 때 쓰던 인코더도 같은 계열의 표준 webp 인코더였다는 점이다. 즉 클라이언트에서 압축하든 서버에서 처리하든 동일한 표준 알고리즘을 쓰게 됐다.
인코더가 통일되니 결과의 일관성이 좋아졌다. 어느 경로로 만들어진 webp든 압축 특성이 비슷했고, 품질 설정의 의미도 같았다. 클라이언트에서 만든 것과 서버에서 만든 것이 눈에 띄게 다르면 사용자가 혼란스러운데, 그런 불일치가 사라졌다. 어떤 사용자가 데스크톱에서 올린 사진과 다른 사용자가 모바일에서 올린 같은 사진이 거의 같은 크기와 품질로 저장되니, 목록과 본문의 이미지 품질도 고르게 유지됐다.
운영 관점에서도 이득이었다. 인코더가 하나면 압축 관련 이슈를 디버깅할 때 살펴볼 코드가 한 갈래로 줄었다. 품질 정책을 바꾸고 싶을 때도 같은 파라미터를 양쪽에 적용하면 됐다. 서로 다른 두 인코더의 특성 차이를 머릿속에 담아두지 않아도 되니, 새 기능을 붙일 때 고려할 변수가 줄었다. 시스템의 개념적 무게가 가벼워진 것이다.
여기까지가 정적 이미지 압축을 안정화한 이야기다. 하지만 이 모든 인코더는 한 장의 정지 이미지를 다룰 뿐이었다. 여러 프레임이 흐르는 움직이는 이미지 앞에서는 전혀 다른 문제가 기다리고 있었다. 다음 편에서는 GIF나 애니메이션 webp를 정적화하지 않고 움직임을 보존하는 방법을 다룬다.