1. 質問と背景
ロングリブドクライアントがNAT、ロードバランサー、サーバーを経由して接続しています。ネットワークが切断された後も、OSはソケットを確立状態(established)として報告し続けます。チーム内では、TCP Keepaliveだけで十分なのか、それともプロトコルにping/pongメッセージを追加すべきかで議論が起きています。両方のメカニズムを比較し、検知、タイムアウト、プロキシ、および再接続の挙動を提案してください。単にネットワークインターフェースに到達可能かだけでなく、セッションがリクエストを処理できる状態にあるかをビジネス側が把握する必要があると仮定します。
2. 面接官が見ているポイント
- トランスポート層のプロービングとアプリケーションの可用性プロービングを区別できているか。
- TCP Keepaliveはカーネルタイマーを使用するため、デフォルト値やミドルボックスの挙動がビジネスSLAにはならないことを理解しているか。
- 誤検知やストームを発生させずに、有界なハートビート、タイムアウト、クローズ、再接続のステートマシンを設計できるか。
- エンドツーエンドの設計において、NAT、プロキシのアイドルタイムアウト、モバイルネットワーク、サーバー負荷を考慮に入れているか。
3. 最初に確認すべき明確化のための質問
- 検知対象はネットワークパス、TCPピアプロセス、アプリケーションセッションの可用性のどれですか?
- NAT、ゲートウェイ、ロードバランサーにはどのアイドルタイムアウトが適用されていますか?
- この接続でやり取りされるのは再試行可能な読み取りですか、それとも確認(ACK)が必要なコマンドやトランザクションですか?
- クライアント数はどれくらいで、許容される追加帯域幅はどの程度ですか?また同時に実行可能な再接続数はいくつですか?
4. 30秒で答えるフレームワーク
TCP Keepaliveはカーネルによって送信され、主にアイドルのハーフオープンTCPピアや切断されたパスを検知します。アプリケーションハートビートはプロトコルによって定義され、ピアアプリケーションが現在も読み取り、書き込み、ビジネス応答の返却が可能であることを検証できます。私ならhealthy、suspect、closed、backoffの各状態を定義し、最も短いミドルボックスのアイドルタイムアウトを基準にハートビート間隔を設定します。Keepaliveはトランスポート層のフォールバックであり、ビジネスSLAを担うのはアプリケーションハートビートです。タイムアウト時には、古いソケットを閉じ、ジッター付き指数バックオフで再接続し、同時接続数を制限します。
5. ステップバイステップの詳細な回答
ステップ1: 各メカニズムが何を監視しているかを定義する
TCP Keepaliveはソケット層で動作します。Linuxの tcp(7) はアイドル期間、プローブ間隔、プローブ回数を公開しており、ACKはTCPスタックが応答できることのみを証明します。アプリケーションが認証されているか、リースが有効か、リクエストを処理できるかは証明しません。Keepaliveは、サイレントなハーフオープン接続を検出し、カーネルリソースを解放するのに有用です。
アプリケーションハートビートはプロトコルメッセージ(セッションやケーパビリティデータを運ぶ ping と、それに対する pong やステートフルな応答など)です。これは、TCPでは検知できないブロックされたイベントループ、期限切れのリース、無効な認証、過負荷状態のサービスを検出できます。引き換えにアプリケーションのCPU、帯域幅、接続キャパシティを消費します。
ステップ2: エンドツーエンドのタイムバジェットからパラメータを設定する
最も短いミドルボックスのアイドルタイムアウト T_idle を特定し、ジッターマージンを考慮してそれより短いハートビート間隔(例えば T_hb <= T_idle / 2)を選択します。アプリケーションの応答期限はビジネスで許容される切断時間未満に抑え、1パケットのドロップで正常なセッションが破棄されないよう、オフライン判定を下す前に N 回の連続失敗を要求します。TCP Keepaliveはアプリケーションの無通信期間におけるトランスポートのフォールバックとしてより長いアイドル期間を設定できますが、アプリケーションのSLAを代替することはできません。
ステップ3: 状態とアクションを設計する
接続後、healthy に入ります。ハートビートを送信すると suspect に遷移し、期限内にアプリケーション応答があれば healthy に戻ります。連続して失敗した場合はソケットを閉じて backoff に遷移します。ランダムジッターと上限(cap)を設けた指数関数的増加を使用し、オフライン中やページが非表示の間は接続試行を一時停止し、再開前にヘルスチェックを実行します。2つの接続が1つのコマンドストリームを同時に消費しないよう、まず古いソケットを閉じます。
ステップ4: メッセージセマンティクスに応じたリカバリを行う
通知はドロップされる可能性があり、再接続後に再サブスクライブします。書き込みコマンドはハートビートの確認応答に依存してはならず、冪等性キーやシーケンス番号を付与し、明示的な確認応答を受け取り、最新の連続した確認ポイントを記録します。再接続後は、カーソルからリプレイするか、状態をクエリします。実行結果が不確かな場合は、サーバーの状態を読み取った上で再試行すべきかを判断します。
ステップ5: ミドルボックスと運用シグナルを検証する
パケットキャプチャや接続ログを使用して、ハートビートがNAT、プロキシ、ロードバランサーを通過していることを確認します。ハートビートRTT、タイムアウト率、失敗したKeepaliveプローブ数、接続継続時間、再接続試行回数、バックオフ時間、同時接続のピークを記録します。ケーブル抜去、NATリクレイム、サーバーイベントループのブロック、認証期限切れ、一斉切断などの障害訓練を実施します。TCPの ESTABLISHED 状態だけではビジネス上の可用性は証明されません。
6. 質の高い回答例
私は2つのメカニズムをレイヤーごとに切り分けて考えます。TCP KeepaliveはサイレントなハーフオープンTCP接続を検出できるカーネルプローブですが、ACKが返ってもアプリケーションがリクエストを処理できるとは限りません。アプリケーションハートビートはプロトコル、認証、リースの健全性を検証できるため、ビジネス判断の主体となります。私はNATやロードバランサーの最短アイドルタイムアウトを測定し、その約半分にハートビートを設定した上で、応答期限と連続失敗閾値を設けます。接続ステートマシンはhealthy、suspect、closed、ジッター付きbackoffの間を遷移し、タイムアウト時には古いソケットを閉じてから再接続します。復旧後、通知は再サブスクライブし、コマンドは冪等性キーと確認カーソルを使用します。Keepaliveはアプリケーションが無通信時のフォールバックであり、ハートビートや無制限のリトライループを置き換えるものではありません。
7. よくある間違い
- TCP ACKをビジネス健全性の証明として扱う → アプリケーションスレッドがスタックしていてもカーネルが応答する可能性がある → アプリケーションのリクエスト・レスポンス型ハートビートを使用する。
- システムのKeepaliveデフォルト値をそのまま適用する → アイドル期間がプロキシのタイムアウトを超える可能性がある → エンドツーエンドのタイムバジェットに基づいて設定する。
- ハートビートの1回のエラーで即座に再接続する → 短時間のパケットロスでストームが発生する → 期限、連続失敗閾値、ジッター付きバックオフを使用する。
- クライアント側のハートビートのみをチューニングする → NATやロードバランサーがより早くセッションを解放する可能性がある → すべてのミドルボックスを調査する。
- 再接続後に書き込みコマンドを盲目的にリプレイする → 二重請求やリソースの重複作成が発生する → 冪等性キー、確認ポイント、状態読み取りを使用する。
8. フォローアップ質問と回答
フォローアップ1: アプリケーションハートビートの方が強力であるなら、なぜTCP Keepaliveを残すのですか?
アプリケーションが長時間無通信になる場合や、アプリケーションのプロトコルコード自体に不具合がある場合があります。Keepaliveはその無通信期間中に停止したトランスポートを検出し、ソケットを解放できます。これは下位レイヤーのフォールバックであり、ビジネス上の確認応答ではありません。
フォローアップ2: ハートビート間隔はできる限り短くすべきですか?
いいえ。間隔を短くすると障害検知は早くなりますが、CPU、帯域幅、モバイルバッテリーの消費が増加し、サーバーの同時接続負荷が高まります。まずはミドルボックスのアイドルバジェットを満たし、障害訓練を行って誤検知率や検知遅延を測定した上で、接続タイプごとにティアを分けるべきです。
フォローアップ3: フリート全体の一斉再起動後の再接続ストームをどのように防ぎますか?
クライアントはランダムジッターを伴う指数バックオフを使用します。サーバー側ではテナントや接続タイプごとにレートリミットを適用し、リトライ待機時間を返すこともあります。オフラインのクライアントは接続試行を停止し、接続性が回復した際に段階的にバッチで再接続を行い、ピークをモニタリングできるようにします。