1. 브라우저에서 처음 만난 Wasm의 현실
저는 2024년 3월, 브라우저에서 이미지 배경 제거 기능을 빠르게 만들고 싶어 WebAssembly를 처음 붙였다. 당시에는 C++로 작성된 기존 이미지 처리 코드를 그대로 웹에 옮기면 자바스크립트보다 훨씬 빨라질 거라 기대했다. 하지만 첫 결과는 기대와 달랐다. 4MB짜리 Wasm 파일을 내려받고 초기화하는 데 제 노트북 크롬에서 약 1.8초가 걸렸고, 작은 이미지 한 장만 처리할 때는 오히려 순수 자바스크립트 코드가 더 빨랐다. 연산 시간만 보고 기술을 선택하면 안 된다는 사실을 그때 체감했다.
제가 처음 놓친 부분은 자바스크립트와 Wasm 사이의 데이터 복사였다. 이미지 픽셀 배열을 자바스크립트에서 Wasm 선형 메모리로 옮기고, 처리가 끝난 뒤 다시 꺼내는 과정이 매번 발생했다. 2,400×1,600 픽셀 이미지를 처리할 때는 픽셀 복사에만 평균 74ms가 추가됐다. 처리 함수 자체는 31ms였으니, 정작 연산보다 연결 코드가 더 오래 걸린 셈이다. 이후에는 메모리를 매번 새로 할당하지 않고 버퍼를 재사용했으며, 여러 장을 한 번에 넘기는 방식으로 바꾼 뒤 전체 시간이 126ms에서 68ms로 줄었다.
2. 속도보다 먼저 부딪힌 메모리 문제
Wasm을 쓰면서 제가 가장 크게 당황한 부분은 선형 메모리였다. C++ 코드에서는 익숙하게 포인터를 다뤘지만, 브라우저에서는 그 포인터가 자바스크립트 객체를 가리키지 않았다. 2024년 5월 테스트 중에는 해제된 버퍼의 주소를 다시 사용해 특정 이미지에서만 결과가 깨지는 오류가 발생했다. 작은 샘플에서는 멀쩡했고 20MB가 넘는 사진에서만 재현돼 원인을 찾는 데 이틀이 걸렸다. 브라우저 개발자 도구에 일반적인 자바스크립트 스택 오류가 남지 않아 더 오래 헤맸다.
제가 택한 해결책은 Rust로 경계면을 다시 작성하는 일이었다. Rust의 소유권 규칙이 처음에는 번거롭게 느껴졌지만, 버퍼의 생명주기를 명시하면서 실수가 눈에 띄게 줄었다. 다만 Rust를 Wasm으로 컴파일한다고 모든 코드가 작아지는 것은 아니었다. 정규식과 파일 처리 라이브러리까지 한꺼번에 포함하자 빌드 결과가 11.6MB로 늘어났다. 기능에 쓰지 않는 의존성을 걷어내고 LTO와 압축을 적용하니 배포 파일은 2.9MB가 됐다. 저는 이 경험 뒤에 속도 측정표와 함께 초기 파일 크기, 메모리 사용량, 모바일 실행 시간도 기록하기 시작했다.
3. 브라우저 밖으로 나갈 때 달라진 규칙
2025년 1월에는 같은 이미지 처리 모듈을 서버에서 실행해 달라는 요청을 받았다. 처음에는 브라우저에서 돌아가던 Wasm 파일을 Node.js에서 그대로 불러오면 된다고 생각했지만, 파일을 읽고 임시 결과를 저장하는 부분에서 막혔다. 브라우저의 Wasm 모듈은 파일 시스템 권한을 자동으로 갖지 않기 때문이다. Node.js 호스트 함수로 파일 접근을 연결하고, 입력 경로와 출력 경로를 명시적으로 넘긴 뒤에야 작업이 이어졌다. 실행 환경이 바뀌면 Wasm 코드보다 호스트와 연결되는 부분을 먼저 점검해야 한다는 교훈을 얻었다.
그 뒤에는 WASI를 이용해 서버 실행 구조를 바꿔 봤다. 제 테스트 환경은 Ubuntu 22.04, 4코어 가상 머신이었고, 10,000장의 1,920×1,080 이미지 변환 작업을 비교했다. 기존 Node.js 구현은 14분 12초, Wasm 모듈은 9분 47초가 걸렸지만, 첫 요청의 준비 시간은 Wasm 쪽이 420ms 더 길었다. 장시간 반복 작업에서는 이득이 있었고 짧은 요청만 처리하는 API에서는 차이가 거의 없었다. 그래서 모든 서버 코드를 Wasm으로 옮기지 않고, CPU 사용량이 높은 변환 함수만 분리했다. 범용 플랫폼이라는 말만 믿고 적용 범위를 넓히면 운영 복잡도만 커질 수 있다는 판단이었다.
4. 샌드박스가 주는 안도감과 착각
Wasm 모듈을 엣지 실행 환경에 올리면서 보안 구조도 직접 확인했다. 제가 작성한 모듈은 호스트가 넘긴 메모리와 함수만 볼 뿐, 서버의 파일 목록이나 환경 변수에 바로 접근하지 못했다. 실수로 디버그 코드에 파일 경로를 넣었을 때도 모듈 내부에서 해당 경로를 읽어 오지는 못했다. 이런 격리는 외부 코드를 받아 실행하는 서비스에서 마음을 조금 놓게 했다. 다만 샌드박스가 코드의 모든 위험을 없애지는 않았다.
실제로 2025년 4월, 이미지 변환 요청에 제한을 두지 않아 512MB 입력을 반복 처리하는 테스트가 들어왔고, 런타임 메모리가 빠르게 소진됐다. Wasm 메모리 접근은 격리돼 있었지만, 호스트가 허용한 실행 시간과 메모리 한도가 넉넉했던 탓에 서비스 전체가 영향을 받았다. 이후 요청 크기를 16MB로 제한하고, 호출당 300ms 타임아웃과 메모리 상한을 적용했다. 로그에는 모듈 이름, 입력 크기, 실행 시간, 트랩 발생 여부를 남겼다. 저는 Wasm의 샌드박스를 보안 장치 하나로 여기지 않고, 권한·자원 제한·입력 검증을 함께 묶어 운영하는 방식으로 바꿨다.
5. 제가 지금 Wasm을 선택하는 기준
제가 여러 프로젝트를 거치며 세운 기준은 단순한다. 이미 C, C++, Rust로 오랜 기간 안정적으로 쓰인 계산 코드를 웹과 서버 양쪽에서 재사용해야 하거나, 실행 환경을 자주 바꾸면서도 같은 결과를 유지해야 할 때 Wasm을 먼저 검토한다. 반대로 화면 상태 관리, 간단한 문자열 가공, 네트워크 요청처럼 호스트 API와 자주 대화하는 작업은 자바스크립트가 훨씬 편했다. 2026년 2월 진행한 내부 실험에서도 5ms 이하로 끝나는 함수는 Wasm으로 옮겨 얻은 이익이 거의 없었다. 호출 경계와 변환 비용이 계산 시간을 상쇄했기 때문이다.
처음 시작하는 분이라면 저는 큰 서비스를 통째로 바꾸기보다 작은 함수 하나를 떼어 양쪽에서 측정해 보라고 권하고 싶다. 입력과 출력 크기, 초기화 시간, 반복 실행 시간, 메모리 증가량을 같은 조건에서 기록하면 홍보 문구보다 현실적인 판단이 나온다. 제 경우에는 이미지 처리 함수 하나를 분리하는 데 3일이 걸렸지만, 이후 브라우저와 서버 코드의 중복이 420줄 줄었고 장애 원인도 좁히기 쉬워졌다. Wasm은 모든 문제를 대신 해결하는 만능 도구가 아니라, 경계와 비용을 직접 관리할 때 가치가 드러나는 실행 방식이다. 작은 실험과 실패 기록부터 남기는 접근이 독자 여러분의 시간과 운영 부담을 줄여 줄 겁니다.
실전에서 겪은 시행착오
2024년 3월 크롬에서 4MB Wasm 파일을 내려받고 초기화하는 데 1.8초가 걸려, 연산 속도만 보고 기술을 선택한 판단이 틀렸음을 확인했습니다.
2,400×1,600 픽셀 이미지에서 자바스크립트와 Wasm 사이의 픽셀 복사에 74ms가 추가되자 버퍼를 재사용하고 여러 장을 한 번에 넘겨 전체 시간을 126ms에서 68ms로 줄였습니다.
적용 전 꼭 확인할 주의사항
- Rust를 Wasm으로 컴파일할 때 정규식과 파일 처리 라이브러리까지 포함하면 빌드 결과가 11.6MB로 늘어날 수 있으므로 사용하지 않는 의존성을 먼저 확인해야 합니다.
- 브라우저 Wasm 모듈을 Node.js에서 실행할 때는 파일 시스템 권한이 자동으로 연결되지 않으므로 호스트 함수와 입력·출력 경로를 명시해야 합니다.
- 512MB 입력을 반복 처리한 테스트처럼 Wasm의 샌드박스만 믿으면 런타임 메모리가 소진될 수 있으므로 입력 크기 16MB, 호출당 300ms 타임아웃, 메모리 상한을 함께 설정해야 합니다.
'IT & 비즈니스' 카테고리의 다른 글
| 구글 클라우드 콘솔에서 내가 처음 헤맨 권한 설정 (0) | 2026.09.08 |
|---|---|
| 온디바이스 모델을 노트북에서 켜 보다 메모리에서 멈춘 실험 (0) | 2026.09.08 |
| AI 초안을 고치느라 처음부터 쓰는 것보다 늦어진 날 (0) | 2026.09.07 |
| 코드 리뷰를 AI에게 맡겼다 되돌린 오후 (0) | 2026.09.07 |
| 라즈베리파이에 추론을 올려보다 전원부터 막힌 기록 (0) | 2026.09.07 |