Async Local Storage Deep Dive 2026 ─ 内部実装・落とし穴・性能を 9 章で読み解く

ALS を表面的に使っている中〜上級 Node.js エンジニアに向けて、AsyncContextFrame までの実装進化・context loss の根本原因・公開ベンチ・2026 年 1 月の DoS 脆弱性までを 9 章で深掘りする

はじめに ─ ALS を「なんとなく」使っているあなたへ

こんな疑念を、毎日感じていないか

// あなたが書いている、おなじみのコード
import { AsyncLocalStorage } from 'node:async_hooks';

type RequestContext = { requestId: string };
const requestContext = new AsyncLocalStorage<RequestContext>();

app.use((req, res, next) => {
  requestContext.run({ requestId: req.headers['x-request-id'] as string }, () => {
    next();
  });
});

// ───── ずっと先のミドルウェアやサービス層で
async function fetchUser(id: string) {
  await db.query(/* ... */);
  await externalApi.get(/* ... */);

  const ctx = requestContext.getStore(); // ← なぜ取れる?
  logger.info({ requestId: ctx?.requestId }, 'user fetched');
}

await を 5 段跨ぎ、setTimeout を経由し、Promise を Promise.all で束ねた先で、なぜ requestId は取れるのか。

そして「なぜか取れない」場面に遭遇したことはないか。EventEmitter の listener、custom thenable、Worker への postMessage の向こう側。そのとき何が起きていたのか、実装レベルで説明できるだろうか。

公式ドキュメントの “Troubleshooting: Context loss” を読んでもいまいち腑に落ちないのは、ALS の内部実装を知らないまま症状の話だけ読んでいるからだ。

このシリーズが目指すこと

ALS の API(run / getStore)は誰でも書ける。だがその裏で:

  • なぜ await を超えてコンテキストが伝播するのか
  • なぜ EventEmitter の listener では消えるのか
  • Node.js のバージョンで性能がどう変わったのか(いつ・なぜ)
  • 2026 年 1 月の DoS 脆弱性で、なぜ React Server Components / Next.js は無事で APM ツールは被弾したのか

これらを「公式ドキュメントを読んでも書いていない」レベルで腹落ちさせるのがこのシリーズの目的。実装の進化を縦に追うことで、表面的な FAQ では答えられない問いに自分で答えを出せるようになる。

対象読者

  • ALS を run() / getStore()書いたことはある中〜上級 Node.js エンジニア
  • 「なんとなく動いている」を脱したい人
  • context loss にハマったことがある、または事前に避けたい人
  • APM やトレーシングを運用していて、性能オーバーヘッドを定量的に説明できるようになりたい人

「ALS とは何?」というレベルから始める入門書ではない。第 1 章でおさらいはするが、最低限の知識は前提にする。

全 9 章の構成

テーマ
1AsyncLocalStorage とは(おさらい)
2async_hooks と AsyncResource の仕組み
3AsyncLocalStorage の “薄さ” ─ Node 13.10 当初の実装
4V8 PromiseHook の登場 ─ Node 16+ で 3-4x になった話
5AsyncContextFrame と createHook との決別 ─ Node 24 の刷新
6Context Loss の根本原因
7性能を読み解く(公開ベンチから)
82026/1 の DoS 脆弱性 ─ なぜ React/Next.js は無事で APM は被弾したか
9これから ─ TC39 AsyncContext と ALS の今後

読み方

各章の末尾に次章への問いかけを仕込んでいる。順に読むと、前章で生まれた疑問が次章で解ける構造になっている。流し読みでも筋は追えるが、章末の問いを意識して読むと 1 章で投げた “なぜ伝播するのか” が、4-5 章の実装で解け、8 章の脆弱性で回収される という伏線の張り方が見える。

途中で「なぜ今この話をしているのか」を見失ったら、目次に戻ってその章のテーマを確認してほしい。各章は単独でも読めるが、シリーズとして読むことで実装の進化が立体的に見える設計にしてある。

それでは始めよう。第 1 章は「ALS とは」のおさらいから。

目次

  1. AsyncLocalStorage とは(おさらい) ALS の API(run / enterWith / exit / getStore)を最短でおさらいし、enterWith の "罠" と「なぜ伝播するのか」の伏線を張る
  2. async_hooks と AsyncResource の仕組み ALS の足元にある async_hooks のライフサイクル(init/before/after/destroy)と AsyncResource の役割を、現在は非推奨になった経緯と合わせて解説する
  3. AsyncLocalStorage の "薄さ" ─ Node 13.10 当初の実装 Node 13.10 で導入された AsyncLocalStorage の当初実装を lib/internal レベルで読み、createHook の薄いラッパーであった事実と、その帰結としての性能課題を確認する
  4. V8 PromiseHook の登場 ─ Node 16+ で 3-4x になった話 PR
  5. AsyncContextFrame と createHook との決別 ─ Node 24 の刷新 Node 24 で AsyncLocalStorage が createHook を内部で使わない実装に進化した経緯を、ContinuationPreservedEmbedderData と AsyncContextFrame の関係から解説する
  6. Context Loss の根本原因 公式ドキュメントの "Troubleshooting Context loss" を、async_hooks と AsyncContextFrame の知識を踏まえて読み直す。callback / EventEmitter / Worker / shared promise の各ケースで何が起きているかを実装レベルで解明する
  7. 性能を読み解く(公開ベンチから) 自前ベンチに頼らず、信頼できる公開ベンチ(Platformatic / PR
  8. 2026/1 の DoS 脆弱性 ─ なぜ React/Next.js は無事で APM は被弾したか 2026 年 1 月に公表された async_hooks 起因の DoS 脆弱性を、TryCatchScope::kFatal の機構と Node 24 で createHook と決別したことによる影響範囲の違いから読み解く。第 5 章の決別が結果的に脆弱性を回避した伏線回収章
  9. これから ─ TC39 AsyncContext と ALS の今後 TC39 AsyncContext proposal の現状、Bun / Deno の動向、ALS を使うときに今知っておくべき実用 takeaway を整理する