こんにちは!ネットワークの深淵を愛するインフラアーキテクトです。
普段何気なく使っているWeb API。ブラウザでURLを叩いたり、アプリからデータを取得したりするとき、裏側では目にも留まらぬ速さでデータがやり取りされていますよね。
「APIのURLを綺麗に設計しよう」「RESTの原則を守ろう」と学ぶとき、どうしてもデータの形やURLの文字列に意識が向きがちですが……ちょっと待ってください。そのデータやURLを乗せて、実際にネットワークの海を渡っている「通信の土台」の仕組みを意識したことはありますか?
今回は、APIのパフォーマンスを裏から支える隠れた主役、「HTTP/1.1 Keep-Alive(コネクション再利用の最適化)」について、インフラの現場の匂いを感じさせつつ、身近な例えを交えて優しく紐解いていきたいと思います。一歩ずつ理解していきましょう!
—
1. 郵便配達で例える「TCPハンドシェイク」の重み
Webブラウザやスマホアプリがサーバーと通信するとき、ベースとなるのは「TCP」という通信規格です。このTCPの世界では、データを送り始める前に必ず「お見合い(ハンドシェイク)」を行うルールになっています。
現実世界の郵便配達で例えてみましょう。
- 通常の通信(コネクションを毎回切る世界):
あなたが友達に手紙を送るたびに、まず「今から手紙を送ってもいいですか?」「はい、どうぞ!」という確認の往復(TCPハンドシェイク)をわざわざ行い、手紙を1通送ったらすぐに郵便ポストを撤去してしまうようなものです。次の手紙を送るには、またゼロから「手紙を送っていいですか?」の挨拶からやり直さなければなりません。
- Keep-Aliveの世界(コネクションを維持する世界):
最初に1回だけ「これからもやり取りする専用のパイプライン(直通電話)」をしっかり開通させます。そのパイプラインが繋がっている間は、挨拶を省略して、何往復でもスピーディーに荷物を送り届けることができますよね。
この「最初のお見合い」には、光の速さであっても物理的な距離に応じた「往復の時間(レイテンシ)」が確実に発生します。特にスマホのモバイル回線などでは、このハンドシェイクのオーバーヘッドが数千ミリ秒の遅延となり、ユーザーをイライラさせる原因になります。
この無駄なオーバーヘッドをごっそり削減してくれる仕組みこそが、HTTP/1.1で標準化されたKeep-Alive(コネクションの持続)なのです。
—
2. Keep-Aliveの仕組みと「タイムアウト」のジレンマ
HTTP/1.1以降、デフォルトでは一度確立したTCPコネクションはすぐに切断されず、サーバーとクライアントの間で保持され続けます。
「じゃあ、ずっと繋ぎっぱなしにしておけば最強じゃないか!」と思いますよね。しかし、世の中そんなに甘くありません。ここがインフラエンジニアの腕の見せ所であり、悩ましいポイントです。
サーバーは、同時に何千、何万というクライアントからの接続を処理しています。もし、すでに用事が済んでいるのに「いつまた使うか分からないから」と、すべてのコネクションを永遠に維持し続けたらどうなるでしょうか?
サーバーのメモリや同時接続数のリソース(枠)がすぐに枯渇してしまい、新しいユーザーがアクセスできなくなってしまいます(これをリソースの食つぶしと呼びます)。
そこで登場するのが「タイムアウト設定」です。
- 「一定時間(例: 5秒間)次のリクエストが来なかったら、このパイプラインは一旦閉じましょうね」
- 「1つのコネクションでやり取りできるリクエストの最大数(Max Requests)を超えたら、一度リフレッシュしましょう」
というルールを設けることで、パフォーマンスの維持とサーバーリソースの節約のバランスを取っているのです。
—
3. 実務で触れるWebサーバー設定(Nginxの例)
実際のインフラ現場では、このKeep-Aliveの挙動をWebサーバー(NginxやApacheなど)の設定ファイルでチューニングします。
例えば、モダンなWeb APIサーバーのフロントによく使われるNginxの設定を覗いてみましょう。
http {
# クライアントとのKeep-Alive接続を維持する最大時間(秒)
# リクエストが途絶えてからこの秒数経つと、サーバー側からコネクションを切断します
keepalive_timeout 65;
# 1つのKeep-Aliveコネクション経由で処理できる最大リクエスト数
# これを超えると、サーバー側から「一度コネクションを張り直そう」と促します
keepalive_requests 100;
# アップストリーム(バックエンドのAPIアプリケーション)との接続維持設定
upstream my_api_backend {
server 127.0.0.1:8080;
# バックエンドへのコネクションをキャッシュして再利用する数
# これにより、バックエンド側のTCPハンドシェイク負荷を劇的に削減します
keepalive 32;
}
}
このように、keepalive_timeout や keepalive_requests といったパラメータを適切に調整することで、APIの応答速度(レスポンスタイム)を劇的に改善することができます。
—
4. クライアント側(コード)から見たコネクション再利用
インフラ側だけでなく、APIを叩くクライアント側のプログラム(SDKやHTTPクライアント)でも、このKeep-Aliveの恩恵を最大限に受けるためのコツがあります。
例えば、Pythonの requests ライブラリを使って、連続してAPIを叩くコードを書いてみましょう。
import requests
# セッションオブジェクトを作成する
# このセッションを使うことで、内部のTCPコネクションが維持(Keep-Alive)されます!
session = requests.Session()
api_endpoints = [
"https://api.example.com/v1/users",
"https://api.example.com/v1/posts",
"https://api.example.com/v1/comments"
]
print("APIへの連続リクエストを開始します...")
for url in api_endpoints:
# 毎回 requests.get() を呼ぶのではなく、同じ session を使い回す
# これにより、2回目以降のリクエストでTCPハンドシェイクがスキップされます
response = session.get(url, timeout=5)
print(f"URL: {url} | ステータスコード: {response.status_code}")
print("すべてのリクエストが完了しました。")
もしここで requests.Session() を使わず、毎回裸の requests.get() を呼び出してしまっていたらどうなるでしょうか?
せっかくサーバー側がKeep-Aliveの準備をしてくれていても、クライアント側が「毎回使い捨ての電話機」を使うようなものなので、リクエストのたびに無駄なTCPハンドシェイクが発生してしまいます。
小さなループ処理や、マイクロサービス間で大量のAPIコールを行うシステムでは、この「セッションの使い回し」を意識するだけで、全体の処理時間が何倍も変わってくるのです。
—
まとめ
今回は、HTTP/1.1 Keep-Aliveとコネクション再利用の最適化について解説しました。
- TCPハンドシェイクは郵便配達の「事前の挨拶」のようなもので、毎回行うと時間がかかる。
- Keep-Aliveを使うことで、同じパイプラインを使い回し、APIのレスポンスを高速化できる。
- ただし、サーバーのリソースを守るために、適切なタイムアウト(
keepalive_timeout)の設定が不可欠。 - クライアント側でも、HTTPセッションを維持して使い回すことがパフォーマンス向上のカギ。
美しいREST APIのURL設計やデータの構造化はもちろん大切ですが、その下を支えるネットワークの「通り道」に少し目を向けてみると、エンジニアとしての視界がぐっと広がりますよね。
日々の開発やインフラ構築の中で、「お、今このリクエストはコネクションが再利用されているな」と、目に見えないパケットの流れをフッと感じられたら、あなたも立派なネットワーク・スペシャリストの仲間入りです!
それでは、また次回の深淵でお会いしましょう。
コメント