障害時に起きること
障害時に起きること
Section titled “障害時に起きること”Node.jsアプリケーションで障害が発生した際のシナリオを詳しく解説します。
シナリオ1: イベントループのブロック
Section titled “シナリオ1: イベントループのブロック”障害のシナリオ
Section titled “障害のシナリオ”時刻: 2024-01-01 10:00:00状況: 大量のリクエスト処理中
10:00:00.000 - リクエスト1受信(large-file.txtを読み取り開始)10:00:00.100 - 同期I/O操作によりイベントループがブロック10:00:05.000 - ファイル読み取り完了(5秒経過)10:00:05.100 - リクエスト2受信(5秒間待機していた)10:00:05.200 - リクエスト3受信(5秒間待機していた)10:00:05.300 - リクエスト4受信(5秒間待機していた)...10:00:10.000 - すべてのリクエストが処理される(10秒経過)実際のコード:
// ❌ 問題のあるコードconst fs = require('fs');
app.get('/data', (req, res) => { // 同期I/O操作によりイベントループがブロック const data = fs.readFileSync('large-file.txt', 'utf8'); res.json({ data });});障害の影響:
- イベントループのブロック: 5秒間、イベントループがブロックされる
- 他のリクエストの処理不可: 5秒間、他のリクエストが処理できない
- タイムアウト: クライアントがタイムアウトする可能性がある
- パフォーマンスの低下: アプリケーション全体のパフォーマンスが低下する
解決策:
// ✅ 解決策: 非ブロッキング操作を使用import { readFile } from 'fs/promises';
app.get('/data', async (req, res) => { try { // 非ブロッキング操作 const data = await readFile('large-file.txt', 'utf8'); res.json({ data }); } catch (error) { res.status(500).json({ error: 'Internal server error' }); }});シナリオ2: メモリリークによるクラッシュ
Section titled “シナリオ2: メモリリークによるクラッシュ”障害のシナリオ
Section titled “障害のシナリオ”時刻: 2024-01-01 10:00:00状況: 長時間実行されるアプリケーション
10:00:00.000 - アプリケーション起動(メモリ使用量: 100MB)10:00:01.000 - リクエスト1受信(イベントリスナー1を追加、メモリ使用量: 105MB)10:00:02.000 - リクエスト2受信(イベントリスナー2を追加、メモリ使用量: 110MB)...10:30:00.000 - リクエスト1000受信(イベントリスナー1000を追加、メモリ使用量: 2GB)10:30:01.000 - ガベージコレクションが動作(メモリ使用量: 1.8GB)10:30:02.000 - OutOfMemoryError発生10:30:03.000 - アプリケーションがクラッシュ実際のコード:
// ❌ 問題のあるコードconst eventEmitter = new EventEmitter();
app.post('/orders', (req, res) => { // イベントリスナーが削除されない eventEmitter.on('order.created', (order) => { sendEmail(order.userId); });
createOrder(req.body); res.json({ status: 'ok' });});障害の影響:
- メモリの枯渇: イベントリスナーが蓄積され、メモリが枯渇する
- ガベージコレクションの頻発: ガベージコレクションが頻繁に動作し、パフォーマンスが低下
- OutOfMemoryError: メモリが枯渇し、アプリケーションがクラッシュ
- データの損失: 処理中のデータが失われる可能性がある
解決策:
// ✅ 解決策: イベントリスナーを適切に管理const eventEmitter = new EventEmitter();
// 一度だけ登録eventEmitter.once('order.created', (order) => { sendEmail(order.userId);});
app.post('/orders', (req, res) => { createOrder(req.body); eventEmitter.emit('order.created', order); res.json({ status: 'ok' });});シナリオ3: 未処理のPromiseによるエラーの隠蔽
Section titled “シナリオ3: 未処理のPromiseによるエラーの隠蔽”障害のシナリオ
Section titled “障害のシナリオ”時刻: 2024-01-01 10:00:00状況: 注文作成処理中
10:00:00.000 - 注文作成リクエスト受信10:00:00.100 - createOrder()を呼び出す(Promiseが処理されない)10:00:00.200 - レスポンスを返す(status: 'ok')10:00:00.300 - createOrder()内でエラー発生(データベース接続エラー)10:00:00.400 - エラーが処理されない(未処理のPromise)10:00:00.500 - ユーザーには「成功」と表示される10:00:01.000 - データベースに注文が存在しないことを確認実際のコード:
// ❌ 問題のあるコードapp.post('/orders', (req, res) => { // Promiseが処理されていない createOrder(req.body); res.json({ status: 'ok' });});
async function createOrder(orderData: OrderData): Promise<Order> { // エラーが発生しても処理されない const order = await prisma.order.create({ data: orderData }); return order;}障害の影響:
- エラーの隠蔽: エラーが発生しても処理されない
- データの不整合: ユーザーには「成功」と表示されるが、データは作成されていない
- デバッグの困難: エラーの原因が分からない
解決策:
// ✅ 解決策: 適切なエラーハンドリングapp.post('/orders', async (req, res) => { try { const order = await createOrder(req.body); res.json(order); } catch (error) { res.status(500).json({ error: 'Internal server error' }); }});
async function createOrder(orderData: OrderData): Promise<Order> { const order = await prisma.order.create({ data: orderData }); return order;}障害時に起きることのポイント:
- イベントループのブロック: 同期I/O操作によりイベントループがブロックされる、非ブロッキング操作を使用
- メモリリークによるクラッシュ: イベントリスナーが蓄積され、メモリが枯渇する、イベントリスナーを適切に管理
- 未処理のPromiseによるエラーの隠蔽: エラーが発生しても処理されない、適切なエラーハンドリングを実装
これらの障害シナリオを理解することで、より堅牢なNode.jsアプリケーションを構築できます。