WASI 0.3이 공식 출시되었으며, 비동기가 이제 WebAssembly 컴포넌트에 네이티브로 도입되었습니다. 안정화된 0.3.0 사양과 함께 런타임 및 툴체인 지원이 본격적으로 제공되고 있습니다.
WASI 0.3이 공식화되었고, 비동기는 이제 WebAssembly 컴포넌트에 네이티브로 탑재되었습니다. WASI 서브그룹은 WASI 0.3.0의 비준에 투표했으며, WASI를 WebAssembly Component Model의 비동기 기본 요소 위로 재정립했습니다. 이제 0.3.0 사양은 안정화되었고, 런타임 및 툴체인 지원도 현재 적용되고 있습니다.
WASI 0.2에서 wasi:io가 담당하던 작업들(pollables, input-streams, output-streams)은 이제 canonical ABI의 일부가 되었으며, Component Model은 이제 이러한 기본 요소를 네이티브로 제공합니다. 그 결과, WASI 0.2에서 0.3으로의 변경 사항 대부분은 전적으로 기계적 이며, 이전보다 시그니처를 크게 단순화합니다. 새로운 비동기 기본 요소는 Component Model의 canonical ABI의 일부로, 바인딩 생성기가 각 언어에 맞는 자연스러운 비동기 바인딩을 생성할 수 있게 해줍니다.
WASI 0.2에서는 각 컴포넌트가 자체 이벤트 루프/비동기 런타임을 필요로 했습니다. 이는 개별 컴포넌트를 호스트에서 실행할 수는 있었지만, 그 이벤트 루프들이 서로 조정할 방법은 없었다는 뜻입니다. 컴포넌트가 스트리밍이나 비동기 API를 사용하면, 다른 어떤 컴포넌트와도 조합할 수 없었습니다.
WASI 0.3에서는 이제 모든 컴포넌트가 공유하는 하나의 이벤트 루프를 관리하는 주체가 호스트가 되었습니다. 이는 canonical ABI에 stream<T>, future<T>, async를 일급 구성 요소로 추가함으로써 가능해졌습니다:
stream<T>와 future<T>는 리소스 타입처럼 동작합니다. 각각은 소유된 핸들이며, 컴포넌트 경계를 넘어 전달될 때 소유권은 호출자에서 피호출자로 이전됩니다. 리소스 타입과 달리, 이들은 빌릴 수 없습니다.future에 전달되면, 런타임은 그것을 기다리던 작업을 스케줄링합니다. 그 값이 여러 컴포넌트 경계를 거쳐 전달되었더라도 마찬가지입니다. 그 값을 전달하는 작성자는 호스트일 수도, 다른 컴포넌트일 수도, 심지어 읽기 쪽 끝을 보유한 동일한 컴포넌트일 수도 있습니다.io_uring 및 Windows의 IOCP/IoRing API와 유사합니다. 호환성이 필요한 프로그램을 위해 epoll/kqueue 스타일의 readiness API를 이 위에서 에뮬레이션할 수 있습니다.async func를 직접 내보내고 가져옵니다. WASI 0.2의 세 단계 start-foo / finish-foo / subscribe 방식은 사라졌습니다.0.3 인터페이스의 변경 대부분은 전적으로 기계적입니다. WASI 0.2는 비동기를 동작시키기 위해 다소 복잡한 우회가 필요했지만, 이제 비동기가 컴포넌트 모델에 네이티브로 들어왔기 때문에 이전과 같은 것을 훨씬 더 사용하기 편한 방식으로 작성할 수 있습니다. 다음은 WASI 0.2에서 wasi:io 패키지로 인코딩하던 패턴들과, Component Model async를 사용하는 0.3에서 그 패턴들이 어떻게 보이는지에 대한 개요입니다:
WASI 0.2 (wasi:io) | WASI 0.3 (Component Model) |
|---|---|
resource pollable | future<T> |
resource input-stream | stream<u8> |
resource output-stream | stream<u8> (쓰기 방향) |
poll(list<pollable>) | future에 대한 await (런타임 처리) |
리소스에 대한 subscribe() | 호출에서 future<...> 반환 |
start-foo / finish-foo | foo: async func(...) |
WASI 0.2의 문제 중 하나는 스트림의 각 읽기 호출에서 종료 오류를 인라인으로 드러냈다는 점입니다. 이는 호출자가 계속 읽을 때만 결과를 알 수 있다는 뜻이었습니다. 읽는 쪽이 일찍 중단하면, 스트림 종료와 오류를 구분할 수 없었습니다. WASI 0.3에서는 이제 스트림이 스트림 소비량과 무관하게 독립적으로 해결되는 추가 future를 반환하여, WASI 0.2의 스트림 상태 문제를 해결합니다:
// WASI 0.2
read-via-stream: func() -> result<input-stream, error-code>;
// WASI 0.3
read-via-stream: func() -> tuple<stream<u8>, future<result<_, error-code>>>;
컴포넌트 모델의 강력한 장점 중 하나는 다른 언어로의 바인딩과 다른 언어에서의 바인딩을 아주 쉽게 만들 수 있다는 점입니다. 여기에 일급 비동기가 추가되면서, 게스트 바인딩 생성기는 이를 활용해 해당 언어에 네이티브하게 느껴지는 비동기 바인딩을 만들 수 있게 되었습니다. 예를 들어 wasi:http/handler 인터페이스를 보겠습니다. 이 인터페이스는 비동기로 표시된 handle 함수 하나를 노출합니다:
interface handler {
handle: async func(request: request) -> result<response, error-code>;
}
Rust에서 이를 사용해 HTTP 서버를 구현하려면 wit-bindgen 크레이트를 사용할 수 있습니다. 이는 interface handler를 trait Guest에 매핑하고, handle: async func를 async fn handle에 매핑합니다:
use wasi::http::types::{ErrorCode, Request, Response};
impl Guest for Component {
async fn handle(request: Request) -> Result<Response, ErrorCode> {
// ...
}
}
게스트 바인딩 생성기를 위한 비동기 지원은 Python, JavaScript, C#, C 등 많은 언어에서도 진행 중입니다. 이 모든 언어는 stackless coroutines 에 의존합니다. 하지만 Component Model의 async ABI는 처음부터 stackful 및 stackless 코루틴을 나란히 수용하도록 설계되었습니다. 이러한 특성을 가진 언어의 예로는 Go가 있습니다. Go는 비동기 함수와 비비동기 함수를 따로 노출하는 대신, 런타임이 동기적으로 보이는 호출을 비동기 호출로 변환할 수 있고, “goroutines”라 불리는 가상 스레드를 통해 동시 실행을 제공합니다.
componentize-go를 사용하면 func Handle를 내보내는 방식으로 HTTP 서버를 구현할 수 있습니다. 이를 통해 goroutine이 블로킹 호출을 수행하면서 스트리밍 본문을 처리할 수 있습니다. 그러면 런타임은 ABI 경계에서 goroutine을 대기 상태로 두고, 스트림이 준비되면 프로그램의 나머지를 막지 않은 채 다시 재개합니다:
package export_wasi_http_handler
import (
. "wit_component/wasi_http_types"
. "go.bytecodealliance.org/pkg/wit/types"
)
func Handle(request *Request) Result[*Response, ErrorCode] {
tx, rx := MakeStreamU8() // ← 1. 채널 쌍 생성
go func() { // ← 2. 가상 스레드 생성
defer tx.Drop()
tx.WriteAll([]uint8("Hello, world!")) // ← 3. 채널에 쓰기
}()
response, send := ResponseNew( // ← 4. HTTP 응답 생성
FieldsFromList([]Tuple2[string, []byte]{
{F0: "content-type", F1: []byte("text/plain")},
}).Ok(),
Some(rx), // ← 5. 수신자를 HTTP 본문으로 전달
trailersFuture(),
)
send.Drop()
return Ok[*Response, ErrorCode](response) // ← 6. HTTP 응답 반환
}
이제 WASI 0.3이 컴포넌트 모델 비동기 지원과 함께 출시되었으므로, 게스트 툴체인과 호스트 런타임은 이 모든 기능을 안정화하기 위한 작업을 본격적으로 진행할 수 있게 되었습니다. 앞으로 몇 주와 몇 달에 걸쳐 각 프로젝트들이 WASI 0.3 지원을 발표하기 시작할 것입니다.
wasi:http의 변경 사항가장 큰 변화를 겪은 인터페이스는 wasi:http입니다. 단순히 폴링 기반 인터페이스를 네이티브 비동기 인터페이스로 기계적으로 변환한 것에 그치지 않고, world를 실제로 재구성하고 일부 핵심 추상화도 변경했습니다. 이제 wasi:http는 wasi:http/service와 wasi:http/middleware라는 두 개의 world를 노출합니다:
interface client { /* ... */ }
interface handler { /* ... */ }
// When used by guest bindings generators, grant the
// ability to make HTTP calls through the `client` import, and
// handle incoming HTTP requests through the `handler` export.
world service {
import client;
export handler;
}
// The middleware world is a super-set of the service world.
world middleware {
include service; // ← Do everything that `service` can do.
import handler; // ← But also pass incoming requests down to another handler.
}
middleware world는 0.2 시대의 proxy world를 대체하며, 다른 핸들러로 요청을 전달할 수 있는 HTTP 핸들러를 정의하는 데 사용됩니다. WASI 0.3에서 새로운 점은 이제 이것이 service chaining을 수행할 수 있다는 것입니다. 이는 컴포넌트를 서로 직접 조합할 수 있게 하는 패턴입니다. 즉, 다른 마이크로서비스와 자주 상호 운용하는 마이크로서비스 역할의 컴포넌트들은 네트워크를 거칠 필요가 없습니다. 대신 런타임이 동일한 프로세스 내부에서 이들을 서로 직접 조합하도록 선택할 수 있습니다. 대부분의 마이크로서비스에서 이는 다른 마이크로서비스를 호출하는 시간을 밀리초에서 나노초로 줄일 것이며, 이는 여섯 자릿수 규모의 차이입니다.
WASI 0.3이 출시되었다는 소식을 기쁘게 전합니다. 이는 다음을 의미합니다:
WebAssembly Components를 시작하기에 가장 좋은 곳은 Wasm Component Model book입니다. 변경 사항의 포괄적인 목록은 WASI 0.3.0 release notes에서 확인할 수 있습니다. WebAssembly Components와 WASI의 다음 단계가 궁금하다면 The Road to Component Model 1.0을 참고하세요.
WASI 0.3은 공동체의 작업입니다. 이번 릴리스를 설계하고, 토론하고, 다듬어 준 WASI 서브그룹의 모든 분들께, 이를 현실로 만드는 구현을 만든 런타임 및 툴체인 유지관리자들께, 그리고 이슈를 등록하고, PR을 검토하고, 사양을 더 나아지게 만든 어려운 질문을 던져 준 모든 기여자들께 감사드립니다. 또한 WASI 위에서 구축하는 사용자 여러분께도 감사드립니다. 여러분의 컴포넌트, 피드백, 그리고 릴리스 후보를 기꺼이 시도해 준 자세가 이번 출시에 반영된 결과를 만들었습니다.
Component Model의 네이티브 비동기는 수년에 걸쳐 만들어졌고, 이제 새로운 장을 엽니다. 비동기와 조합 가능성이 일급 개념이 되고, 동일한 기본 요소가 모든 언어를 뒷받침하는 장입니다. 여러분이 이 위에서 무엇을 만들지 무척 기대됩니다. WASI 0.3에 오신 것을 환영합니다.