サーバー内部
リクエストが到着したときに、ルーティングやロジックの実行からレスポンス生成まで、サーバー内部で何が起こるかを追ってください。
サーバーとは実際に何か
サーバーとは、単に継続的に動作し、リクエストを待ち受けて応答するプログラムです。
サーバーはリクエストを継続的に待ち受けます
リクエストがサーバーに入り → 内部で処理され → レスポンスがクライアントへ戻る
- サーバーは、オペレーティングシステムの管理下で動作している単なるプログラムです。
- 特定のポートで待ち受け、受信したリクエストを待ちます。
- 長時間稼働し続け、その間に多くのリクエストを処理します。
詳細
「サーバー」という言葉は特別な機械のように聞こえますが、技術的にはただのプログラムです。バックエンドアプリケーションを起動すると、オペレーティングシステムはプロセスを作成し、それをメモリ上で動かし続けます。
一度だけ実行して終了する短いスクリプトとは異なり、サーバーは長時間動作するように設計されています。ネットワークソケットを開き、ポート(80 や 443 など)にバインドし、受信接続を待ちます。
リクエストが届くと、サーバーはデータを読み取り、処理し、レスポンスを返します。処理が終わっても停止せず、次のリクエストを引き続き待ち受けます。
重要なメンタルモデルは次のとおりです: バックエンド = OS の管理下で継続的に動作するプログラム。フレームワーク、データベース、API など、その他すべてはこのシンプルな土台の上に成り立っています。
ネットワーキング層
あなたのサーバーはソケットを通じてオペレーティングシステムと通信し、実際のネットワーク通信はOSが処理します。
- あなたのサーバーはネットワークハードウェアと直接通信しません。
- オペレーティングシステムがネットワークデータを受け取り、接続を管理します。
- ソケットは、サーバーがデータを送受信するために使うインターフェースです。
詳細
サーバーを起動して「ポートで待ち受ける」とき、あなたは ソケット と呼ばれるものを作成しています。ソケットは、オペレーティングシステムが提供する単なる通信チャネルです。
インターネットからデータが届くと、まずマシンのネットワークハードウェアに到達します。オペレーティングシステムのカーネルがそのデータを処理し、ポート番号に基づいてどのプログラムが受け取るべきかを判断します。
その後、OSはデータを正しいソケットに接続されたバッファへ格納します。サーバーは、リクエストを処理する準備ができたときに、そのソケットから読み取ります。
つまり、サーバーが生の電気信号や物理パケットを直接扱うことはありません。OSが提供するきれいなソフトウェアインターフェースを通じてデータを読み書きします。
ソケットは、バックエンドコードと外部の世界をつなぐ橋であり、その下にある複雑なネットワークの詳細は、オペレーティングシステムが静かに管理しています。
オペレーティングシステム
あなたのサーバーが動作するのは、オペレーティングシステムが裏側でハードウェア、リソース、分離を管理しているからです。
- OS は CPU、メモリ、ストレージ、ネットワークへのアクセスを制御します。
- プロセスを分離するため、1つのプログラムが別のプログラムを破壊することはできません。
- 実行をスケジューリングし、リソース割り当てを管理します。
詳細
オペレーティングシステム(OS)は、あなたのサーバーと物理ハードウェアの間にあります。バックエンドコードが CPU、RAM、ディスク、ネットワークカードを直接制御するわけではありません。制御しているのは OS です。
サーバーが動作すると、OS は CPU 時間を与え、メモリを割り当て、ネットワークソケットを開き、ファイルの読み書きを許可します。この仲介がなければ、プログラム同士がハードウェアへのアクセスをめぐって無秩序に競合してしまいます。
OS は分離も強制します。各サーバープロセスには独自の仮想メモリ空間が割り当てられ、実行中の他のプログラムへの意図しない干渉を防ぎます。
高性能なバックエンドシステムであっても、いつ実行され、どれくらいの時間実行されるかは OS のスケジューラに完全に依存しています。このレイヤーをバイパスするサーバーはありません。
簡単に言えば、オペレーティングシステムはサーバーの実行を可能にする目に見えない土台です。
並行処理モデル
サーバーは同時に多くのリクエストを処理する必要があり、その並行処理モデルがその方法を決定します。
- 複数のリクエストが同時に到着することがあります。
- サーバーはスレッドまたはイベントループを使って並行処理を管理します。
- 並行処理モデルはパフォーマンスとスケーラビリティに直接影響します。
詳細
実際のシステムでは、リクエストは1件ずつ到着するわけではありません。数百、数千ものクライアントが同時にリクエストを送ることがあります。サーバーは、他の処理を止めたりブロックしたりせずに、複数のリクエストを進められる必要があります。
一般的な方法の1つは スレッドベースのモデル です。複数のスレッドが並列に動作し、それぞれが1つのリクエストを処理します。これは分かりやすいですが、スレッドを作りすぎるとメモリやCPUをかなり消費することがあります。
もう1つの方法は イベント駆動モデル です。1つのスレッドがノンブロッキングI/Oを使い、リクエストを非同期に処理します。データベース呼び出しのような遅い処理を待つ代わりに、他のタスクを続けて処理し、準備ができたら再開します。
どちらのモデルも、無駄なリソースを最小限にしながらスループットを最大化することを目指しています。並行処理モデルの選択は、負荷が高いときにサーバーがどれだけうまく動作するかを左右します。
複数のリクエストを効率よく処理することが、単純なプログラムをスケーラブルなバックエンドシステムへと変えるため、並行処理を理解することは不可欠です。
リクエストのライフサイクル
サーバーの視点では、リクエストは OS、framework、あなたの handler code を通って処理され、response として返されます。
- operating system は network data を受け取り、それを server process に渡します。
- framework は request を解析し、正しい handler に振り分けます。
- あなたの code が logic を実行し、必要に応じて database とやり取りし、response を返します。
詳細
client が HTTP request を送ると、まず machine の network interface に届きます。operating system は packet を処理し、その data を server に関連付けられた socket buffer に入れます。
server は受信した data を読み取り、framework(たとえば Express、Django、FastAPI など)がそれを解析します。ここには headers、URL path、query parameters、request body の抽出が含まれます。
その後 framework は request を適切な handler function にルーティングします。これは、あなたが書いた code の部分です。ここで business logic が実行されます。たとえば、input の validation、計算の実行、database の問い合わせ、外部 service の呼び出しなどです。
handler が処理を終えると、response object を返します。framework はそれを HTTP response に整形し、OS が network stack を通して送信し、client に戻ります。
server の視点では、この lifecycle は継続的に繰り返されます: receive → process → respond。すべての backend request は、この構造化された flow に従います。
ハンドラーの内部
ハンドラーは、サーバーの実際のビジネスロジックが実行され、リクエストを意味のあるレスポンスに変換する場所です。
if (!req.body.email) {
return error("missing email")
}
if (!user.canPurchase) {
return error("permission denied")
}const price = cart.total()
if (price > creditLimit) {
throw new Error("limit")
}
applyDiscount(cart)const user = await db.users.find(id) await cache.set( "cart:" + user.id, cart )
return {
status: "success",
orderId: order.id,
total: price
}- ハンドラーは、受信したリクエストデータを検証し、解釈します。
- 計算やルールチェックなどのビジネスロジックを実行します。
- レスポンスを返す前に、キャッシュ、データベース、または外部サービスと連携することがあります。
詳細
フレームワークがリクエストを解析したあと、handler function を呼び出します。これは、実際にあなたが書いたコードの部分です。ここで本当の処理が行われます。
ハンドラーは通常、入力の検証から始まります。必要なフィールドが存在するか、値が正しい形式か、ユーザーがその操作を実行する権限を持っているかを確認します。
次にビジネスロジックが続きます。ここには、計算、ルールの適用、データの変換、クエリの準備などが含まれます。必要に応じて、ハンドラーはデータベースを呼び出したり、キャッシュにアクセスしたり、別のサービスと通信したりします。
処理が完了すると、ハンドラーは response object を構築します。これには、JSON データ、成功メッセージ、またはエラーの説明が含まれることがあります。
ハンドラーはバックエンドシステムの中核です。前段の処理はリクエストを準備し、後段の処理は結果を届けますが、意思決定が行われるのはハンドラーの内部です。
キャッシュ層
キャッシュは、頻繁に使われるデータを高速なメモリに保存し、サーバーが毎回データベースを問い合わせなくて済むようにします。
- サーバーは、データベースを問い合わせる前にまずキャッシュを確認します。
- キャッシュヒットが起きると、重いデータベース処理なしで素早くデータを返せます。
- キャッシュミスが起きると、データベースを問い合わせ、その結果を将来のために保存します。
詳細
データベースは強力ですが、メモリと比べると比較的遅いです。もしすべてのリクエストが直接データベースを問い合わせると、負荷が高い状況ではすぐにボトルネックになります。
キャッシュ は、最近または頻繁にアクセスされたデータを高速なメモリ(多くの場合 RAM)に保存します。リクエストが届くと、サーバーはまず必要なデータがキャッシュにすでにあるかを確認します。
データが見つかった場合(キャッシュヒット)、サーバーはすぐに結果を返せます。これにより、レイテンシとデータベース負荷が大幅に減ります。
データが見つからない場合(キャッシュミス)、サーバーはデータベースを問い合わせて結果を取得し、それをキャッシュに保存してからクライアントに返します。
キャッシュはパフォーマンスとスケーラビリティを向上させますが、キャッシュされたデータをデータベースと整合させ続けるといった複雑さも生みます。正しく実装すれば、システムへの負荷を大きく減らせます。
データベースとのやり取り
データがメモリやキャッシュにない場合、サーバーはデータベースに問い合わせて、永続的な情報を取得または変更します。
- ハンドラーは、保存されたデータが必要なときにデータベースへクエリを送信します。
- データベースはクエリを処理し、結果を返します。
- データベースの速度と設計は、サーバーのパフォーマンスに直接影響します。
詳細
データベースは、ユーザーアカウント、注文、メッセージ、アプリケーションの状態などのデータを永続的に保存する役割を担います。サーバーがメモリやキャッシュにまだない情報を必要とするとき、データベースにクエリを送信します。
データベースはクエリを解析し、インデックスやテーブルなどの保存構造を検索して、要求されたデータを返します。この処理は、ディスクアクセスや複雑な検索を伴うことがあるため、通常はメモリ内操作よりも遅くなります。
結果を受け取った後、サーバーはデータを変換したり、ビジネスロジックを適用したり、レスポンスとして整形してからクライアントに返します。
最適化されていないクエリ、インデックスの不足、または高トラフィックによって、データベースがボトルネックになることがあります。そのため、データベース設計とクエリ効率はバックエンドのパフォーマンスにとって非常に重要です。
実際の多くのシステムでは、データベースとのやり取りはリクエストのライフサイクルの中でも特にコストの高い部分の1つです。
プロセスのライフサイクルとリソース管理
サーバープロセスにはライフサイクルがあります。起動し、ソケットやファイルディスクリプタなどのリソースを取得し、継続的に動作し、最後はきれいにシャットダウンしなければなりません。
- 起動時に、サーバーは設定を初期化し、ソケットを開き、依存先へ接続します。
- 実行中は、メモリ、ファイルディスクリプタ、ネットワーク接続などのリソースを保持します。
- シャットダウン時には、接続を閉じてリソースを安全に解放する必要があります。
詳細
サーバープロセスが起動すると、初期化処理を行います。これには、設定の読み込み、待ち受けソケットのオープン、データベースへの接続、キャッシュの準備などが含まれることがよくあります。
実行中、プロセスは複数の file descriptors を所有します。これは、ネットワークソケット、開いているファイル、パイプなどのリソースへのハンドルです。オペレーティングシステムはこれらを管理しており、一度に開ける数にはシステム上の制限があります。
サーバーが file descriptors をリークしたり、接続を適切に閉じなかったりすると、やがてシステムの上限に達し、新しいリクエストを受け付けられなくなることがあります。
デプロイ、スケーリング、障害などの理由で停止するとき、よく設計されたサーバーは graceful shutdown を行います。新しいリクエストの受付を停止し、進行中の処理を完了させ、データベース接続を閉じ、リソースを解放します。
適切なライフサイクル管理は、メモリリーク、ディスクリプタ枯渇、再起動時の不整合な状態を防ぎます。安定したバックエンドシステムは、規律あるリソース管理に大きく依存しています。
サーバー障害シナリオ
サーバーはさまざまな理由で障害を起こします。どこで障害が発生するかを理解すると、より安定したシステムを設計しやすくなります。
- アプリケーションエラーはクラッシュや 500 レスポンスの原因になります。
- リソース枯渇(CPU、メモリ、ファイルディスクリプタ)はサーバーの動作を停止させることがあります。
- データベースのような外部依存先は、ボトルネックになったり完全に障害したりすることがあります。
詳細
障害はサーバースタックの複数の層で発生し得ます。アプリケーションレベルでは、未処理の例外やロジックバグによって 500 エラーが発生したり、プロセス自体がクラッシュしたりすることがあります。
リソースレベルでは、高トラフィックによって CPU、メモリ、またはファイルディスクリプタの上限が枯渇することがあります。たとえば、開いている接続が多すぎると、新しいクライアントが接続できなくなる場合があります。メモリ使用量が過剰になると、オペレーティングシステムによって out-of-memory (OOM) kill が発生することがあります。
同時実行の問題も障害の原因になります。ブロッキング処理はリクエストのタイムアウトにつながることがあり、レースコンディションは一貫しない動作を引き起こすことがあります。
外部システムは、さらに別の障害ポイントを生みます。データベースが遅くなったり利用できなくなったりすると、リクエストが滞留し、システム全体のレイテンシが増加することがあります。
これらの障害シナリオを理解することは重要です。実際のバックエンドシステムは、理想的な条件でどれだけ速く動くかではなく、何か問題が起きたときにどう振る舞うかで評価されるからです。
質問セクション
1 / 5