HTTP — ステートレスなプロトコルとコネクション再利用
一言でいうと
HTTPは、リクエスト1つにレスポンス1つを返す、ステートレスな規約であり、性能の歴史は、その上でTCP接続をどう節約して使うかの歴史でした。
なぜ必要なのか
HTTP/1.0は、リクエストごとにTCP接続を新しく結んで切っていました。画像30枚が入ったページを開くと、接続を30回結びます。接続1つごとに往復が1回(TLSまで使うなら2、3回)追加されるので、遅延が、そのままユーザーの体感につながりました。
どう動くのか
HTTP/1.1のkeep-aliveは、応答の後も接続を開けておき、次のリクエストで再利用します。往復のコストを大きく減らしましたが、限界があります。1つの接続で、応答はリクエストの順序どおりに出る必要があります。前のリクエストが遅いと、後ろのリクエストが待ちます(アプリケーション層のヘッドオブラインブロッキング)。ブラウザーは、ドメインごとに接続を6個ほど開く方式で回避し、そのため、リソースを複数のドメインに散らす手法まで流行しました。
HTTP/2は、1つの接続の中で、リクエストをストリームとして多重化して、この問題をなくしました。ヘッダーも圧縮します。ただし、依然としてTCP 1つの接続の上にあるので、パケット1つが失われると、その後のすべてのストリームが一緒に止まります。アプリケーション層の順番待ちはなくなりましたが、トランスポート層の順番待ちは残りました。
HTTP/3は、QUIC(UDPベース)の上に移って、ストリームごとの独立した配送を得ました。1つのストリームの損失が、他のストリームをふさぎません。
メソッドの意味も、規約の一部です。GETは安全(副作用なし)で冪等であり、PUTとDELETEは安全ではありませんが冪等で、POSTは両方とも違います。冪等性は、リトライ可能性と直結します。タイムアウトが起きたとき、リクエストがサーバーに届いたかわからないので、冪等でないリクエストをうっかりリトライすると、注文が2回できます。そのため、決済APIは、冪等性キーを要求します。
ステータスコードも、ひとまとめにしてはいけません。4xxはリクエストを直す必要があり、5xxはサーバーの問題なので、リトライに意味がある場合があります。特に、502、503、504は原因が違います。502は、ゲートウェイがバックエンドから不正な応答を受け取ったもので、503は、サービスが自分で対処できないと言ったもので、504は、ゲートウェイがバックエンドを待って待ちくたびれたものです。ログでこの3つを区別しないと、見当違いの場所を掘ることになります。
現場での姿
コネクションプールの設定が、よくある事故の場所です。クライアントはkeep-aliveで接続を再利用しようとするのに、サーバーや途中のロードバランサーのアイドルタイムアウトのほうが短いと、サーバーがちょうど閉じた接続に、クライアントがリクエストを送る競合が生まれます。症状は、断続的な接続リセットで、負荷と無関係に、一定の割合で現れます。クライアントのアイドルタイムアウトを、サーバーより短く設定するのが定石です。
続くクイズで確認すること
バージョンごとに何が解決されて何が残ったのか、冪等性がなぜリトライ設計の前提なのかを説明できるかを確認します。