従来のサーバーレスにおけるコールド スタートの問題

2024 年、Datadog の分析により、Lambda 呼び出しの 40% が本番環境で行われていることが文書化されました。 コールド スタートが 500 ミリ秒を超えると発生します。 Python または Java 関数の場合、この数値は 1 ~ 3 秒に増加します。 コールド スタートとは、リクエストが到着した瞬間からリクエストが到着するまでの時間です。 関数は実際にそれを処理する準備ができています。これは、関数の開始にかかる時間です。 コンテナーを作成し、ランタイムをロードし、依存関係を初期化します。

Cloudflare Workers は根本的に異なるアプローチを採用しています。コンテナの代わりに、 V8 アイソレート。測定可能な結果: 平均起動時間は以下です 1ms、 従来の用語の意味での「コールド スタート」はゼロです。なぜ降りる必要があるのかを理解する V8 エンジンのアーキテクチャとプロセス分離モデルについて詳しく説明します。

何を学ぶか

  • V8 Isolate とは何ですか? OS プロセスや Docker コンテナーとの違いは何ですか?
  • コンテナが構造的なコールド スタートに悩まされる理由とその症状がどのように現れるか
  • Cloudflare Workersの実行モデル: リクエストのルーティングからプールの分離まで
  • スナップショット V8: JavaScript の初期化コストを削減する技術
  • 分離モデルの制限: CPU 時間、メモリ、利用可能な API
  • ベンチマークの比較: ワーカー vs Lambda vs Lambda@Edge
  • ワーカーを使用する場合とコンテナが引き続き適切な選択となる場合

コンテナ、プロセス、分離株: 分類法

分離物を理解するには、まず分離物が何を置き換えているのかを理解する必要があります。すべてのレベル 抽象化の開始コストは異なります。

原生的 絶縁 典型的なスタートアップ メモリのオーバーヘッド Esempi
VM(ハイパーバイザー) ハードウェア 10~60秒 512MB~2GB EC2、GCE、Azure VM
コンテナ (Linux 名前空間) カーネル (cgroups + 名前空間) 100ミリ秒~2秒 50~500MB Docker、Lambda、Cloud Run
OSプロセス カーネル (PID、VAS) 10~100ミリ秒 10~100MB Node.js、Pythonプロセス
V8 絶縁 ランタイム (個別のヒープ、共有メモリなし) < 1ms 1~10MB Cloudflare ワーカー、Deno デプロイ

Un V8 絶縁 V8 JavaScript ヒープの分離されたインスタンスです。 独自のヒープ アロケーター、独自の JavaScript オブジェクト、独自のガベージ コレクター。 2 つの分離株 これらは JavaScript メモリを共有しないため、相互に干渉することはできません。これです 労働者の安全隔離の基礎ですが、アーキテクチャ上の結果は次のとおりです。 分離の作成はミリ秒ではなくマイクロ秒かかる操作であることを理解してください。

Lambda がコールド スタートに悩まされる理由

リクエストがコンテナのない「コールド」(ウォームではない)Lambda 関数に到着したとき 事前に割り当てられている場合)、AWS は次のシーケンスを実行する必要があります。

# Sequenza di cold start Lambda (Node.js 20)
1. Allocazione risorse compute (EC2/Firecracker VM)      ~50-200ms
2. Download immagine container dal registro              ~50-300ms (dipende dalla dimensione)
3. Setup rete: VPC, ENI attachment (se VPC configurata) ~200-1000ms (!)
4. Avvio runtime Node.js                                 ~30-80ms
5. Caricamento dependencies (node_modules)               ~20-200ms
6. Esecuzione handler initialization code               ~10-500ms
7. Prima invocazione effettiva                           ----------

Totale cold start: 360ms - 2.28 secondi (VPC worst case: fino a 3-5s)

Firecracker (AWS で使用される microVM) により、VM の起動時間が約 125 ミリ秒に短縮されました。 しかし、構造的な問題は残ります。各実行環境は分離されたコンテナです。 カーネル レベルでは、コールド スタートのたびに最初から開始する必要があります。

V8 分離: ワーカー アーキテクチャ

Cloudflareワーカーは上で実行されます 労働者によってリリースされたオープンソース ランタイム 2022 年の Cloudflare。すべての PoP (Point of Presence、世界中に 300 以上あります) が実行されます。 ワーカープロセスのフリート。リクエストが到着すると:

Sequenza di gestione richiesta in Cloudflare Workers:

1. Richiesta arriva al PoP Cloudflare piu vicino        ~0ms (BGP anycast routing)
2. Routing al processo workerd appropriato               ~0.1ms
3. Lookup/allocation dell'isolate per questo Worker      ~0.5ms (da pool precreato)
4. Esecuzione del fetch handler                          ~0.1ms overhead
5. Risposta

Totale "startup": < 1ms

重要な点はステップ 3 です。workerd は、 分離株のプール 事前に初期化された。各アイソレートはすでにワーカー コードをロードしています おかげで V8 スナップショット.

V8 スナップショット: ヒープ状態のシリアル化

V8 は、「スナップショット」と呼ばれるバイナリ形式でのヒープのシリアル化をサポートしています。いつ ワーカー、Cloudflare をデプロイします。

  1. ワーカーの JavaScript コードを一時的に分離して実行します。
  2. コードの初期化 (モジュール評価) を完了させます。
  3. 分離ヒープをバイナリ スナップショットにシリアル化します。
  4. このスナップショットを PoP に配布します
  5. 新しいアイソレートは、最初からではなく、このスナップショット (逆シリアル化) から作成されます。
// Il tuo Worker ha questa struttura:
import { Router } from 'itty-router';

// Questo codice viene eseguito durante il "module evaluation"
// e serializzato nello snapshot
const router = new Router();

router.get('/users/:id', async ({ params, env }) => {
  const user = await env.DB.get(`user:${params.id}`);
  return Response.json({ user });
});

// Il fetch handler e il punto di ingresso per ogni richiesta
export default {
  fetch: router.fetch
};

// Quando un isolate viene creato dallo snapshot:
// - router e gia costruito e configurato
// - tutte le route sono gia registrate
// - NON c'e alcun costo di module evaluation per ogni richiesta

これは Lambda で起こることとは根本的に異なります。Lambda では、 各コールド スタートでは、すべてのモジュール初期化コードを再実行する必要があります。ワーカーズでは、 このフェーズは展開時に 1 回だけ発生しました。

セキュリティの分離: V8 サンドボックス

よくある反対意見は次のとおりです。「複数のワーカーが同じプロセスで実行される場合、どのように実行するのか」 彼らは孤立しているのですか?」答えは、 V8 サンドボックス、メカニズム 多層:

V8 絶縁層

  • ヒープの分離: 各アイソレートには完全に個別のヒープがあります。 別のアイソレートのメモリには JavaScript 経由でアクセスできません。
  • 共有された可変状態はありません: ワーカーのグローバル変数は 他のワーカーにも表示されます。
  • 制御された API: 危険な機能(ファイルシステム、 network raw、process) は、V8 自体ではなく、workerd ランタイムによって制御されます。
  • サンドボックス V8: V8 には、次のようなサンドボックス メカニズムが含まれています。 任意のマシンコードを実行したり、外部メモリにアクセスしたりするための JavaScript その山。
  • 追加のプロセス分離: 労働者はできる 利用可能な Syscall をフィルタリングするために secmp-bpf を設定します。

このモデルにはリスクがないわけではありません。V8 パーサーの脆弱性により、 理論的には隔離体からの脱出を可能にします。 Cloudflareにはバグ報奨金プログラムがあります 専用で定期的に V8 を更新します。高セキュリティのワークロードに対して、Cloudflare は次のことを実現します。 さらに厳格な隔離方法。

分離モデルの制限

コールド スタートがないことにはコストがかかります。分離されたモデルはコンテナーに制限を課します。 彼らは以下のものを持っていません:

限界 Cloudflare ワーカー (無料/有料) AWSラムダ
リクエストあたりの CPU 時間 10ms (無料) / 30s (有料、一時停止あり) 15分
最大メモリ 128MB 10GB
バンドルサイズ 10MB(圧縮)/1MB(無料) 250MB(解凍後)
ダイレクトTCP 制限付き (ワーカー TCP ソケット API) フルアクセス
ファイルシステム 利用不可 /tmp (512MB)
ランタイム言語 JavaScript/TypeScript/WASM どれでも
Node.jsの互換性 部分的 (nodejs_compat フラグ) 完了

の限界 CPU時間 理解することが最も重要です: それは壁時計のタイムアウトではありません。ワーカーは実際に CPU 時間を測定します 消費された。ワーカーは、そうでない場合よりも多くの非同期 I/O 呼び出し (フェッチ、KV) を行うことができます。 CPU時間を消費します。この制限は、JavaScript コードの実行にのみ適用されます。

ベンチマーク: ワーカー vs Lambda vs Lambda@Edge

以下は、公開ベンチマークと公式ドキュメント (2025 年) に基づく比較です。

メトリック Cloudflare ワーカー AWS Lambda (Node.js) ラムダ@エッジ バーセルエッジ機能
コールドスタート P50 < 1ms ~200ms ~100ミリ秒 < 5ms
コールドスタート P99 < 5ms ~1500ミリ秒 ~500ms < 50ms
グローバルPoP 300以上 ~30 地域 ~450 (ただし制限あり) ~100+
世界平均レイテンシー ~10ms (エッジ) ~50 ~ 200 ミリ秒 (地域ごと) ~25~100ミリ秒 ~30ms
無料枠 100,000 リクエスト/日 100万リクエスト/月 100 万リクエスト/月 (共有 Lambda) 100GB-時間/月

最小限のワーカー: 実行モデルを理解する

これまでの説明を具体的にするために、最も単純なコードを次に示します。 ワーカーのライフサイクルを示します。

// worker.ts - formato ES Module (obbligatorio con isolates moderni)

// FASE 1: Module evaluation (eseguita UNA VOLTA, serializzata nello snapshot)
console.log('Questo viene eseguito solo al deploy, non per ogni richiesta');

const CONFIG = {
  version: '1.0',
  region: 'auto',
};

// FASE 2: Export del handler - il punto di ingresso per ogni richiesta
export default {
  // Chiamato per ogni HTTP request
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    // request: la richiesta HTTP
    // env: i binding (KV, R2, D1, secrets, variables)
    // ctx: context per waitUntil() - operazioni post-risposta

    const url = new URL(request.url);
    const path = url.pathname;

    // ctx.waitUntil() permette operazioni asincrone dopo la risposta
    ctx.waitUntil(logRequest(request, env));

    if (path === '/health') {
      return new Response(JSON.stringify({
        status: 'ok',
        config: CONFIG,
        timestamp: Date.now(),
      }), {
        headers: { 'Content-Type': 'application/json' },
      });
    }

    return new Response('Not Found', { status: 404 });
  },
};

async function logRequest(request: Request, env: Env): Promise<void> {
  // Questo viene eseguito dopo che la risposta e gia stata inviata
  // Non blocca il client
  await env.ANALYTICS_KV.put(
    `log:${Date.now()}`,
    JSON.stringify({
      url: request.url,
      method: request.method,
      cf: request.cf, // Informazioni Cloudflare: paese, ASN, datacenter, etc.
    })
  );
}

// Interfaccia per i binding definiti in wrangler.toml
interface Env {
  ANALYTICS_KV: KVNamespace;
  API_KEY: string; // secret
}

同時実行モデル: 1 つの分離、多数のリクエスト

微妙だが重要な側面: Lambda とは異なり、各呼び出しには ワーカー内の独自の隔離された環境 同じ分離物で処理できる 複数の競合するリクエスト。これが可能なのは、 JavaScript はイベント ループを備えたシングルスレッドなので、実際の処理はありません。 共有状態では同時実行が可能ですが、I/O バウンドのリクエストは多重化できます。

// ATTENZIONE: stato globale condiviso tra richieste
// In Lambda questo non sarebbe un problema (ogni invocazione ha il suo processo)
// In Workers, piu richieste possono condividere lo stesso isolate

// SBAGLIATO: questo contatore e condiviso tra tutte le richieste
let requestCount = 0;

export default {
  async fetch(request: Request): Promise<Response> {
    requestCount++; // Race condition! Non fare questo.
    return new Response(`Request #${requestCount}`);
  },
};

// CORRETTO: usa ctx.waitUntil() per operazioni post-risposta
// e storage esterno (KV) per contatori condivisi
export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const count = parseInt(await env.COUNTERS.get('requests') ?? '0') + 1;
    ctx.waitUntil(env.COUNTERS.put('requests', String(count)));
    return new Response(`Requests: ${count}`);
  },
};

労働者の世界的な地位: 注意

Lambda (各呼び出しが分離されている) とは異なり、Workers では状態 グローバル JavaScript は内部の同時リクエスト間で共有可能 同じように分離されました。必要なデータには常に外部ストレージ (KV、D1、R2) を使用してください。 永続化または共有されます。不変のグローバル変数 (構成、ルーター) 彼らは安全です。可変変数は危険です。

Workerd: オープンソース ランタイム

2022 年 9 月に Cloudflare がオープンソース化しました 労働者 (github.com/cloudflare/workerd)、ワーカーを強化するランタイム。彼が持っていたこれ 重要な結果:

  • デノデプロイ 同様のアーキテクチャを持つ V8 アイソレートを採用
  • ミニフレア (ローカル シミュレーター) v3 以降、内部で workerd を使用しています。
  • プライベート環境向けにワーカードをオンプレミスで実行できる
  • コミュニティはランタイムに貢献し、セキュリティ コードを検査できます
# Architettura workerd (semplificata)

┌─────────────────────────────────────────────────┐
│                  workerd process                │
│                                                 │
│  ┌──────────────┐  ┌──────────────┐            │
│  │  Isolate #1  │  │  Isolate #2  │   ...      │
│  │  (Worker A)  │  │  (Worker B)  │            │
│  │              │  │              │            │
│  │  V8 Heap A   │  │  V8 Heap B   │            │
│  │  (isolato)   │  │  (isolato)   │            │
│  └──────┬───────┘  └──────┬───────┘            │
│         │                 │                    │
│  ┌──────▼─────────────────▼───────┐            │
│  │        I/O Subsystem           │            │
│  │  (fetch, KV, R2, D1, DO, AI)  │            │
│  └───────────────────────────────┘            │
│                                                 │
│  ┌─────────────────────────────────────────┐   │
│  │        Network Layer                    │   │
│  │  (Cloudflare anycast, TLS, HTTP/3)     │   │
│  └─────────────────────────────────────────┘   │
└─────────────────────────────────────────────────┘

ワーカーを使用しない場合

V8 アイソレートがすべての答えになるわけではありません。 Lambda またはコンテナーが使用されるシナリオがあります。 正しい選択のままです:

  • CPU を大量に使用する長時間の計算: ML トレーニング、ビデオ レンダリング、 オーディオエンコーディング。 30 秒の CPU 時間と 128MB の RAM という制限は法外です。
  • 非 JavaScript コード: Workers は WebAssembly をサポートしていますが、すべてではありません 言語は WASM に適切にコンパイルされます。ネイティブ Python、Java、Ruby には Lambda が必要です。
  • 完全な Node.js エコシステム: 多くの npm ライブラリはネイティブ Node.js API を使用します (fs、高度な暗号化、バッファー操作) はワーカーでは使用できません。
  • 複雑な状態管理: WebSocketセッションが必要な場合 多くのステータスを持つ耐久性のあるオブジェクトについては、耐久性のあるオブジェクトを検討してください (シリーズの記事 4 を参照)。
  • 永続的な接続データベース: ワーカーが接続を維持しない リクエスト間の永続的な TCP。従来のデータベースへのプールには Hyperdrive を使用します。

結論と次のステップ

V8 の分離は、単なる最適化ではなく、アーキテクチャのパラダイム シフトを表しています。 分離境界をカーネル レベルからランタイム レベルに移動させるため、次のような時間が発生します。 マルチテナント エッジ コンピューティングに十分なセキュリティを備えたミリ秒未満のブート。費用 従来のコンテナよりも制限された実行環境です。

ほとんどの RESTful API、認証ミドルウェア、変換 リクエストやコンテンツのカスタマイズなど、ワーカーの制約は単なる制約ではありません。 許容範囲内であり、遅延の増加は全体的で測定可能です。

シリーズの次の記事

  • 第2条: 最初の Cloudflare ワーカー — フェッチ ハンドラー、 Wrangler and Deploy: from concept to practice, with a working worker in production.
  • 第3条: エッジ永続性 — ワーカー KV、R2、および D1 SQLite: Workers で利用可能な各ストレージ レイヤーをいつ、どのように使用するか。
  • 第4条: 永続オブジェクト — 強い一貫性のある状態 e WebSocket: エッジのステートフル アプリケーション用の最も強力なプリミティブ。