본문 바로가기
Infra2026년 8월 27일16분 읽기

WebAssembly 서버리스 — 콜드스타트 없는 엣지 컴퓨팅의 다음 단계

YS
김영삼
조회 8
WebAssembly 서버리스 — 콜드스타트 없는 엣지 컴퓨팅의 다음 단계

서버리스의 마지막 병목은 코드가 아니라 '시작'이다. WebAssembly는 이 시작 비용을 밀리초에서 마이크로초로 끌어내린다.

컨테이너 기반 함수(Function-as-a-Service)는 콜드스타트라는 구조적 세금을 안고 산다. Wasm은 무거운 OS·언어 런타임을 건너뛰고 격리된 선형 메모리 인스턴스를 거의 즉시 만들어낸다. 2026년 현재 Fastly, Cloudflare Workers, wasmCloud, Spin 같은 런타임이 이 모델을 프로덕션으로 밀어붙이고 있다. 무엇이 실제로 달라지고, 어디서 컨테이너를 대체할 수 없는지 짚는다.

~5µs
Wasm 인스턴스 생성 시간(런타임별 편차)
100ms~수초
전형적 컨테이너 콜드스타트 범위
수백KB~수MB
.wasm 아티팩트 크기(이미지 대비 극소)
수천/노드
한 노드에 올릴 수 있는 인스턴스 밀도

위 수치는 런타임·하드웨어·워크로드에 따라 크게 달라지는 대략적 특성이지 보장된 벤치마크가 아니다. 그러나 자릿수(order of magnitude) 차이는 일관되게 관찰된다. 이 글은 그 자릿수 차이가 어디서 오는지, 그리고 그 대가로 무엇을 포기하는지를 다룬다.

콜드스타트라는 세금

서버리스의 약속은 '요청이 없으면 0으로 스케일'이다. 문제는 0에서 1로 올라올 때다. 컨테이너 기반 FaaS는 요청이 도착하면 다음을 거친다: 컨테이너 이미지 확보 → 마이크로VM 또는 컨테이너 부팅 → 언어 런타임 초기화(JVM 워밍업, Node 부트, 파이썬 인터프리터 로드) → 사용자 코드 초기화. 각 단계가 수십~수백 밀리초를 먹고, 무거운 런타임에서는 초 단위로 튄다.

AWS Lambda의 Firecracker 마이크로VM은 이 부팅을 100ms대까지 줄였지만, 여전히 '가상 머신 하나를 새로 켜는' 일이다. 트래픽이 뜸한 함수, VPC 연결이 있는 함수, 콜드스타트가 사용자 대면 지연(latency)에 직접 노출되는 엣지 경로에서는 이 세금이 아프다. 프로비저닝된 동시성(provisioned concurrency)으로 인스턴스를 미리 켜두면 콜드스타트는 숨길 수 있지만, 그건 '항상 켜져 있는 서버'로 돌아가는 것이라 서버리스의 비용 모델을 훼손한다.

참고콜드스타트는 '평균 지연'보다 'p99 꼬리 지연'의 문제다. 요청의 99%가 워밍된 인스턴스에 붙어도, 1%가 콜드로 500ms를 맞으면 SLO는 그 1%가 결정한다. Wasm의 가치는 평균이 아니라 이 꼬리를 평평하게 만드는 데 있다.

Wasm은 왜 즉시 켜지는가

WebAssembly는 이식 가능한 바이너리 명령 포맷이다. 원래 브라우저용으로 설계됐지만, 서버 관점에서 중요한 건 실행 모델이다. Wasm 모듈은 선형 메모리(linear memory) 하나와 검증된 바이트코드로 구성된다. 런타임(Wasmtime, V8, Wizer 등)은 이 모듈을 미리 컴파일(AOT)해두었다가, 요청이 오면 새 인스턴스 = 새 선형 메모리 + 실행 스택만 할당한다. OS 프로세스도, 컨테이너 네임스페이스도, 언어 VM 부팅도 없다.

그래서 인스턴스화가 마이크로초 단위다. 격리는 프로세스 경계가 아니라 Wasm 샌드박스 자체가 제공한다. 모듈은 자기 선형 메모리 밖을 건드릴 수 없고, 명시적으로 임포트(import)한 함수 외에는 어떤 시스템 콜도 호출할 수 없다. 기본이 '아무것도 못 함'이고, 능력(capability)을 하나씩 주입받는 구조라 멀티테넌트 환경에 잘 맞는다.

Cloudflare Workers는 여기에 V8 isolate를 결합한다. 하나의 프로세스 안에서 수천 개의 isolate가 각자 격리된 힙을 갖고, 요청 사이에 컨텍스트만 스위칭한다. 이것이 '콜드스타트 없는 서버리스'라는 마케팅 문구의 실체다 — 프로세스가 아니라 힙 하나를 초기화하니까.

항목컨테이너 FaaSWasm 서버리스
격리 경계OS 프로세스/마이크로VMWasm 샌드박스(선형 메모리)
시작 단위밀리초~초마이크로초
아티팩트OCI 이미지(수십MB~).wasm 모듈(수백KB~수MB)
시스템 접근거의 전체(리눅스 커널)임포트한 능력만(WASI)
밀도노드당 수십~수백노드당 수천
이식성아키텍처/OS 종속아키텍처 독립(같은 바이너리)

WASI와 컴포넌트 모델

순수 Wasm은 파일도, 소켓도, 시간도 모른다. 서버에서 쓸모가 있으려면 표준화된 시스템 인터페이스가 필요하고, 그게 WASI(WebAssembly System Interface)다. WASI Preview 1은 POSIX 유사 파일·시계·랜덤을 제공했지만 한계가 뚜렷했다. 2024년 이후 표준화된 WASI 0.2(Preview 2)컴포넌트 모델(Component Model) 위에서 재설계됐다.

컴포넌트 모델의 핵심은 WIT(Wasm Interface Type)로 기술한 타입 있는 인터페이스다. 모듈이 선형 메모리에 숫자만 주고받던 저수준 ABI 위에, 문자열·레코드·리스트·리소스 같은 고수준 타입을 언어 중립적으로 정의한다. 그 결과 Rust로 만든 컴포넌트와 Go로 만든 컴포넌트가, 서로의 메모리 레이아웃을 몰라도 함수 호출로 조립된다. 이것이 'Wasm의 진짜 이식성'을 완성하는 조각이다.

// world.wit — 컴포넌트가 무엇을 임포트/익스포트하는지 선언
package example:greeter@0.1.0;

interface greetings {
  // 이름을 받아 인사말을 돌려주는 익스포트 함수
  greet: func(name: string) -> string;
}

world app {
  // 호스트가 제공하는 능력(로깅)만 임포트
  import wasi:logging/logging@0.1.0;
  export greetings;
}

위 WIT는 언어와 무관한 '계약'이다. 호스트는 greet를 호출할 수 있고, 컴포넌트는 wasi:logging 외에는 아무 능력도 갖지 못한다. 능력 기반 보안(capability-based security)이 인터페이스 수준에서 강제되는 것이다.

언어 지원 — Rust, Go, JS

Rust는 사실상 1급 시민이다. wasm32-wasip2 타깃이 안정적이고, cargo componentwit-bindgen이 WIT에서 타입 안전한 바인딩을 생성한다. 런타임(GC·리플렉션)이 없어 아티팩트가 작고 시작이 빠르다.

// lib.rs — 위 WIT를 구현하는 Rust 컴포넌트
wit_bindgen::generate!({ world: "app" });

use exports::example::greeter::greetings::Guest;

struct Component;

impl Guest for Component {
    fn greet(name: String) -> String {
        // 임포트한 로깅 능력 사용
        wasi::logging::logging::log(
            wasi::logging::logging::Level::Info,
            "greeter",
            &format!("greeting {name}"),
        );
        format!("안녕하세요, {name}님!")
    }
}

export!(Component);

Go는 두 갈래다. 표준 Go 컴파일러는 GC와 런타임이 무거워 Wasm 아티팩트가 크고 컴포넌트 모델 지원이 늦었다. 그래서 임베디드용 TinyGo가 오랫동안 실질적 경로였고, wasip2와 컴포넌트 지원도 TinyGo 쪽이 앞섰다. 2026년 현재 표준 Go도 WASI 지원을 넓히고 있지만, 최소 아티팩트가 필요하면 여전히 TinyGo가 유리하다.

JavaScript는 접근이 다르다. JS는 인터프리터 언어라 그냥 컴파일되지 않는다. 대신 엔진을 Wasm 안에 넣는다. StarlingMonkey(SpiderMonkey 기반)나 QuickJS를 Wasm으로 빌드하고, 그 안에서 사용자 JS를 실행한 뒤 wizer로 힙 스냅샷을 미리 떠 시작을 앞당긴다. Cloudflare Workers는 아예 V8 isolate를 직접 쓰므로 이 계층이 다르지만, Fastly Compute나 Spin의 JS SDK는 이 '엔진 임베딩' 방식을 쓴다.

주의언어별 '시작 빠름'은 조건부다. JS나 Python처럼 인터프리터를 Wasm에 임베딩하는 경우, 엔진 초기화 자체가 비용이라 Wizer 스냅샷 없이는 컨테이너만큼 느려질 수 있다. 'Wasm이니까 무조건 µs'가 아니라 '무엇을 컴파일했는가'가 시작 시간을 결정한다.

엣지 런타임 지형(2026)

Wasm 서버리스는 단일 제품이 아니라 여러 런타임의 생태계다. 성격이 제각각이라 선택 기준이 다르다.

  • Fastly Compute — Wasmtime 기반, 엣지 PoP에서 요청당 새 인스턴스를 µs급으로 생성하는 '요청마다 새 인스턴스' 모델. 강한 격리가 세일즈 포인트.
  • Cloudflare Workers — V8 isolate 중심. 순수 Wasm도 실행하지만 실전 대부분은 JS/TS. 글로벌 엣지 배포와 스토리지(KV·D1·R2·Durable Objects) 통합이 강점.
  • Spin(Fermyon) — 개발자용 Wasm 앱 프레임워크. spin.toml로 컴포넌트를 선언하고 트리거(HTTP·큐)에 묶는다. 로컬 개발 경험이 좋고 컴포넌트 모델을 정면으로 채택.
  • wasmCloud(CNCF) — 액터 모델 + 컴포넌트. 비즈니스 로직(컴포넌트)과 능력 제공자(capability provider)를 분리해, 같은 컴포넌트를 클라우드·엣지·온프레미스에 재배치. 분산 메시(NATS)로 연결.
# Spin 앱 스캐폴딩부터 배포까지 (예시 흐름)
spin new -t http-rust hello-edge
cd hello-edge

# 컴포넌트 빌드 (wasm32-wasip2 타깃)
spin build

# 로컬 실행 — 콜드스타트 없이 즉시 응답
spin up

# Fermyon Cloud / 셀프호스트로 배포
spin deploy

장점 vs 한계 — 정직한 손익

장점. (1) 콜드스타트 실종에 가까운 시작으로 p99 꼬리 지연이 평평해진다. (2) 기본 거부(deny-by-default) 능력 모델이라 공급망·멀티테넌시 보안에 유리하다. (3) 아키텍처 독립 바이너리 하나가 x86·ARM·엣지 어디서나 돈다. (4) 인스턴스가 가벼워 노드당 밀도가 높고, 그만큼 유휴 비용이 낮다.

한계. (1) 생태계가 아직 얇다 — 많은 크레이트·라이브러리가 wasip2에서 컴파일되지 않거나 스레드·소켓을 가정한다. (2) 시스템 접근이 의도적으로 제한적이라, 임의의 네이티브 라이브러리·파일시스템·저수준 네트워킹이 필요한 워크로드는 맞지 않는다. (3) 디버깅·관측성 도구가 컨테이너 생태계만큼 성숙하지 않다(DWARF 디버깅·프로파일링이 개선 중). (4) 긴 실행·상태 유지·대용량 메모리 워크로드는 Wasm의 강점(짧고 가벼움)과 어긋난다.

참고언제 쓰지 말아야 하나: 무거운 네이티브 의존성(예: 특정 ML 런타임, GPU), 장시간 배치, 로컬 디스크에 대용량을 쓰는 작업, 성숙한 컨테이너 관측성이 SLO에 필수인 경우다. 이때는 컨테이너/마이크로VM이 여전히 옳다.

컨테이너와의 공존

Wasm은 컨테이너를 대체하려는 게 아니라 겹쳐진다. 쿠버네티스는 runwasicontainerd shim을 통해 Wasm 모듈을 파드처럼 스케줄한다. RuntimeClass로 특정 워크로드만 Wasm 런타임(Wasmtime/WasmEdge)에 태우고, 나머지는 기존 컨테이너로 돌린다. OCI 레지스트리는 Wasm 아티팩트도 담을 수 있어 배포 파이프라인을 공유한다.

apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: wasmtime
handler: wasmtime   # containerd shim이 이 핸들러로 Wasm 실행
---
apiVersion: v1
kind: Pod
metadata:
  name: greeter
spec:
  runtimeClassName: wasmtime   # 이 파드만 Wasm 런타임에
  containers:
    - name: app
      image: registry.example.com/greeter:0.1.0  # .wasm 아티팩트

실전 패턴은 '요청 관문·변환·인증 같은 짧고 폭발적인 로직은 Wasm 엣지로, 상태·장기 실행·무거운 의존성은 컨테이너 백엔드로' 나누는 하이브리드다. 사이드카(Envoy·Istio)에서 Wasm 필터로 프록시 로직을 확장하는 것도 같은 흐름의 다른 얼굴이다.

2026년, 무엇이 실제로 움직였나

WASI 0.2가 안정된 뒤 컴포넌트 모델 도구(wit-bindgen, cargo component, jco)가 프로덕션 수준으로 올라왔고, wasip2를 타깃으로 하는 라이브러리가 늘고 있다. WASI 0.3는 async(비동기)를 언어 표준으로 끌어와 스트리밍·동시성 처리를 매끄럽게 하려는 방향으로 논의가 진행 중이다. 쿠버네티스 통합(runwasi)은 실험을 넘어 실전 배포 사례가 쌓이는 단계다.

냉정하게 보면, Wasm 서버리스는 '모든 워크로드의 미래'가 아니라 특정 형태의 워크로드(짧고, 격리가 중요하고, 엣지 지연이 SLO인)를 컨테이너보다 잘 처리하는 도구다. 콜드스타트 세금이 아팠던 팀에게는 이미 실용적인 선택지이고, 생태계 성숙과 함께 그 경계는 계속 넓어질 것이다.

자주 묻는 질문

Wasm 서버리스가 컨테이너 서버리스를 완전히 대체하나요?

아니요. 짧고 폭발적이며 강한 격리와 낮은 지연이 중요한 워크로드에서 유리할 뿐입니다. 무거운 네이티브 의존성, 장시간 실행, 대용량 상태를 다루는 작업은 컨테이너/마이크로VM이 여전히 적합합니다. 현실적 그림은 대체가 아니라 하이브리드 공존입니다.

Cloudflare Workers는 Wasm인가요, 아닌가요?

Workers의 격리 단위는 V8 isolate이며, JavaScript/TypeScript가 주력입니다. Wasm 모듈도 실행할 수 있지만 실전 대부분은 JS입니다. '콜드스타트 없음'의 원리는 프로세스가 아니라 isolate(격리된 힙) 하나만 초기화하기 때문이며, 이는 Wasm 인스턴스화가 가벼운 이유와 같은 계열의 아이디어입니다.

WASI Preview 1과 0.2(Preview 2)의 차이가 뭔가요?

Preview 1은 POSIX 유사 저수준 인터페이스로, 파일·시계·랜덤 정도를 제공하고 인터페이스가 언어 중립적 타입 시스템 없이 저수준 ABI에 묶여 있었습니다. 0.2는 컴포넌트 모델과 WIT 기반으로 재설계돼, 문자열·레코드·리소스 같은 고수준 타입을 언어 간에 안전하게 주고받고 능력을 인터페이스 수준에서 강제합니다.

JavaScript나 Python도 Wasm으로 빠르게 시작하나요?

조건부입니다. 이들은 인터프리터 언어라 엔진(QuickJS·SpiderMonkey 등) 자체를 Wasm으로 빌드해 그 안에서 실행합니다. 엔진 초기화가 비용이므로, Wizer로 힙 스냅샷을 미리 떠두지 않으면 시작이 컨테이너만큼 느려질 수 있습니다. Rust처럼 런타임이 없는 언어가 µs급 시작에 가장 유리합니다.

쿠버네티스에서 Wasm을 어떻게 돌리나요?

runwasi 기반 containerd shim과 RuntimeClass를 사용합니다. 특정 파드에만 runtimeClassName으로 Wasm 런타임(Wasmtime/WasmEdge)을 지정하면, 그 워크로드는 Wasm으로, 나머지는 기존 컨테이너로 함께 스케줄됩니다. .wasm 아티팩트는 OCI 레지스트리에 담아 기존 배포 파이프라인을 공유할 수 있습니다.

Wasm 서버리스의 가장 큰 실무 걸림돌은 무엇인가요?

생태계 성숙도입니다. 많은 라이브러리가 스레드·소켓·네이티브 확장을 가정해 wasip2에서 곧바로 컴파일되지 않고, 디버깅·프로파일링·관측성 도구가 컨테이너 생태계만큼 풍부하지 않습니다. 언어·런타임 조합을 먼저 작은 워크로드로 검증한 뒤 확대하는 접근이 안전합니다.

댓글 0

아직 댓글이 없습니다.
Ctrl+Enter로 등록