1 Node.jsとは何ですか?また、どのような用途で使用されますか?
Node.jsは、V8 JavaScriptエンジンを基盤とし、ネットワーク、ファイルシステム、プロセス、ストリーム、OSへのアクセス機能などのAPIによって拡張された、ブラウザ外で動作するJavaScript実行環境(ランタイム)です。主にREST API、GraphQLサービス、リアルタイムWebSocketサーバー、マイクロサービス、CLIツール、プロキシ、APIゲートウェイなどに広く利用されています。最大の強みはノンブロッキングI/Oであり、1つのプロセスで大量のネットワーク接続を効率的に処理できます。一方で、CPU負荷の高い長時間の演算を行うと、ワーカースレッドや別プロセス、あるいは別サービスに切り離さない限りイベントループをブロックしてしまうというトレードオフがあります。
AI コーチを使ってこの質問に答えてみる
2 Node.js の内部動作の仕組みはどのようになっていますか?
JavaScriptはV8上で実行され、Node.jsのAPIは非同期処理をオペレーティングシステムまたはlibuvのスレッドプールに委任します。メインのJavaScriptスレッドはI/O操作の完了を待たず、他のコードの実行を継続します。処理が完了すると、そのコールバックやPromiseの継続処理がイベントループで実行可能な状態になります。このようにして、ユーザーのJavaScriptが通常1つのメインスレッド上で動作していても、Node.jsは高い並行性を実現しています。
AI コーチを使ってこの質問に答えてみる
3 V8、libuv、およびイベントループとは何ですか?
V8はJavaScriptをコンパイルおよび実行し、JavaScriptオブジェクトとメモリを管理します。libuvはクロスプラットフォームなイベントループ、非同期I/Oの統合、および一部のファイルシステム操作、DNS、暗号処理などのためのワーカースレッドプールを提供します。イベントループは、完了した処理のコールバックを実行するタイミングを決定します。Node.js自体はJavaScript APIをネイティブ機能と結び付ける役割を果たしており、V8単体ではHTTPサーバー、ファイルシステムアクセス、プロセスAPIなどは提供されません。
AI コーチを使ってこの質問に答えてみる
4 Node.jsがシングルスレッドと呼ばれることが多いのはなぜですか?
単一のNode.jsプロセスは、通常1つのメインスレッド上でユーザーのJavaScriptを実行するため、2つの通常のリクエストハンドラがそのスレッド内でまったく同時にJavaScriptを実行することはありません。しかし、プラットフォーム全体が文字通り1つのスレッドだけで構成されているわけではありません。libuvにはスレッドプールが存在し、OS (Operating System)が非同期I/Oを実行し、V8がガベージコレクションを行い、さらにNode.jsはworker_threadsや子プロセスもサポートしています。そのため、この表現は主にデフォルトのJavaScript実行モデルを指しています。
AI コーチを使ってこの質問に答えてみる
5 Node.jsはどのようにして多数のリクエストを並行して処理できるのですか?
Node.jsはリクエストごとにスレッドをブロックするのではなく、I/O処理を開始した後にイベントループへと制御を戻します。たとえば、メインスレッドが新しい接続の受け付けや処理可能なコールバックの実行を継続している間に、1,000件のデータベース呼び出しを並行して処理(in-flight)できます。これはHTTP呼び出し、データベースクエリ、ファイル操作、ソケット通信などのI/Oバウンドなワークロードに適しています。一方で、リクエストハンドラで重い同期的なCPU処理を行うと、イベントループがブロックされて他のすべてのリクエストが遅延するため、パフォーマンスが大きく低下します。
AI コーチを使ってこの質問に答えてみる
6 Node.js のイベントループの主なフェーズにはどのようなものがありますか?
libuv のイベントループには、timers、pending callbacks、idle/prepare、poll、check、close callbacks などのフェーズがあります。timers は setTimeout や setInterval のコールバックを実行し、poll は多くの I/O(Input/Output)イベントを処理し、check は setImmediate のコールバックを実行し、close はソケットの切断などのクローズイベントを処理します。また、Node.js には process.nextTick や Promise のマイクロタスクを含むマイクロタスクキューがあり、各コールバックの合間に実行されます。マイクロタスクが多すぎると I/O が飢餓状態(スタベーション)に陥り、サービスが停止したように見える原因になります。
AI コーチを使ってこの質問に答えてみる
これらの回答を声に出して練習する準備はできていますか?
声で回答し、技術の深さ、構成、英語でのコミュニケーションについてフィードバックを受け取れます。
7 同期、非同期、およびノンブロッキング処理の違いは何ですか。
同期処理は次の行が実行される前に処理を完了して値を返し、リクエストハンドラ内の`fs.readFileSync`のようにメインスレッドをブロックします。非同期処理は、コールバックや`Promise`を通じて後から結果を返します。ノンブロッキングとは、処理が保留されている間もスレッドが他の作業を継続できることを意味します。これらの用語は関連していますが同一ではありません。非同期は結果がどのように渡されるかを表し、ノンブロッキングはスレッドが待機するかどうかを表します。バックエンドのリクエストハンドラでは、一般的に非同期かつノンブロッキングのAPIを優先すべきです。
import fs from 'node:fs';
const syncData = fs.readFileSync('file.txt', 'utf8');
const asyncData = await fs.promises.readFile(
'file.txt',
'utf8'
);
AI コーチを使ってこの質問に答えてみる
8 イベントループのスタベーション(event loop starvation)とは何ですか?
イベントループのスタベーションは、JavaScriptの処理やマイクロタスクがイベントループを占有し続け、I/O(Input/Output)、タイマー、新しいリクエストを迅速に処理できなくなることで発生します。主な原因としては、重い同期計算、巨大なJSON処理、際限のないPromiseチェーン、過剰な`process.nextTick`の使用などが挙げられます。対策としては、処理を細かく分割(チャンク化)する、入力サイズを制限する、CPUプロファイリングを実施する、CPU集約型の処理を`worker_threads`や別サービスへ切り出す、性能上重要なリクエストパスで同期APIを避けることなどが有効です。
app.get('/report', (req, res) => {
const result = performVeryHeavyCalculation();
res.json(result);
});
AI コーチを使ってこの質問に答えてみる