- Authors

- Name
- Youngju Kim
- @fjvbn20031
- はじめに — ブラウザは始まりにすぎなかった
- 三つの性質 — なぜWASMが汎用ランタイムなのか
- WASI — WASMがシステムと対話する方法
- エッジとサーバーレス — コンテナを脅かす
- プラグインシステム — WASMが正解になる場所
- コンポーネントモデル — モジュールを越えて
- なぜこれが汎用ランタイムなのか — 大きな絵
- 冷静な現実 — まだ残る課題
- おわりに — ランタイムの再発明
- 参考資料
はじめに — ブラウザは始まりにすぎなかった
WebAssembly(略してWASM)を初めて聞くと、たいてい「ブラウザでC++を動かす技術」くらいに理解します。間違いではありません。WASMは実際にブラウザで生まれ、JavaScriptには荷が重い重い計算をウェブで可能にするために作られました。
しかしWASMの本当の野心はブラウザの外にあります。よく考えてみると、WASMがブラウザで解決した問題(安全に隔離され、どんな言語からもコンパイルでき、近ネイティブ速度で動く移植可能なバイナリ)は、ブラウザとは何の関係もない性質です。これらの性質は、サーバーでも、エッジでも、プラグインシステムでも同じように魅力的です。
この記事は、ブラウザを離れたWebAssemblyを扱います。WASMがシステムと対話するために作ったWASIというインターフェース、エッジとサーバーレスでなぜWASMがコンテナを脅かすのか、プラグインシステムでなぜWASMが正解になりつつあるのか、そしてコンポーネントモデルがこのすべてをどう結び付けるのかを見ます。WASMがどうやって近ネイティブ速度を出すのか、その原理をブラウザで直接試したいならWASM原理ラボが良い出発点で、WAT(WebAssemblyテキスト)を手で書いてバイナリにコンパイルしてみたいならWebAssemblyスタジオで実験できます。
三つの性質 — なぜWASMが汎用ランタイムなのか
WASMがブラウザを越えて広がる根本的な理由は、三つの性質にあります。この三つはもともとブラウザのために設計されましたが、実は はるかに広い場所で通用する普遍的な美点でした。
- 近ネイティブ速度:WASMは事前コンパイルされた低レベルのバイトコードなので、パースが速く、JIT/AOTで機械語に近く実行されます。インタプリタ言語のように遅くなく、配布はソースではなく移植可能なバイナリで行います。
- サンドボックス隔離:WASMモジュールは、デフォルトでは何もできない状態から始まります。メモリは自分の線形メモリに閉じ込められ、システム資源にはホストが明示的に渡したものだけアクセスします。信頼できないコードを動かすのに、これ以上良い出発点はありません。
- 移植性:WASMバイトコードはCPUアーキテクチャ(x86、ARMなど)に依存しません。一度コンパイルした
.wasmを、どのアーキテクチャ、どのOSのWASMランタイムでもそのまま動かせます。「一度ビルドしてどこでも実行」という長年の夢にかなり近い形です。
この三つの性質を並べてみると、これがなぜ強力な組み合わせなのかが見えてきます。コンテナは隔離と移植性を与えますが、重くて起動が遅いです。ネイティブバイナリは速いですが隔離が弱くアーキテクチャに縛られます。インタプリタ言語は移植性が良いですが遅いです。WASMはこの三つの良い点(速い、安全、移植可能)を一つの封筒に入れようとする試みです。この組み合わせが効く場所がブラウザだけではない、というのが核心です。
WASI — WASMがシステムと対話する方法
ここで根本的な問題が一つあります。WASMモジュールはデフォルトでは何もできません。ファイルを読むことも、ネットワークに接続することも、時計を見ることもできません。ブラウザではこれは問題になりません。JavaScriptが必要な機能をすべて渡してくれるからです。ところがブラウザの外では? JavaScriptのないサーバーでWASMがファイル一つ読むのに、何に頼ればいいのでしょうか。
この空白を埋めるのがWASI(WebAssembly System Interface)です。WASIは、WASMモジュールがシステム資源にアクセスするための標準化されたインターフェース、いわばPOSIXふうのシステムコール規約です。ファイルを開く、読む、書く、時計を読む、乱数を得るといった基本機能を、WASMモジュールが呼び出せる標準関数として定義します。
核心は、WASIが能力ベースのセキュリティ(capability-based security)モデルに従う点です。従来のプログラムは、実行された瞬間、そのユーザーがアクセスできるすべてのファイルにアクセスできます。WASIモジュールは違います。デフォルトではどのファイルにもアクセスできず、ホストが明示的に渡したディレクトリやファイルハンドルだけを使えます。
従来のプロセス:
実行 = 「このユーザーの持つすべての権限をそのまま継承」
(プログラムが好き放題にファイルシステム全体を漁れる)
WASIモジュール:
実行 = 「何の権限もなく開始」
ホストが渡したものだけ使用可能
(例: 「この一つのディレクトリだけ読め」— 残りは存在すらしない)
このモデルの含意は大きいです。WASMモジュールにちょうど必要な能力だけを与え、残りをまったく見えなくできます。信頼できないコードを動かすとき、「このプログラムが誤ってか悪意でか何に触れられるか」という心配が根本的に減ります。隔離が「デフォルト遮断、明示的に許可」という安全な方向にひっくり返っているからです。
WASIは進化し続けています。初期のWASIがファイルや標準入出力といった基本に集中していたのに対し、以降のバージョンはネットワーキングや非同期I/Oといったより豊かな機能へ範囲を広げています。この進化の方向が、後で扱うコンポーネントモデルと噛み合います。
エッジとサーバーレス — コンテナを脅かす
WASMがブラウザの外で最も熱く使われている場所が、エッジコンピューティングとサーバーレスです。FastlyのCompute、Cloudflare WorkersといったプラットフォームがWASMを実行単位として採用したことで、「リクエストをどう処理するか」の構図が変わりつつあります。
なぜよりによってWASMなのでしょうか。サーバーレスとエッジの核心的な課題はコールドスタートです。リクエストが来たとき、コードを実行する環境をどれだけ速く立ち上げられるかが鍵です。ここでコンテナとWASMの違いが際立ちます。
- コンテナ:隔離のためにLinuxのネームスペース、cgroup、ファイルシステムのレイヤーをセットアップします。軽量とはいえ、コールドスタートが数百ミリ秒から秒単位になりえます。
- WASMモジュール:すでにサンドボックスが言語レベルで保証されるので、重いOSレベルの隔離をセットアップする必要がありません。インスタンス生成が極めて軽く、コールドスタートがミリ秒以下に下がります。
この違いが生む結果は大きいです。コールドスタートが事実上消えると、リクエストごとに新しいインスタンスを立ち上げることが負担でなくなります。すると、リクエストをユーザーに最も近いエッジノードで、必要なときだけ一瞬で立ち上げて処理し捨てるモデルが現実的になります。各エッジノードに重いコンテナオーケストレーションを入れる必要なく、軽量なWASMランタイム一つで多数のテナントのコードを安全に隔離して動かせます。
密度(density)の面も重要です。WASMインスタンスはコンテナよりはるかに軽いので、同じハードウェアではるかに多くのインスタンスを同時に動かせます。エッジのように資源が限られた環境で、この密度はそのまま経済性です。一つのプロセスの中で数千の隔離されたWASMインスタンスを動かすことは、数千のコンテナを動かすことよりはるかに安価です。
もちろんトレードオフもあります。WASM/WASI環境は完全なLinuxではないので、既存のコンテナイメージをそのまま移せません。アプリケーションがWASIのまだ対応していないシステム機能に依存すると行き詰まります。つまりエッジWASMは「軽くて速いが制限された」環境であり、完全なOSが必要なワークロードには依然としてコンテナが合います。
プラグインシステム — WASMが正解になる場所
WASMが静かに、しかし決定的に地歩を固めているもう一つの領域がプラグインシステムです。あるアプリケーションに第三者が作ったコードを安全に組み込みたいとき、WASMはほぼ理想的な答えになります。
プラグインの古いジレンマを考えてみましょう。プラグインは強力でこそ有用ですが、強力なほど危険です。ネイティブプラグイン(たとえば共有ライブラリ)はホストプロセスと同じアドレス空間で動くので、バグ一つがホスト全体を殺し、悪意あるプラグインが何でもできます。逆にプラグインを別プロセスに隔離すると安全ですが、遅く複雑になります。
WASMはこのジレンマを綺麗に解きます。プラグインをWASMモジュールにすれば、ホストと同じプロセスの中で速く動きながらサンドボックスで隔離されます。プラグインはホストが明示的に渡した関数だけを呼べ、ホストのメモリを好き放題に触れません。おまけにどんな言語でもプラグインを書けるので、プラグイン開発者がホストと同じ言語を使う必要もありません。
実際の事例がこの方向を証明します。
- Envoyプロキシ:ネットワークプロキシEnvoyは、WASMでフィルタを拡張できるようにしました。トラフィックを傍受して操作するカスタムロジックを、プロキシを再コンパイルせずにWASMモジュールとして注入します。この拡張規約が発展し、proxy-wasmというインターフェースとして標準化されました。
- Figma:デザインツールFigmaは、プラグインをサンドボックスで安全に動かすためにWASMベースのアプローチを活用します。第三者プラグインがユーザーのデザインデータを扱いつつ、その実行を安全に閉じ込めます。
- データベース:いくつかの現代のデータベースが、ユーザー定義関数(UDF)や拡張をWASMで実行する方向を探っています。ユーザーの書いたコードをデータベースプロセスの中で動かすことは従来きわめて危険でしたが、WASMのサンドボックスがこれを安全にします。
これらの事例の共通点は、「信頼できない拡張コードを、ホストを脅かさずに、速く実行」する必要です。これがまさにWASMの三つの性質(速い・安全・言語非依存)が輝く地点です。プラグインシステムを新しく設計する人なら、今やWASMを真剣に候補に挙げる価値があります。
コンポーネントモデル — モジュールを越えて
ここまで話してきたWASMは、たいてい「モジュール」という単位でした。しかし純粋なWASMモジュールには実務で痛い限界があります。モジュール間で、あるいはモジュールとホストの間でやり取りできるのが、基本的に数値(整数と浮動小数点)だけだということです。文字列一つ、リスト一つ、構造体一つを渡そうとしても標準化された方法がなく、各自でメモリのポインタと長さをやり取りする低レベルの規約を手で合わせる必要がありました。
この問題を解くのがコンポーネントモデル(Component Model)です。コンポーネントモデルは、WASMモジュールの上に高レベルの型システムとインターフェース定義を乗せます。文字列、リスト、レコード、バリアントといった豊かな型をインターフェースのレベルで定義し、異なる言語で書かれたコンポーネントたちがこの型を通じて自然に通信できるようにします。
核心のアイデアを整理するとこうです。
- インターフェースを言語中立に記述する:WIT(WebAssembly Interface Types)という言語で「このコンポーネントはこういう関数を提供し、こういう型をやり取りする」を宣言します。この記述は特定の言語に縛られません。
- 言語間の合成が自然になる:Rustで書いたコンポーネントとGoで書いたコンポーネントが、互いの内部メモリ表現を知らなくてもインターフェース型を通じて安全に値をやり取りします。文字列は文字列として、リストはリストとして行き来します。
- 再利用と組み立てが易しくなる:コンポーネントをレゴブロックのように組み合わせられます。あるコンポーネントの出力を別のコンポーネントの入力につなぎ、必要な部分だけ差し替えます。
コンポーネントモデルが重要な理由は、これがWASMを「ブラウザでC++を動かす技術」から「言語中立なソフトウェア組み立て規格」へ格上げするからです。先に見たWASIの最新の方向も、このコンポーネントモデルの上で定義されます。つまりシステムインターフェースさえコンポーネントインターフェースで表現され、プラットフォームが提供する能力とモジュールが要求する能力が同じ型言語で噛み合います。
なぜこれが汎用ランタイムなのか — 大きな絵
ここまでの断片を一つにまとめてみましょう。WASMは近ネイティブ速度で動き、言語に依存せず、強くサンドボックスされ、アーキテクチャに移植可能です。WASIがシステムアクセスを能力ベースで標準化し、コンポーネントモデルが言語間の組み立てを可能にします。この絵を遠くから見ると、見覚えのある何かが浮かんできます。まさに「汎用ランタイム」の輪郭です。
歴史的に私たちは「一度書いてどこでも動かす」という夢を何度も追いました。そのたびに代償がありました。移植性を得るために重いランタイムを入れたり、性能を諦めたり、特定の言語に閉じ込められたりしました。WASMが興味深いのは、これらの代償を同時に減らそうとする点です。
- コンテナより軽い:OSレベルの隔離なしに言語レベルで隔離するので、コールドスタートと密度で有利です。
- 言語ランタイムより移植的:特定の言語VMに縛られず、どんな言語でもコンパイルして入れられます。
- ネイティブより安全:サンドボックスがデフォルトなので、信頼できないコードを動かすのに適します。
これがWASMがブラウザを越えてサーバー、エッジ、プラグイン、さらにはIoTまで広がる理由です。「安全に隔離された、速く移植可能なコード実行単位」という必要はどこにでもあり、WASMはその必要へのかなり良い答えだからです。
冷静な現実 — まだ残る課題
もちろん、WASMがすべてを征服したという話は誇張です。ブラウザの外のWASMには、まだ成熟途上の部分が多くあります。
- エコシステムの成熟度:コンテナのエコシステムは、長年かけて鍛えられた膨大なツール、レジストリ、運用ノウハウを備えています。WASM側は速く発展していますが、まだそこまで厚くありません。プロダクション運用に必要な可観測性、デバッグ、デプロイのツールが引き続き埋められている最中です。
- WASIの標準化が進行中:ネットワーキング、非同期、ファイルシステムなど核心機能の標準がまだ進化しています。標準が固まっていく過程なので、バージョン間の差異や未対応の機能にぶつかることがあります。
- 言語ごとの対応の偏り:RustのようにWASMターゲットが成熟した言語がある一方で、ランタイムが重くWASMに合いにくい、あるいは対応が初期段階の言語もあります。どの言語で何をコンパイルするかによって体験が大きく異なります。
- 完全なシステムではない:強調してきたとおり、WASM/WASIは完全なOSではありません。既存のLinuxアプリケーションをそのまま移そうとすると、対応していないシステムコールに阻まれます。WASMは「新しくその上に合わせて書く」環境であって、「既存のものをそのまま入れる」環境ではありません。
これらの課題を総合すると、ブラウザの外のWASMは「潜在力は明確だが、まだ埋められている最中」の技術です。今日すぐにすべてのコンテナを置き換えることはできませんが、信頼できないコードを軽く隔離する必要のある特定の領域(エッジ関数、プラグイン、マルチテナント実行)では、すでに実戦で優位を見せています。
おわりに — ランタイムの再発明
WebAssemblyはブラウザで生まれましたが、それが本当に再発明しているのは「コードを安全に速く、どこでも実行する方法」そのものです。近ネイティブ速度、サンドボックス隔離、アーキテクチャ移植性という三つの性質は、ブラウザだけの必要ではなくコンピューティングのどこにでもある必要であり、WASMはその必要に一つの封筒で答えます。
WASIがシステムとの対話を能力ベースで標準化し、エッジとサーバーレスがコールドスタートの壁を崩し、プラグインシステムが信頼のジレンマを解き、コンポーネントモデルが言語の障壁を下げます。これらの断片が集まると、WASMは特定のプラットフォームの技術ではなく、プラットフォームを横断する汎用実行規格に近づきます。
もちろん、行く道は残っています。エコシステムは埋められている最中で、標準は固まっている最中で、完全なOSを置き換えもしません。しかし方向は明確です。次に「この信頼できないコードをどう安全に動かすか」「このロジックをどう複数の言語と複数の環境で再利用するか」を悩むことがあれば、ブラウザの外のWebAssemblyを候補リストに載せておいてください。そのリストでWASMが占める位置は、毎年広がっています。
参考資料
- WebAssembly公式サイト: https://webassembly.org/
- WASI: https://wasi.dev/
- コンポーネントモデル (WebAssembly): https://component-model.bytecodealliance.org/
- Bytecode Alliance: https://bytecodealliance.org/
- Fastly Compute: https://www.fastly.com/products/compute
- Cloudflare Workers: https://developers.cloudflare.com/workers/
- proxy-wasm (Envoy拡張): https://github.com/proxy-wasm/spec