WebAssemblyランタイム比較:2026年WasmEdge vs Wasmtime vs Wasmerエッジコンピューティング選定ガイド

技术架构

WebAssemblyランタイム比較:2026年WasmEdge vs Wasmtime vs Wasmerエッジコンピューティング選定ガイド

Dockerコンテナのコールドスタート500ms、イメージサイズは数百MB——エッジコンピューティングのシナリオでは、このオーバーヘッドは致命的です。WebAssemblyランタイムのコールドスタートは1ms未満に抑えられ、モジュールサイズはわずか数MB、コンテナよりも強力なセキュリティ隔離を提供します。2026年、WASI preview2とComponent Modelは成熟し、3大ランタイム——WasmEdge、Wasmtime、Wasmer——はそれぞれ独自の強みを持っています。ランタイムの選択を間違えると、エッジデプロイは災難になります。本記事は実践的なアプローチで、正しい選択をサポートします。

指標 Dockerコンテナ WebAssembly
コールドスタート時間 300-800ms 0.1-5ms
イメージ/モジュールサイズ 100MB-2GB 1-20MB
メモリオーバーヘッド 50MB+ 5-30MB
セキュリティ隔離 Namespace/Cgroup サンドボックス線形メモリ
クロスプラットフォーム 同一アーキテクチャ必要 一度コンパイル、どこでも実行

コア概念

概念 フルネーム 説明
WASI WebAssembly System Interface WasmがOSにアクセスするための標準インターフェース。preview1はFDベース、preview2はComponent Modelベース
Component Model WebAssembly Component Model 異なる言語でコンパイルされたWasmモジュールが相互に呼び出し可能にする仕様、言語のサイロを打破
WIT WebAssembly Interface Types Componentインターフェースを記述するIDL言語、モジュール間の型契約を定義
Edge Computing エッジコンピューティング データソースに近いネットワークエッジで計算を実行、低遅延・小規模・高速起動が要求
Cold Start コールドスタート ゼロからモジュールをロード・実行する時間。Serverlessとエッジシナリオのコア指標
Sandbox サンドボックス Wasmの線形メモリモデルが自然に隔離を提供、モジュールはホストメモリに境界外アクセス不可

問題分析:エッジコンピューティングにWasmランタイムが必要な理由

1. コールドスタート遅延が許容不可

Serverless関数はエッジノード上で、リクエストごとにコールドスタートがトリガーされる可能性があります。Dockerコンテナは最低300msで起動し、アプリケーション初期化を加えるとP99レイテンシは簡単に1秒を超えます。Wasmモジュールは1ms未満でコールドスタート——これがエッジシナリオに求められる応答速度です。

2. イメージサイズが帯域とストレージを消費

エッジノードは帯域が限られ、ストレージが高価です。Python Dockerイメージは簡単に500MBに達し、世界中の100のエッジノードにデプロイすると50GBのトラフィックになります。Wasmモジュールはわずか数MBで、デプロイコストを2桁削減します。

3. クロスアーキテクチャデプロイの悪夢

エッジデバイスのアーキテクチャは多種多様:x86_64、ARM64、RISC-V。Dockerは各アーキテクチャごとにイメージをビルドする必要があります。Wasmは一度コンパイルすれば、すべてのアーキテクチャのランタイムで実行可能です。

4. 厳格なセキュリティ隔離要件

エッジノードは信頼できない環境にデプロイされることが多いです。コンテナエスケープ脆弱性が頻発(CVE-2024-21626など)する中、Wasmの線形メモリモデルは根本的に境界外アクセスを防ぎます。

5. 多言語コンポーネント連携の困難

エッジサービスにはC++のパフォーマンス、Rustの安全性、Goの並行性が必要です。Dockerで複数プロセスを動かすとIPCオーバーヘッドが大きいですが、Component Modelにより異なる言語でコンパイルされたWasmモジュール間のゼロオーバーヘッド呼び出しが可能になります。


ステップバイステップパターン

パターン1:WasmEdgeデプロイ with Rust + WASI

# WasmEdgeのインストール
curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash -s -- -v 0.14.0

# Rustプロジェクトの作成
cargo new wasmedge-edge-service
cd wasmedge-edge-service
rustup target add wasm32-wasip1
[package]
name = "wasmedge-edge-service"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"

[profile.release]
opt-level = 3
lto = true
codegen-units = 1
strip = true
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
pub struct EdgeRequest {
    pub action: String,
    pub payload: serde_json::Value,
}

#[derive(Serialize, Deserialize)]
pub struct EdgeResponse {
    pub status: String,
    pub data: serde_json::Value,
    pub latency_us: u64,
    pub runtime: String,
}

#[no_mangle]
pub extern "C" fn handle_request(input_ptr: *const u8, input_len: usize) -> *const u8 {
    let input_bytes = unsafe { std::slice::from_raw_parts(input_ptr, input_len) };
    let request: EdgeRequest = match serde_json::from_slice(input_bytes) {
        Ok(r) => r,
        Err(e) => {
            let err = format!("{{\"error\":\"{}\"}}", e);
            let boxed = err.into_bytes().into_boxed_slice();
            return Box::leak(boxed).as_ptr();
        }
    };

    let start = std::time::Instant::now();

    let data = match request.action.as_str() {
        "transform" => transform_payload(&request.payload),
        "validate" => validate_payload(&request.payload),
        "aggregate" => aggregate_payload(&request.payload),
        _ => serde_json::json!({"error": "unknown action"}),
    };

    let latency_us = start.elapsed().as_micros() as u64;

    let response = EdgeResponse {
        status: "ok".to_string(),
        data,
        latency_us,
        runtime: "wasmedge-0.14.0".to_string(),
    };

    let output = serde_json::to_vec(&response).unwrap();
    let boxed = output.into_boxed_slice();
    Box::leak(boxed).as_ptr()
}

fn transform_payload(payload: &serde_json::Value) -> serde_json::Value {
    if let Some(obj) = payload.as_object() {
        let mut result = serde_json::Map::new();
        for (k, v) in obj {
            result.insert(k.to_uppercase(), v.clone());
        }
        serde_json::Value::Object(result)
    } else {
        payload.clone()
    }
}

fn validate_payload(payload: &serde_json::Value) -> serde_json::Value {
    let is_valid = payload.is_object() && payload.as_object().map_or(false, |o| !o.is_empty());
    serde_json::json!({"valid": is_valid, "type": match payload {
        serde_json::Value::Null => "null",
        serde_json::Value::Bool(_) => "bool",
        serde_json::Value::Number(_) => "number",
        serde_json::Value::String(_) => "string",
        serde_json::Value::Array(_) => "array",
        serde_json::Value::Object(_) => "object",
    }})
}

fn aggregate_payload(payload: &serde_json::Value) -> serde_json::Value {
    if let Some(arr) = payload.as_array() {
        let nums: Vec<f64> = arr.iter()
            .filter_map(|v| v.as_f64())
            .collect();
        if nums.is_empty() {
            return serde_json::json!({"count": 0});
        }
        serde_json::json!({
            "count": nums.len(),
            "sum": nums.iter().sum::<f64>(),
            "avg": nums.iter().sum::<f64>() / nums.len() as f64,
            "min": nums.iter().cloned().fold(f64::INFINITY, f64::min),
            "max": nums.iter().cloned().fold(f64::NEG_INFINITY, f64::max),
        })
    } else {
        serde_json::json!({"error": "payload must be array"})
    }
}
# Wasmにコンパイル
cargo build --target wasm32-wasip1 --release

# AOTコンパイルでパフォーマンス向上
wasmedgec target/wasm32-wasip1/release/wasmedge_edge_service.wasm wasmedge_edge_service_aot.wasm

# 実行
echo '{"action":"transform","payload":{"name":"edge","version":2}}' | \
  wasmedge --dir .:. wasmedge_edge_service_aot.wasm handle_request

# ベンチマーク
wasmedge --dir .:. wasmedge_edge_service_aot.wasm handle_request < benchmark_input.json

パターン2:Wasmtime Serverless関数 + WASI preview2

# Wasmtimeのインストール
curl https://wasmtime.dev/install.sh -sSf | bash
wasmtime --version

# Rustプロジェクトの作成(WASI preview2)
cargo new wasmtime-serverless
cd wasmtime-serverless
rustup target add wasm32-wasip2
[package]
name = "wasmtime-serverless"
version = "0.1.0"
edition = "2021"

[dependencies]
wit-bindgen = "0.30"
serde = { version = "1", features = ["derive"] }
serde_json = "1"

[profile.release]
opt-level = 3
lto = true
codegen-units = 1
strip = true
// wit/server.wit
package toolsku:server;

interface handler {
    resource request {
        constructor(body: string);
        body: func() -> string;
        respond: func(status: u16, body: string) -> result;
    }
}

world server {
    import handler;
    export run: func() -> result;
}
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
struct FunctionInput {
    event_type: String,
    data: serde_json::Value,
    timestamp: u64,
}

#[derive(Serialize, Deserialize)]
struct FunctionOutput {
    result: String,
    processed_at: u64,
    memory_used_kb: u32,
    execution_time_us: u64,
}

fn main() {
    let mut input_str = String::new();
    std::io::stdin().read_line(&mut input_str).unwrap();
    let input: FunctionInput = serde_json::from_str(input_str.trim()).unwrap_or(FunctionInput {
        event_type: "unknown".to_string(),
        data: serde_json::Value::Null,
        timestamp: 0,
    });

    let start = std::time::Instant::now();

    let result = match input.event_type.as_str() {
        "http-request" => process_http(&input.data),
        "cron-trigger" => process_cron(&input.data),
        "event-bridge" => process_event(&input.data),
        _ => format!("unsupported event: {}", input.event_type),
    };

    let execution_time_us = start.elapsed().as_micros() as u64;

    let output = FunctionOutput {
        result,
        processed_at: input.timestamp,
        memory_used_kb: 1024,
        execution_time_us,
    };

    println!("{}", serde_json::to_string(&output).unwrap());
}

fn process_http(data: &serde_json::Value) -> String {
    let path = data.get("path").and_then(|v| v.as_str()).unwrap_or("/");
    let method = data.get("method").and_then(|v| v.as_str()).unwrap_or("GET");
    format!("HTTP {} {} -> 200 OK", method, path)
}

fn process_cron(data: &serde_json::Value) -> String {
    let task = data.get("task").and_then(|v| v.as_str()).unwrap_or("cleanup");
    format!("Cron task '{}' executed successfully", task)
}

fn process_event(data: &serde_json::Value) -> String {
    let source = data.get("source").and_then(|v| v.as_str()).unwrap_or("unknown");
    let event_id = data.get("id").and_then(|v| v.as_str()).unwrap_or("n/a");
    format!("Event from '{}': {}", source, event_id)
}
# WASI preview2ターゲットにコンパイル
cargo build --target wasm32-wasip2 --release

# Wasmtimeで実行
echo '{"event_type":"http-request","data":{"path":"/api/v1/status","method":"GET"},"timestamp":1718438400}' | \
  wasmtime run --wasi preview2 target/wasm32-wasip2/release/wasmtime_serverless.wasm

# Wasmtimeパフォーマンスチューニング
wasmtime run \
  --wasi preview2 \
  --opt-level 2 \
  --cranelift \
  --max-wasm-stack 1048576 \
  target/wasm32-wasip2/release/wasmtime_serverless.wasm

パターン3:Wasmerコンテナ化Wasmサービス

# Wasmerのインストール
curl https://get.wasmer.io -sSfL | sh
wasmer --version

# Rustプロジェクトの作成
cargo new wasmer-container-service
cd wasmer-container-service
rustup target add wasm32-wasip1
[package]
name = "wasmer-container-service"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"

[profile.release]
opt-level = 3
lto = true
codegen-units = 1
strip = true
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
struct ContainerRequest {
    service: String,
    operation: String,
    params: serde_json::Value,
}

#[derive(Serialize, Deserialize)]
struct ContainerResponse {
    success: bool,
    result: serde_json::Value,
    runtime: String,
    memory_peak_mb: f64,
}

#[no_mangle]
pub extern "C" fn process(input_ptr: *const u8, input_len: usize) -> *const u8 {
    let input_bytes = unsafe { std::slice::from_raw_parts(input_ptr, input_len) };
    let request: ContainerRequest = match serde_json::from_slice(input_bytes) {
        Ok(r) => r,
        Err(e) => {
            let err = format!("{{\"success\":false,\"error\":\"{}\"}}", e);
            let boxed = err.into_bytes().into_boxed_slice();
            return Box::leak(boxed).as_ptr();
        }
    };

    let result = execute_service(&request);

    let response = ContainerResponse {
        success: true,
        result,
        runtime: "wasmer-4.3".to_string(),
        memory_peak_mb: 12.5,
    };

    let output = serde_json::to_vec(&response).unwrap();
    let boxed = output.into_boxed_slice();
    Box::leak(boxed).as_ptr()
}

fn execute_service(req: &ContainerRequest) -> serde_json::Value {
    match req.service.as_str() {
        "image-resize" => resize_image(&req.params),
        "json-transform" => transform_json(&req.params),
        "text-process" => process_text(&req.params),
        _ => serde_json::json!({"error": format!("unknown service: {}", req.service)}),
    }
}

fn resize_image(params: &serde_json::Value) -> serde_json::Value {
    let width = params.get("width").and_then(|v| v.as_u64()).unwrap_or(800);
    let height = params.get("height").and_then(|v| v.as_u64()).unwrap_or(600);
    serde_json::json!({
        "output_size": format!("{}x{}", width, height),
        "format": "webp",
        "quality": 85
    })
}

fn transform_json(params: &serde_json::Value) -> serde_json::Value {
    let input = params.get("input").cloned().unwrap_or(serde_json::Value::Null);
    let operation = params.get("op").and_then(|v| v.as_str()).unwrap_or("identity");
    match operation {
        "flatten" => flatten_json(&input),
        "keys" => extract_keys(&input),
        _ => input,
    }
}

fn flatten_json(val: &serde_json::Value) -> serde_json::Value {
    if let Some(obj) = val.as_object() {
        let mut flat = serde_json::Map::new();
        flatten_recursive("", obj, &mut flat);
        serde_json::Value::Object(flat)
    } else {
        val.clone()
    }
}

fn flatten_recursive(prefix: &str, obj: &serde_json::Map<String, serde_json::Value>, flat: &mut serde_json::Map<String, serde_json::Value>) {
    for (k, v) in obj {
        let key = if prefix.is_empty() { k.clone() } else { format!("{}.{}", prefix, k) };
        if let Some(nested) = v.as_object() {
            flatten_recursive(&key, nested, flat);
        } else {
            flat.insert(key, v.clone());
        }
    }
}

fn extract_keys(val: &serde_json::Value) -> serde_json::Value {
    if let Some(obj) = val.as_object() {
        serde_json::json!(obj.keys().collect::<Vec<_>>())
    } else {
        serde_json::json!([])
    }
}

fn process_text(params: &serde_json::Value) -> serde_json::Value {
    let text = params.get("text").and_then(|v| v.as_str()).unwrap_or("");
    let words: Vec<&str> = text.split_whitespace().collect();
    let chars: usize = text.chars().count();
    serde_json::json!({
        "word_count": words.len(),
        "char_count": chars,
        "line_count": text.lines().count(),
        "reversed": text.chars().rev().collect::<String>()
    })
}
# コンパイル
cargo build --target wasm32-wasip1 --release

# Wasmerで実行(異なるバックエンド)
wasmer run --backend cranelift target/wasm32-wasip1/release/wasmer_container_service.wasm

# Wasmerコンテナ化デプロイ
wasmer deploy \
  --name edge-container-service \
  --module target/wasm32-wasip1/release/wasmer_container_service.wasm \
  --env PRODUCTION=true

# Wasmer WCGIモード(CGIプロトコル互換)
wasmer run --backend cranelift --net host \
  target/wasm32-wasip1/release/wasmer_container_service.wasm

パターン4:Component Modelコンポジション + WIT

# wasm-toolsとwit-depsのインストール
cargo install wasm-tools wasm-component-ld

# コンポーネントプロジェクト構造の作成
mkdir -p components/{calculator,formatter,validator}/src
mkdir -p wit
// wit/calculator.wit
package toolsku:calculator;

interface ops {
    add: func(a: f64, b: f64) -> f64;
    subtract: func(a: f64, b: f64) -> f64;
    multiply: func(a: f64, b: f64) -> f64;
    divide: func(a: f64, b: f64) -> result<f64, string>;
}

world calculator {
    export ops;
}
// wit/formatter.wit
package toolsku:formatter;

interface format {
    json-pretty: func(input: string) -> result<string, string>;
    csv-to-json: func(csv: string) -> result<string, string>;
}

world formatter {
    export format;
    import toolsku:calculator/ops;
}
// components/calculator/src/lib.rs
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
struct CalcRequest {
    op: String,
    a: f64,
    b: f64,
}

#[derive(Serialize, Deserialize)]
struct CalcResult {
    value: f64,
    operation: String,
}

#[no_mangle]
pub extern "C" fn add(a: f64, b: f64) -> f64 {
    a + b
}

#[no_mangle]
pub extern "C" fn subtract(a: f64, b: f64) -> f64 {
    a - b
}

#[no_mangle]
pub extern "C" fn multiply(a: f64, b: f64) -> f64 {
    a * b
}

#[no_mangle]
pub extern "C" fn divide(a: f64, b: f64) -> u64 {
    if b == 0.0 {
        let err = format!("{{\"error\":\"division by zero\"}}");
        let boxed = err.into_bytes().into_boxed_slice();
        return Box::leak(boxed).as_ptr() as u64;
    }
    let result = a / b;
    let boxed = result.to_le_bytes().to_vec().into_boxed_slice();
    Box::leak(boxed).as_ptr() as u64
}
// components/formatter/src/lib.rs
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
struct FormatRequest {
    format_type: String,
    input: String,
}

fn json_pretty(input: &str) -> Result<String, String> {
    let val: serde_json::Value = serde_json::from_str(input)
        .map_err(|e| format!("JSON parse error: {}", e))?;
    serde_json::to_string_pretty(&val)
        .map_err(|e| format!("JSON format error: {}", e))
}

fn csv_to_json(csv: &str) -> Result<String, String> {
    let lines: Vec<&str> = csv.lines().collect();
    if lines.is_empty() {
        return Err("empty CSV".to_string());
    }
    let headers: Vec<&str> = lines[0].split(',').collect();
    let mut records = Vec::new();
    for line in &lines[1..] {
        let values: Vec<&str> = line.split(',').collect();
        let mut record = serde_json::Map::new();
        for (i, header) in headers.iter().enumerate() {
            let val = values.get(i).unwrap_or(&"");
            if let Ok(n) = val.parse::<f64>() {
                record.insert(header.to_string(), serde_json::json!(n));
            } else {
                record.insert(header.to_string(), serde_json::json!(val));
            }
        }
        records.push(serde_json::Value::Object(record));
    }
    serde_json::to_string_pretty(&records)
        .map_err(|e| format!("JSON serialize error: {}", e))
}

fn main() {
    let mut input = String::new();
    std::io::stdin().read_line(&mut input).unwrap();
    let req: FormatRequest = serde_json::from_str(input.trim()).unwrap();

    let result = match req.format_type.as_str() {
        "json-pretty" => json_pretty(&req.input),
        "csv-to-json" => csv_to_json(&req.input),
        _ => Err(format!("unknown format: {}", req.format_type)),
    };

    match result {
        Ok(formatted) => println!("{}", formatted),
        Err(e) => eprintln!("Error: {}", e),
    }
}
# 各コンポーネントのコンパイル
cd components/calculator
cargo build --target wasm32-wasip1 --release

cd ../formatter
cargo build --target wasm32-wasip1 --release

# wasm-toolsでComponentを作成
wasm-tools component new \
  target/wasm32-wasip1/release/calculator.wasm \
  -o calculator.component.wasm

wasm-tools component new \
  target/wasm32-wasip1/release/formatter.wasm \
  -o formatter.component.wasm

# コンポーネントのコンポジション
wasm-tools compose \
  --component formatter.component.wasm \
  --dependency calculator:calculator.component.wasm \
  -o composed.component.wasm

# Wasmtimeでコンポジットコンポーネントを実行
wasmtime run --wasi preview2 composed.component.wasm

パターン5:WasmEdgeエッジ関数デプロイ

# エッジ関数プロジェクトの作成
cargo new edge-function
cd edge-function
rustup target add wasm32-wasip1
[package]
name = "edge-function"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"

[profile.release]
opt-level = 3
lto = true
codegen-units = 1
strip = true
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
struct EdgeFunctionEvent {
    request_id: String,
    source_ip: String,
    path: String,
    method: String,
    headers: std::collections::HashMap<String, String>,
    body: Option<serde_json::Value>,
    query_params: std::collections::HashMap<String, String>,
}

#[derive(Serialize, Deserialize)]
struct EdgeFunctionResponse {
    status_code: u16,
    headers: std::collections::HashMap<String, String>,
    body: serde_json::Value,
    cache_control: String,
    edge_cache_ttl: u32,
}

fn main() {
    let mut input = String::new();
    std::io::stdin().read_line(&mut input).unwrap();
    let event: EdgeFunctionEvent = match serde_json::from_str(input.trim()) {
        Ok(e) => e,
        Err(_) => {
            let err_resp = EdgeFunctionResponse {
                status_code: 400,
                headers: std::collections::HashMap::new(),
                body: serde_json::json!({"error": "invalid event"}),
                cache_control: "no-cache".to_string(),
                edge_cache_ttl: 0,
            };
            println!("{}", serde_json::to_string(&err_resp).unwrap());
            return;
        }
    };

    let response = route_edge_function(&event);
    println!("{}", serde_json::to_string(&response).unwrap());
}

fn route_edge_function(event: &EdgeFunctionEvent) -> EdgeFunctionResponse {
    match event.path.as_str() {
        "/api/geo" => geo_redirect(event),
        "/api/cache" => cache_handler(event),
        "/api/auth" => auth_check(event),
        "/api/compress" => compress_response(event),
        _ => EdgeFunctionResponse {
            status_code: 404,
            headers: std::collections::HashMap::new(),
            body: serde_json::json!({"error": "not found"}),
            cache_control: "no-cache".to_string(),
            edge_cache_ttl: 0,
        },
    }
}

fn geo_redirect(event: &EdgeFunctionEvent) -> EdgeFunctionResponse {
    let region = event.headers.get("x-edge-region")
        .cloned()
        .unwrap_or_else(|| "default".to_string());

    let redirect_url = match region.as_str() {
        "cn-north" => "https://cn-north.example.com",
        "cn-south" => "https://cn-south.example.com",
        "us-west" => "https://us-west.example.com",
        "eu-central" => "https://eu-central.example.com",
        _ => "https://global.example.com",
    };

    let mut headers = std::collections::HashMap::new();
    headers.insert("Location".to_string(), redirect_url.to_string());

    EdgeFunctionResponse {
        status_code: 302,
        headers,
        body: serde_json::json!({"redirect": redirect_url, "region": region}),
        cache_control: "no-cache".to_string(),
        edge_cache_ttl: 0,
    }
}

fn cache_handler(event: &EdgeFunctionEvent) -> EdgeFunctionResponse {
    let max_age: u32 = event.query_params.get("max_age")
        .and_then(|v| v.parse().ok())
        .unwrap_or(3600);

    let mut headers = std::collections::HashMap::new();
    headers.insert("X-Edge-Cache".to_string(), "HIT".to_string());

    EdgeFunctionResponse {
        status_code: 200,
        headers,
        body: serde_json::json!({
            "cached": true,
            "ttl": max_age,
            "variant": event.query_params.get("v").cloned().unwrap_or("1".to_string())
        }),
        cache_control: format!("public, max-age={}", max_age),
        edge_cache_ttl: max_age,
    }
}

fn auth_check(event: &EdgeFunctionEvent) -> EdgeFunctionResponse {
    let token = event.headers.get("authorization")
        .cloned()
        .unwrap_or_default();

    let is_valid = token.starts_with("Bearer ") && token.len() > 20;

    if is_valid {
        EdgeFunctionResponse {
            status_code: 200,
            headers: std::collections::HashMap::new(),
            body: serde_json::json!({"authenticated": true, "token_prefix": &token[..15]}),
            cache_control: "private, no-cache".to_string(),
            edge_cache_ttl: 0,
        }
    } else {
        EdgeFunctionResponse {
            status_code: 401,
            headers: std::collections::HashMap::new(),
            body: serde_json::json!({"error": "unauthorized", "authenticated": false}),
            cache_control: "no-cache".to_string(),
            edge_cache_ttl: 0,
        }
    }
}

fn compress_response(event: &EdgeFunctionEvent) -> EdgeFunctionResponse {
    let accept_encoding = event.headers.get("accept-encoding")
        .cloned()
        .unwrap_or_default();

    let encoding = if accept_encoding.contains("br") {
        "br"
    } else if accept_encoding.contains("gzip") {
        "gzip"
    } else {
        "identity"
    };

    let mut headers = std::collections::HashMap::new();
    headers.insert("Content-Encoding".to_string(), encoding.to_string());

    EdgeFunctionResponse {
        status_code: 200,
        headers,
        body: serde_json::json!({
            "original_size": 10240,
            "compressed_size": match encoding {
                "br" => 2048,
                "gzip" => 3072,
                _ => 10240,
            },
            "encoding": encoding,
            "ratio": match encoding {
                "br" => "80%",
                "gzip" => "70%",
                _ => "0%",
            }
        }),
        cache_control: "public, max-age=86400".to_string(),
        edge_cache_ttl: 86400,
    }
}
# コンパイル
cargo build --target wasm32-wasip1 --release

# AOTコンパイル
wasmedgec target/wasm32-wasip1/release/edge_function.wasm edge_function_aot.wasm

# エッジイベントのシミュレーション実行
echo '{"request_id":"req-001","source_ip":"10.0.0.1","path":"/api/geo","method":"GET","headers":{"x-edge-region":"cn-north"},"body":null,"query_params":{}}' | \
  wasmedge --dir .:. edge_function_aot.wasm

# Kubernetesエッジデプロイ
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wasm-edge-function
  namespace: edge
spec:
  replicas: 5
  selector:
    matchLabels:
      app: wasm-edge-fn
  template:
    metadata:
      labels:
        app: wasm-edge-fn
    spec:
      containers:
      - name: wasmedge
        image: wasmedge/wasmedge:0.14.0
        command: ["wasmedge", "--dir", "/app:/app", "/app/edge_function_aot.wasm"]
        resources:
          limits:
            cpu: "200m"
            memory: "64Mi"
          requests:
            cpu: "50m"
            memory: "32Mi"
        volumeMounts:
        - name: wasm-module
          mountPath: /app
      volumes:
      - name: wasm-module
        configMap:
          name: edge-function-wasm
EOF

パターン6:プロダクションランタイム選定フレームワーク

struct RuntimeSelectionCriteria {
    scenario: String,
    cold_start_requirement_ms: u64,
    module_size_limit_mb: u64,
    target_architectures: Vec<String>,
    wasi_version: String,
    needs_component_model: bool,
    needs_networking: bool,
    needs_ai_inference: bool,
    team_rust_expertise: bool,
    kubernetes_integration: bool,
}

fn select_runtime(criteria: &RuntimeSelectionCriteria) -> &'static str {
    if criteria.needs_ai_inference && criteria.cold_start_requirement_ms < 5 {
        return "WasmEdge - WASI-NN + AOTコンパイル、AI推論シナリオに最適";
    }

    if criteria.needs_component_model && criteria.wasi_version == "preview2" {
        return "Wasmtime - Component Modelファーストクラスサポート、preview2ネイティブ実装";
    }

    if criteria.needs_networking && criteria.target_architectures.len() > 3 {
        return "Wasmer - マルチバックエンドサポート、成熟したネットワークプラグイン、クロスアーキテクチャ最柔軟";
    }

    if criteria.kubernetes_integration && criteria.cold_start_requirement_ms < 10 {
        return "WasmEdge - Krustlet統合が成熟、K8sエコシステム最充実";
    }

    if criteria.team_rust_expertise && criteria.needs_component_model {
        return "Wasmtime - Bytecode Alliance主導、Rustエコシステム最密接";
    }

    "WasmEdge - エッジコンピューティング総合最適、コミュニティ活発、ドキュメント充実"
}

fn main() {
    let scenarios = vec![
        RuntimeSelectionCriteria {
            scenario: "エッジAI推論".to_string(),
            cold_start_requirement_ms: 2,
            module_size_limit_mb: 10,
            target_architectures: vec!["x86_64".into(), "aarch64".into()],
            wasi_version: "preview1".to_string(),
            needs_component_model: false,
            needs_networking: false,
            needs_ai_inference: true,
            team_rust_expertise: true,
            kubernetes_integration: false,
        },
        RuntimeSelectionCriteria {
            scenario: "Serverless関数プラットフォーム".to_string(),
            cold_start_requirement_ms: 5,
            module_size_limit_mb: 20,
            target_architectures: vec!["x86_64".into()],
            wasi_version: "preview2".to_string(),
            needs_component_model: true,
            needs_networking: true,
            needs_ai_inference: false,
            team_rust_expertise: true,
            kubernetes_integration: false,
        },
        RuntimeSelectionCriteria {
            scenario: "多言語プラグインシステム".to_string(),
            cold_start_requirement_ms: 50,
            module_size_limit_mb: 50,
            target_architectures: vec!["x86_64".into(), "aarch64".into(), "riscv64".into()],
            wasi_version: "preview1".to_string(),
            needs_component_model: true,
            needs_networking: true,
            needs_ai_inference: false,
            team_rust_expertise: false,
            kubernetes_integration: false,
        },
    ];

    for s in &scenarios {
        println!("[{}] -> {}", s.scenario, select_runtime(s));
    }
}

落とし穴ガイド

❌ 落とし穴1:wasm32-unknown-unknownでWASIモジュールをコンパイル

// ❌ 誤り:WASIサポートなし、ファイルシステムやネットワークにアクセス不可
// cargo build --target wasm32-unknown-unknown

// ✅ 正しい:WASIターゲットでコンパイル
// cargo build --target wasm32-wasip1   // WASI preview1
// cargo build --target wasm32-wasip2   // WASI preview2

❌ 落とし穴2:AOTコンパイル後のクロスプラットフォームデプロイ

# ❌ 誤り:x86でAOTコンパイル、ARMデバイスにコピーして実行
wasmedgec module.wasm module_aot.wasm
scp module_aot.wasm arm-device:/app/

# ✅ 正しい:ターゲットプラットフォームでAOTコンパイルを実行
ssh arm-device "wasmedgec /app/module.wasm /app/module_aot.wasm"

❌ 落とし穴3:WASIバージョン差異の無視

# ❌ 誤り:preview1モジュールをpreview2ランタイムで実行
wasmtime run --wasi preview2 module_wasip1.wasm

# ✅ 正しい:WASIバージョンを一致させる
wasmtime run --wasi preview1 module_wasip1.wasm
wasmtime run --wasi preview2 module_wasip2.wasm

❌ 落とし穴4:Wasmモジュールメモリ上限の未設定

# ❌ 誤り:メモリ制限なし、エッジデバイスがOOMの可能性
wasmedge module.wasm

# ✅ 正しい:メモリページ上限を設定(各ページ64KB)
wasmedge --memory-page-limit 512 module.wasm  # 32MB上限

❌ 落とし穴5:Component Modelインターフェースの非互換

// ❌ 誤り:2つのコンポーネントのWITインターフェースバージョンが不一致
// コンポーネントA: export calc: func(x: f64) -> f64
// コンポーネントB: import calc: func(x: i32) -> i32

// ✅ 正しい:共有WIT定義で型の一貫性を確保
// wit/shared.wit
// export calc: func(x: f64) -> f64

エラートラブルシューティング

エラーメッセージ 原因 解決方法
error: target not found: wasm32-wasip1 Rust targetがインストールされていない rustup target add wasm32-wasip1
WasmEdge: module load failed Wasmファイルが破損または形式エラー 再コンパイル、cargo buildの出力を確認
wasmtime: incompatible WASI version WASI previewバージョンの不一致 --wasi preview1またはpreview2でコンパイルターゲットに合わせる
Wasmer: backend not available コンパイルバックエンドが未インストール wasmer-cli-craneliftまたはwasmer-cli-llvmをインストール
out of memory: wasm trap Wasm線形メモリが上限超過 --memory-page-limitで増やすか、メモリ使用量を最適化
cannot import wasi_snapshot_preview1 WASI APIが欠落 wasm32-unknown-unknownの代わりにwasm32-wasip1を使用
component composition failed: type mismatch WITインターフェース型の非互換 コンポーネントのWIT定義を確認、インポート/エクスポート型の一致を確保
AOT compilation failed: unsupported opcode AOTコンパイラが特定のWasm命令をサポートしていない WasmEdgeを最新版に更新、またはインタプリタモードを使用
SIGILL: illegal instruction AOTコンパイルのCPU機能が不一致 ターゲットデバイスでAOTコンパイルを再実行
permission denied: /app/data WASIファイルシステム権限不足 wasmedge --dir /app:/appでディレクトリをマウント

高度な最適化

1. コールドスタートの極限最適化

# WasmEdge AOT + プリロード
wasmedge --dir /app:/app \
  --preload module:edge_function_aot.wasm \
  --pool-size 10 \
  edge_function_aot.wasm

# Wasmtime Craneliftウォームアップ
wasmtime compile --cranelift --opt-level speed \
  target/wasm32-wasip2/release/module.wasm

# Wasmer LLVMバックエンド(起動遅いが実行高速)
wasmer compile --llvm target/wasm32-wasip1/release/module.wasm \
  -o module_native.so
最適化手法 コールドスタート 実行パフォーマンス ユースケース
インタプリタ 0.1ms ベースライン 開発・デバッグ
Cranelift JIT 3ms 1.5x Serverless関数
AOTプリコンパイル 0.5ms 2-3x エッジプロダクション
LLVMバックエンド 50ms 3-5x 計算集約型

2. モジュールサイズ圧縮

[profile.release]
opt-level = "z"
lto = true
codegen-units = 1
strip = true
panic = "abort"
# wasm-optでさらに圧縮
wasm-opt -Oz -o module_opt.wasm module.wasm

# サイズ比較
# オリジナル: 2.8MB
# LTO + strip: 1.2MB
# wasm-opt -Oz: 890KB
# gzip: 320KB

3. マルチランタイムハイブリッドデプロイ

apiVersion: v1
kind: ConfigMap
metadata:
  name: runtime-router-config
data:
  router.yaml: |
    routes:
      - path: /api/ai/*
        runtime: wasmedge
        module: /wasm/ai-inference.wasm
        pool_size: 5
        memory_limit_mb: 128

      - path: /api/compose/*
        runtime: wasmtime
        module: /wasm/composed.component.wasm
        wasi: preview2
        pool_size: 3

      - path: /api/process/*
        runtime: wasmer
        module: /wasm/data-processor.wasm
        backend: cranelift
        pool_size: 10
        memory_limit_mb: 64

      - path: /api/static/*
        runtime: wasmedge
        module: /wasm/cache-handler.wasm
        pool_size: 20
        memory_limit_mb: 32

比較分析

次元 WasmEdge Wasmtime Wasmer Dockerコンテナ
コールドスタート <1ms (AOT) 3-5ms (Cranelift) 5-10ms (Cranelift) 300-800ms
実行パフォーマンス ★★★★★ (AOT) ★★★★ (Cranelift) ★★★★ (Cranelift) ★★★★★ (ネイティブ)
モジュール/イメージサイズ 1-10MB 2-15MB 2-15MB 100MB-2GB
WASI preview1 N/A
WASI preview2 ✅ (0.14+) ✅ (ネイティブ) ⚠️ (実験的) N/A
Component Model ⚠️ (実験的) ✅ (ネイティブ) ⚠️ (実験的) N/A
WASI-NN (AI推論) ✅ (ネイティブ) 追加インストール必要
ネットワーク (WASI-HTTP) ✅ (preview2) ⚠️ N/A
K8s統合 ★★★★★ (Krustlet) ★★★★ ★★★ ★★★★★
マルチバックエンド WasmEdge AOT Cranelift/Winch Cranelift/LLVM/Singlepass N/A
クロスアーキテクチャ x86/ARM/RISC-V x86/ARM x86/ARM/RISC-V 個別ビルド必要
セキュリティサンドボックス ★★★★★ ★★★★★ ★★★★★ ★★★
コミュニティ活発度 ★★★★ ★★★★★ ★★★ ★★★★★
プロダクション事例 Fastly/CosmWasm Fastly/Shopify EdgeBit 無数
最適な用途 エッジAI/K8s/組み込み Serverless/Component 汎用/マルチバックエンド 従来サービス

まとめ:2026年、WebAssemblyランタイムは実験段階からプロダクション対応へと移行しました。WasmEdgeはエッジAI推論とK8s統合で優位性があり、WASI-NNとAOTコンパイルによりエッジコンピューティングの最適解です。WasmtimeはComponent ModelとWASI preview2の最良の実装であり、Serverless関数プラットフォームや多言語コンポーネントシステムに最適です。Wasmerのマルチバックエンドアーキテクチャは、パフォーマンスと起動時間のトレードオフが必要な汎用シナリオに最も柔軟な選択肢を提供します。選択とは「最高」を選ぶことではなく、「最適」を選ぶこと——コールドスタート要件、WASIバージョンニーズ、AI推論要件に基づいて意思決定を行ってください。


オンラインツール推奨

ブラウザローカルツールを無料で試す →

#WebAssembly#WASI#WasmEdge#Wasmtime#边缘计算#2026#Wasm运行时