Node.jsは非同期で動作するため、リクエストごとに変数のスコープを持つことが難しいとされてきました。これにより、リクエスト単位でログのトレースIDやユーザー情報を扱う際、グローバル変数やクロージャでは意図しない値の共有が発生することがあります。
この問題を解決するために登場したのが、AsyncLocalStorage
です。本記事では、AsyncLocalStorage
の基本的な使い方から、Expressとの組み合わせによる実践的な活用方法まで詳しく解説します。
AsyncLocalStorageとは
AsyncLocalStorage
は、Node.jsのasync_hooks
モジュールに含まれているクラスで、非同期コンテキストごとに状態(スコープ)を保持できます。各リクエストにユニークなトレースIDやユーザー情報を割り当てて、ログやデバッグ時に利用するのが代表的なユースケースです。
導入と基本構文
Node.js v13.10.0以降であれば、追加パッケージのインストールなしに利用できます。
const { AsyncLocalStorage } = require('async_hooks');
const asyncLocalStorage = new AsyncLocalStorage();
// 任意のコンテキストをrunで設定
asyncLocalStorage.run({ requestId: 'abc123' }, () => {
someAsyncOperation().then(() => {
const store = asyncLocalStorage.getStore();
console.log('リクエストID:', store.requestId);
});
});
Expressと組み合わせた実用例
通常のミドルウェアでは、非同期処理を通じてコンテキストが失われることがありますが、AsyncLocalStorage
を使えば、リクエストライフサイクル全体で情報を保持できます。
1. 初期化とミドルウェア定義
// context.js
const { AsyncLocalStorage } = require('async_hooks');
const asyncLocalStorage = new AsyncLocalStorage();
module.exports = {
asyncLocalStorage,
};
// middleware/context.js
const { asyncLocalStorage } = require('../context');
const { v4: uuidv4 } = require('uuid');
function contextMiddleware(req, res, next) {
const requestId = uuidv4();
asyncLocalStorage.run({ requestId }, () => {
next();
});
}
module.exports = contextMiddleware;
2. ルーティングとログ出力
// server.js
const express = require('express');
const app = express();
const contextMiddleware = require('./middleware/context');
const { asyncLocalStorage } = require('./context');
app.use(contextMiddleware);
app.get('/', (req, res) => {
const store = asyncLocalStorage.getStore();
console.log('リクエストID:', store.requestId);
res.send('Hello, World');
});
app.listen(3000, () => {
console.log('Server running at http://localhost:3000');
});
ユースケースとメリット
AsyncLocalStorageは、次のような場面で特に有用です:
- ログのトレース性向上: 各リクエストに一意のIDを持たせて、非同期ログを統合
- 認証情報のスレッドセーフな保持: ユーザーIDなどの状態をグローバルに使わず参照可能
- デバッグの効率化: 問題発生時のリクエストスコープを特定しやすい
注意点と落とし穴
AsyncLocalStorage
は非常に便利ですが、次のような点に注意が必要です:
- イベントや
setTimeout
など、async_hooks
にフックできない実装では一部動作が保証されないことがある - 古いNode.jsバージョンでは未対応(v12では不完全)
- 長期間保持されるとメモリリークの原因となる場合がある
まとめ
AsyncLocalStorage
は、Node.jsの非同期環境下で「リクエスト単位の状態保持」を可能にする非常に強力なツールです。ExpressなどのWebフレームワークと組み合わせることで、ログの一貫性向上、ユーザー情報の安全な参照、トレース機能の強化など多くのメリットを享受できます。
一見地味な機能に見えますが、スケーラブルなNode.jsアプリケーション設計には欠かせない要素の一つです。ぜひプロジェクトに取り入れて、デバッグ効率や可読性の高いコードベースを構築しましょう。