- Authors

- Name
- Youngju Kim
- @fjvbn20031
들어가며 — "Docker보다 작고 JVM보다 빠른"
2019년 Docker 창시자 Solomon Hykes의 트윗이 화제였다.
"If WASM+WASI existed in 2008, we wouldn't have needed to create Docker."
논란이 많은 발언이었지만, 핵심은 분명했다. Wasm은 그저 브라우저용 기술이 아니다. 2025년 지금, Cloudflare Workers, Fastly Compute@Edge, Shopify Functions, Figma 플러그인, 그리고 심지어 Kubernetes 위의 SpinKube까지 — 전부 Wasm을 엔진으로 쓴다.
이 글에서는:
- WebAssembly의 탄생 — asm.js의 그림자에서 Wasm 1.0까지
- 바이트코드 구조와 스택 가상머신 실행 모델
- Sandbox의 수학적 보증 — 왜 컨테이너보다 안전한가
- WASI — 브라우저 밖의 Wasm 시스템 인터페이스
- Wasmtime / Wasmer / WasmEdge 런타임 비교
- Component Model — 언어 중립 모듈 시스템의 비밀
- Cloudflare Workers가 왜 Isolate 대신 Wasm도 지원하는가
- Kubernetes + Wasm — runwasi, SpinKube의 그림
- 실무 함정과 미래
이전 글 HTTP/3와 QUIC 완전 해부에서 전송 계층의 재설계를 봤다. Wasm은 실행 계층의 재설계다.
1. 탄생 배경 — asm.js가 남긴 숙제
JavaScript의 불운한 수명
2010년대 초, 브라우저는 사실상 "JavaScript만 돌아가는 운영체제"였다. 하지만 JS는:
- 동적 타입 → JIT이 타입 가정을 끊임없이 재확인
- 가비지 컬렉션 지연
- 고성능 수치 연산에 부적합
asm.js (Mozilla, 2013)
답은 JavaScript를 "서브셋"으로 쓰는 asm.js였다.
function add(x, y) {
x = x|0; // 비트 OR로 정수 강제 변환
y = y|0;
return (x + y)|0;
}
|0 같은 힌트로 타입을 "미리" 확정하면, 브라우저가 기계어 코드로 최적 컴파일 가능. Emscripten이 C/C++ → asm.js로 변환해 브라우저에서 Unreal Engine이 돌았다.
하지만 asm.js는 한계가 분명했다:
- 파싱 비용 (여전히 텍스트)
- 가독성 제로
- 명세가 "비공식"
Wasm 1.0 (2017, W3C 표준)
4개 브라우저 벤더(Mozilla, Google, Microsoft, Apple)가 모여 공식 바이트코드를 설계. 2019년 W3C 공식 권고. 이름은 WebAssembly.
특징:
- 바이너리 포맷 (파싱 빠름)
- 정적 타입 기반 안전성
- 스택 기반 가상 머신
- 선형 메모리 하나 + 호출 스택 분리
2. Wasm 모듈의 해부
최소 예제
(module
(func $add (param $a i32) (param $b i32) (result i32)
local.get $a
local.get $b
i32.add
)
(export "add" (func $add))
)
WAT(WebAssembly Text format)로 작성. 컴파일하면 바이너리 .wasm. 브라우저/런타임에서 add(3, 5) 호출 가능.
모듈의 Section 구조
.wasm 파일은 섹션의 나열이다.
[0] Custom - 메타데이터 (이름, 디버그 정보)
[1] Type - 함수 시그니처 목록
[2] Import - 외부에서 가져오는 항목
[3] Function - 함수 → 타입 인덱스 매핑
[4] Table - 함수 포인터 테이블 (동적 호출용)
[5] Memory - 선형 메모리 정의
[6] Global - 전역 변수
[7] Export - 외부에 노출하는 항목
[8] Start - 모듈 초기화 함수
[9] Element - Table 초기화
[10] Code - 함수 본문 (바이트코드)
[11] Data - Memory 초기화
핵심: 선형 메모리 (Linear Memory)
Wasm 프로그램은 하나의 큰 연속된 바이트 배열을 메모리로 본다. 64KB 단위 페이지. 기본 시작 크기와 최대 크기 지정.
(memory 1 10) ;; 최소 1 페이지(64KB), 최대 10 페이지
C/C++/Rust가 "포인터"라고 부르는 것은 이 배열의 **오프셋(i32)**에 불과. 프로그램이 할당/해제를 자체 관리한다 (malloc/free도 Wasm으로 컴파일된 코드).
중요: Wasm은 이 메모리 밖으로 절대 접근할 수 없다. 호스트의 메모리에도, 다른 모듈의 메모리에도. 이게 Sandbox의 1차 방어선이다.
스택 머신
Wasm은 스택 기반 VM.
local.get $a ;; 스택 push a
local.get $b ;; 스택 push b
i32.add ;; 스택에서 둘 pop, 더해서 push
레지스터 기반(예: x86)과 달리, 스택 명령은 단순하고 인코딩이 짧다. 런타임이 이를 기계어로 JIT/AOT 컴파일할 때 최적화 여지가 크다.
3. Sandbox의 수학적 보증
왜 Wasm은 안전한가
Wasm이 제공하는 격리:
- 메모리 격리 — 선형 메모리 밖 접근 불가. 호스트도 다른 Wasm 모듈도 못 건드림
- Control Flow 무결성 — 함수 포인터는 Table을 통해서만 호출. 임의 주소로 점프 불가
- 시스템 콜 없음 — 기본 Wasm은 외부 세계에 직접 접근할 수 없음. Import된 함수만 호출 가능
- 결정론적 — 동일 입력 → 동일 출력 (부동소수점 NaN 비트 규격화 등으로 결정성 확보)
Capability 기반 보안
컨테이너는 "모든 기본 권한 ON → 취약한 것만 OFF"(deny-list 방식). Wasm은 반대. "아무 것도 못 함 → 명시적으로 Capability 전달하면 가능" (allow-list, Capability 기반).
예: 파일 읽기. Wasm 모듈은 기본적으로 파일시스템에 접근 불가. 호스트가 fd_read 함수를 import에 제공했다면, 호스트가 어떤 fd를 열어줬는지까지만 접근 가능. 경로 조작(../etc/passwd)도 호스트가 root fd를 어떤 디렉토리로 줬는지에 따라 제한.
컨테이너 vs Wasm 보안 비교
| 측면 | 컨테이너 | Wasm |
|---|---|---|
| 격리 방식 | Linux namespace/cgroup | 가상머신 + 타입 안전 |
| 기본 권한 | 많음 (deny-list) | 없음 (allow-list) |
| 탈출 공격 면 | 커널 CVE (예: Dirty COW) | VM 명세 수학적 검증 |
| 시스템 콜 | 수백 개 | 호스트가 import한 것만 |
| 시작 시간 | 100ms~수초 | 1ms 미만 |
실제로 Fastly는 Wasm에서 발견된 sandbox 탈출을 "다른 Wasm 모듈을 공격" 수준도 아닌 "Wasmtime 버그로 OOM"으로만 경험한다고 보고.
4. WASI — 브라우저 밖으로 나가다
문제: 브라우저 API만으론 서버에서 못 씀
초기 Wasm은 브라우저 API (fetch, DOM) 의존. 서버에서 파일을 읽거나 소켓을 열 표준 방법이 없었다.
WASI 0.1 (2019) — POSIX-ish
Mozilla의 Lin Clark가 주도. POSIX처럼 보이는 시스템 인터페이스를 Wasm용으로.
wasi_snapshot_preview1
- fd_read / fd_write
- fd_seek
- path_open
- clock_time_get
- random_get
- args_get / environ_get
특징: 파일 디스크립터가 호스트가 명시적으로 준 것만 쓸 수 있음 (Capability).
WASI 0.2 (2024) — Component Model 기반
0.1의 한계:
- 오직 "동기 POSIX" 모델
- HTTP/sockets 표준 부재
- 인터페이스가 C 스타일로 고정
0.2는 Component Model 위에 재구축.
wasi:http— HTTP 요청/응답 처리 표준wasi:sockets— TCP/UDPwasi:io/streams— 비동기 스트림wasi:cli— CLI 앱용 stdio
언어 중립 인터페이스 정의 (WIT).
WIT 예시
package wasi:http@0.2.0;
interface types {
variant method {
get,
post,
put,
delete,
other(string),
}
record request {
method: method,
uri: string,
headers: list<tuple<string, string>>,
body: option<body>,
}
}
이 인터페이스는 Rust, Go, JavaScript, Python 각각의 바인딩으로 생성 가능. "언어 간 표준 API".
5. Component Model — 언어 중립의 비밀
왜 모듈만으론 부족한가
Wasm 1.0 모듈은 숫자와 바이트만 주고받는다. 문자열, 배열, 구조체는 선형 메모리 오프셋으로 직접 조율해야 한다. 언어마다 규칙이 달라 상호운용이 악몽.
예: Rust → JavaScript로 문자열 전달
- Rust는 UTF-8 길이+포인터
- JS는 UTF-16
- 변환 코드를 손으로 썼다
Component Model (2024 안정화)
Wasm 위에 **"컴포넌트"**라는 상위 단위. 컴포넌트 간:
- 고수준 타입 (string, record, variant, list, option, result)
- 자동 ABI 변환
- import/export를 WIT로 선언
핵심 이점
- 언어 X로 만든 컴포넌트를 언어 Y로 만든 컴포넌트에 꽂아 사용
- 버전 관리 — WIT 인터페이스의 semver
- Registry — warg.io 같은 컴포넌트 패키지 레지스트리 출현
실제 사례:
- Rust로 만든 이미지 처리 컴포넌트를
- Go로 만든 HTTP 핸들러에서 import해서
- JavaScript로 만든 오케스트레이터가 조립
npm + Docker + gRPC의 결합이라는 평가.
6. Wasm 런타임 3대장
Wasmtime (Bytecode Alliance)
- 기본 엔진: Cranelift (Rust 컴파일러 프로젝트)
- 언어: Rust
- 특징: WASI 표준 구현의 레퍼런스
- 안정성, 보안 감사 품질 최상
wasmtime run module.wasm한 줄로 실행- Fastly Compute가 백엔드로 사용
Wasmer
- 언어: Rust
- 컴파일러 플러그러블: Singlepass(빠른 JIT), Cranelift, LLVM(최고 성능)
- wapm 패키지 매니저
wasmer run명령, 임베딩 API 다양 (C, Go, Python 등)- 클라우드 기반 Wasmer Edge 제공
WasmEdge (CNCF Sandbox)
- 언어: C++
- 특징: AI 워크로드 특화 — Tensor 연산, ONNX 지원
- Docker + Wasm 통합(runwasi)에 활발
- 많은 임베디드/IoT 환경 지원
성능 비교 (2025 Bytecode Alliance 벤치)
| 런타임 | Startup | Throughput | Memory |
|---|---|---|---|
| Wasmtime (Cranelift) | 0.3ms | 100% | 기준 |
| Wasmer (LLVM) | 2s 컴파일 / 0.1ms 재실행 | 115% | 1.3x |
| WasmEdge | 0.5ms | 95% | 0.8x |
선택 기준:
- 표준 준수 → Wasmtime
- 극한 성능 → Wasmer LLVM
- 작은 메모리 + AI → WasmEdge
7. Cloudflare Workers — Wasm이 아닌 Isolate, 그러나 Wasm도
흔한 오해
"Cloudflare Workers = Wasm"이라고 알고 있는 경우가 많다. 실은:
- 기본은 V8 Isolate — JavaScript 런타임을 경량 격리 단위로 분리
- Isolate 하나 = 약 3MB 메모리, 5ms 콜드 스타트
- Wasm은 Isolate 안에서 실행 가능
왜 Isolate + Wasm인가
- JavaScript 생태계 그대로 씀 (npm)
- 성능 크리티컬 경로는 Wasm으로
- 예: Cloudflare Pages가 Rust → Wasm으로 이미지 변환
Fastly Compute@Edge — 완전 Wasm
Fastly는 전적으로 Wasm 기반.
- 각 요청이 새 Wasm 인스턴스
- 언어: Rust, Go (TinyGo), JS, AssemblyScript
- 콜드 스타트 35μs (마이크로초!)
1밀리초 미만 콜드 스타트가 가능한 이유: Wasm AOT 컴파일 결과를 재사용하고, Sandbox가 가볍기 때문.
8. Kubernetes에서 Wasm 돌리기
runwasi 프로젝트
containerd의 shim으로 들어가 OCI 이미지로 패키징된 Wasm을 컨테이너처럼 실행. 기존 Kubernetes 생태계에 그대로 얹힘.
apiVersion: v1
kind: Pod
spec:
runtimeClassName: wasmtime
containers:
- image: ghcr.io/example/hello-wasm:latest
name: hello
SpinKube (Fermyon, 2024)
Spin은 Fermyon의 Wasm 프레임워크. SpinKube는 Kubernetes CRD로 Wasm 앱을 관리.
장점:
- Pod이 수 밀리초에 뜸
- Deno/Node보다 훨씬 작은 메모리
- Serverless가 "항상 살아있는 것처럼" 반응
단점:
- 디버깅 도구 성숙 부족
- 모든 라이브러리가 Wasm 타겟 빌드되진 않음
- WASI 0.2 네이티브 지원이 아직 과도기
KWasm (Liquid Reply)
DaemonSet으로 Wasm 런타임을 노드에 설치. 기존 Kubernetes 클러스터를 Wasm 지원 환경으로 업그레이드.
9. 언어별 Wasm 여정
Rust — 황금 표준
cargo build --target wasm32-wasiwasm-bindgen로 브라우저 JS FFIwit-bindgen로 Component Model- 크레이트 생태계의 상당 부분이 Wasm 지원
Go — TinyGo가 구원
- 표준 Go 컴파일러는 큰 런타임 포함 (GC, goroutine)
- Go 1.21부터
GOOS=wasip1 GOARCH=wasm로 WASI 공식 지원 - 하지만 바이너리 크기가 여전히 몇 MB
- TinyGo는 LLVM 기반으로 훨씬 작은 출력 (수십 KB)
- 단점: 표준 라이브러리 일부 미지원
JavaScript/TypeScript — AssemblyScript
- TypeScript 문법으로 Wasm 작성
- 빠른 학습 곡선
- 그러나 풀 JS 생태계는 못 씀
Python — Pyodide, py2wasm
- 브라우저에서 Python 돌리는 Pyodide
- CPython 자체를 Wasm으로 컴파일 (10MB+)
- 서버리스용으론 무겁지만 교육/데이터 시각화 유용
C/C++ — Emscripten, wasi-sdk
- 원래 asm.js 시대부터 지원
- Unity/Unreal 엔진도 Wasm 빌드
10. 성능 — "네이티브의 80%"가 진짜인가
이론
Wasm 바이트코드는 실행 시 JIT/AOT로 기계어. 같은 C 소스를:
- Native (gcc) vs Wasm (clang → wasm → Wasmtime AOT)
- 대부분 벤치마크에서 80~95% 네이티브 속도
지연 요소들
- 경계 검사 — 선형 메모리 접근마다 bounds check. 현대 런타임은 OS의 가상 메모리 트릭(mmap + guard pages)으로 제로 오버헤드에 근접
- SIMD — Wasm SIMD 128비트 제안이 표준화. Rust의
std::simd일부를 Wasm에 매핑 - Atomics + Threads — 공유 메모리와 아토믹 연산 지원. 웹워커로 병렬 실행
- GC 제안 — 호스트 GC에 의존하지 않는 Wasm용 GC. TypeScript/Kotlin/Java를 효율적으로 Wasm으로
현실 — 병목은 FFI
"Wasm이 네이티브보다 느리다"는 벤치마크들, 파고 보면 대부분 FFI 횟수가 많은 케이스다. 호스트 함수를 자주 호출하면 boundary 전환 비용. 해결:
- Wasm 내부에서 최대한 처리
- 배치 호출
- 공유 메모리 기반 IPC
11. 실전 사례 — 누가 어떻게 쓰는가
Figma
플러그인 시스템을 QuickJS + Wasm으로. 서드파티 코드를 브라우저에서 안전하게 실행. Plugin API 제한된 capability만 노출.
Shopify Functions
서드파티 앱이 체크아웃 로직을 커스터마이즈 가능. Rust → Wasm으로 업로드. 지연이 극도로 민감한 영역이라 Wasm의 빠른 시작이 필수.
Adobe Photoshop Web
Photoshop 데스크톱 코드를 C++에서 Wasm으로 빌드. 브라우저에서 실제 Photoshop 커널이 돌아감. Emscripten 활용.
1Password
브라우저 확장의 암호화 모듈을 Rust → Wasm으로. 보안 감사와 성능을 동시 달성.
Envoy Proxy
WASM 필터로 동적 확장. Lua/Go 필터보다 성능 좋고 언어 선택 자유. Istio의 기본 확장 메커니즘.
12. 함정과 현실적 한계
한계 1: Thread 모델
Wasm의 병렬성은 여전히 제한적. 공유 메모리 원자 연산은 가능하지만:
- 웹워커 기반이라 메시지 기반 통신이 기본
- 리턴/재진입 불가한 런타임 많음
- goroutine/async Rust의 풍부함 대비 부족
한계 2: 네트워킹
WASI 0.2가 wasi:sockets 도입했지만:
- 브라우저에서는 여전히 fetch API만 (CORS/Same-Origin)
- 서버 Wasm에서도 라이브러리 지원이 제각각
한계 3: 파일시스템 일관성
"Capability 기반 fd"는 안전하지만:
- 경로 기반 코드 대량 포팅 번거로움
- POSIX
fork()/exec()부재 (의도적)
한계 4: 디버깅
- DWARF 지원 향상 중이지만 gdb/lldb 경험은 아직 네이티브 수준 아님
- Chrome DevTools가 Wasm 디버깅 제공하지만 소스맵 한계
- 스택 트레이스 읽기 난해
한계 5: 바이너리 크기
"경량"이라지만:
- Rust hello world: ~200KB (wasm32-wasi)
- TinyGo: ~50KB
- AssemblyScript: ~10KB
- Python/CPython: ~10MB
엣지에서 콜드 스타트하려면 수십 KB가 목표인데 Rust는 panic! 체인만 해도 덩치.
13. 보안 고려사항
1. Wasm 자체는 안전하지만 import는 위험
모듈이 fd_write를 imported했다면, 호스트가 안전한 구현을 제공해야 한다. "Wasm이라서 안전"은 틀리고, **"Wasm 런타임과 호스트 바인딩이 안전해야"**가 정확.
2. Denial of Service
- 무한 루프: Fuel 또는 Epoch 기반 인터럽트로 제한
- 메모리 폭주: Memory limit 명시
- 스택 오버플로: Wasm 명세가 제한 지원
3. Side Channel
- Spectre/Meltdown 류 타이밍 공격은 Wasm 내부에서도 가능
- 런타임이 timing-precision 제한 (1ms 단위로 라운딩 등)
4. Supply Chain
- 컴포넌트 레지스트리(warg)의 서명 검증 필수
- WIT 인터페이스 호환성 검증
14. 미래 — 어디로 가는가
임박한 표준들
- WASI 0.3 — 비동기/스트림 더 풍부
- Exception Handling — try/catch 표준화
- Stack Switching — 언어별 코루틴/async
- GC Proposal — Wasm GC (이미 Chrome 119+/Firefox 120+ 지원)
- Relaxed SIMD — 플랫폼별 SIMD 최적화
Trend 예측
- 서버리스 네이티브 — Lambda처럼 기동이 중요한 영역이 Wasm으로 이동
- 플러그인 시스템 공통 언어 — Kafka Connect, VSCode 확장 등이 Wasm 기반으로 재탄생 중
- Edge AI — WasmEdge + ONNX로 모델 추론을 엣지로
- Kubernetes 포드 대안 — Wasm 워크로드가 Pod의 일부 영역 대체
- 브라우저 + 서버 통합 — 같은 Wasm 모듈을 양쪽에서 실행
한계의 실명
- JavaScript 생태계의 중력 — 당분간 JS 대체 X
- POSIX 코드베이스의 막대함 — 전부 Wasm으로 못 옮김
- 디버깅/관측성 성숙도 — 2~3년은 더 필요
15. 실무 체크리스트 12가지
- 사용 사례가 명확할 때만 도입 — 플러그인, 서버리스, 클라이언트 고성능
- 컴파일러 옵션으로 바이너리 크기 최적화 —
--release,wasm-opt -Os,strip - WASI 0.2 Component Model 기반 선택 — 미래 호환성
- Wasmtime을 레퍼런스 런타임으로 — 표준 구현 확인
- Capability 최소 원칙 — 필요한 fd만 열어줌
- Fuel / Epoch로 실행 시간 제한 — DoS 방어
wasm-opt체인 — 크기 30%+ 감소- 언어 선택은 팀 역량 기반 — Rust가 가장 성숙, TinyGo는 Go 친화
- hot path는 Wasm, cold path는 JS/호스트 — 혼합 설계
- 관측성 — 호스트가 로그/메트릭 제공 — Wasm 내부 자체 로깅은 제한
- 메모리 한계 + OOM 핸들링 — 호스트에서 감싸기
- CI에서 여러 런타임 테스트 — Wasmtime, Wasmer, WasmEdge 호환성
다음 글 예고 — CDN과 엣지 컴퓨팅의 아키텍처
Wasm이 "서버 없는 서버"의 엔진이라면, 그걸 전 세계에 뿌리는 인프라가 CDN과 엣지 네트워크다. 다음 글에서는:
- CDN의 탄생 — Akamai 1998년부터 Cloudflare, Fastly까지
- Anycast 라우팅의 마법
- 캐시 계층 전략 — tiered, pull-through, push
- 엣지 컴퓨팅의 의미 — Lambda@Edge vs Workers vs Compute@Edge
- Cache Invalidation — "컴퓨터 과학의 두 난제 중 하나"
- DDoS 방어 — L3/L4/L7, Rate Limiting, Bot Management
- 이미지/비디오 변환의 엣지 처리
- Zero Trust 네트워크 — Cloudflare Access, Access Zero Trust
- 상위 CDN 벤더 비교 (Cloudflare/Akamai/Fastly/AWS CloudFront)
"사용자가 1초 안에 페이지를 본다"는 목표 뒤에 얼마나 많은 계층이 있는지, 거기서 Wasm이 어떻게 역할을 하는지 이어서 보자.
"인터넷은 평평하지 않다. 지구의 곡률을 따라 CDN이 깔려 있고, 그 위에서 Wasm이 뛰어다닌다."