章と記事の一覧
動かす基礎
Workers の実行モデル
isolate、状態を持たない、制限、課金
従来のサーバー開発の感覚で Worker を書き始めると、真っ先に裏切られるのがグローバル変数の寿命である。リクエスト間で共有できるメモリはどこにあるのか。答えは、どこにもない。
Worker の本質は、イベントが届くたびに呼び出される関数である。HTTP リクエストの場合は fetch(request, env, ctx) が受け取ったリクエストを処理し、返した Response オブジェクトがそのまま世界中のクライアントへ送り出される。
支配する 4 つの制約
- 状態はリクエストをまたいで存続しない。 グローバル変数に書き込んだ値は、同じ isolate が偶然再利用されている間だけ生き残る。次のリクエストが別の大陸のデータセンターへ飛べば、そこにあるのはまっさらなメモリである。永続化したいデータは、必ず KV、D1、R2、あるいは Durable Objects へ逃がさなければならない。
- 永続するディスクも、待ち受けるソケットもない。
node:fsで触れるのはメモリ上の仮想ファイルシステムで、書き込めるのは/tmpだけである。中身はリクエストごとに独立し、残らない。Raw ソケットを開いて待ち受けることもできない。外部との通信路は、fetch()、外向きの TCP 接続を張るconnect()、プラットフォームが注入するバインディング(env.DB等)に限られる。 - 課金はリクエスト数と CPU 時間で決まる。 外部 API からのレスポンスを待っている時間や、タイマーで停止している時間は CPU 時間に数えない。経過時間そのものにも課金されない。
- リソースに上限がある。 メモリ上限は 1 つの isolate あたり 128MB。CPU 実行時間は無料枠で 1 回あたり 10ms、有料枠でも既定で 30 秒(最大 5 分)に制限される。上限に当たり続けた実行は打ち切られ、クライアントには Error 1102(
Worker exceeded resource limits)が返る。
制限値の比較
| 項目 | 無料プラン | 有料プラン |
|---|---|---|
| リクエスト | 10 万/日 | 上限なし(1,000 万/月を含み、超過は従量) |
| CPU 時間 | 10ms/回 | 30 秒/回(最大 5 分) |
| メモリ | 128MB | 128MB |
「リクエストごとに用意される、使い捨ての清潔な部屋」。このメンタルモデルを持てば、グローバル変数に状態を置く誤りを避けられる。
export default {
async fetch(request, env, ctx) {
// request: 受信した HTTP 要求
// env: バインディング(KV、D1、シークレット等)
// ctx: waitUntil 等のライフサイクル制御
return new Response("hello from workers");
},
};// isolate が再利用される間だけ残る(リクエスト間の永続性は保証されない)
// Survives only while the isolate is reused (not guaranteed across requests)
let hits = 0;
export default {
async fetch(request, env, ctx) {
hits += 1;
console.log("hits =", hits);
console.log("method =", request.method);
// waitUntil: レスポンスを返したあとも非同期処理を継続する
// waitUntil: Continue background tasks after returning the response
ctx.waitUntil(Promise.resolve().then(() => console.log("post-response task complete")));
return Response.json({
hits,
url: request.url,
// エディタでは null(エディタが送るリクエストにはヘッダーがない)
// null in the editor (the request the editor sends has no headers)
userAgent: request.headers.get("user-agent"),
});
},
};
実行結果
実行するとここに結果が出ます。ネットワークは塞がれています。