Dev Study
← 境界の設計
レッスン 8 / 10·約 9 分·第14期 境界の設計

境界は、壁ではない — 越えてくるもの

リクエストがアプリケーションに届くまで
  1. ブラウザURL入力
  2. DNS名前→IP
  3. ルータ経路選択
  4. ロードバランサ振り分け
  5. Nginx受け口
  6. アプリ処理今ここ
  7. DB保存

線を引くと何が消えるかを見てきました。今回は逆に、線を引いても消えずに越えてくるものを見ます。ここでの「境界」は、変更の影響を閉じ込めるつもりで引いた線です。

まず結論

インタフェースが固定するのは、名前・引数・戻り値の形までです。相手がどれだけ速いかは書かれていません。だから呼ぶ側を1文字も変えなくても、線の向こうが遅くなるだけで応答が変わります。

TCPとIPの規格には、層をまたいで渡る値が明記されています。しかし自分たちが引いた線には、そういう文書がありません。そこが今回の焦点です。

? 読む前に予測

呼ぶ側のコードを変えず、依存先の応答だけを 50ms から 1000ms に遅くしました。呼ぶ側に同時6本を投げたとき、最後に返る要求はどうなるでしょうか。

当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。

抽象原理

インタフェースが固定していないもの

まず語を切ります。ここでの「壁」は分散編の壁時計とは無関係で、越えられない仕切りという比喩です。

前回まで、線を引くと何が消えるかを見てきました。今回は逆に、線を引いても消えず、越えてくるものを見ます。まず、インタフェースを決めたときに固定されたものを数えます。

  • 名前(どの手続きを呼ぶか)
  • 引数(何を渡すか)
  • 戻り値の形(何が返ってくるか)

ここまでです。「どれくらいで返るか」は、どこにも書かれていません。 型定義にも OpenAPI にも関数のシグネチャにも、その欄がありません。書く場所が無いのだから、相手の速さが変わっても定義書は変わらない。境界は形だけを固定して、それ以外を素通りさせます。

具体実物

規格は、何が越えるかを書いている

第2期で、TCPのチェックサムの計算範囲にIPアドレスが入っていることを見ました。原文(RFC 9293 §3.1)はこう書いています。

The checksum also covers a pseudo-header (Figure 2) conceptually prefixed to the TCP header. ... Including the pseudo-header in the checksum gives the TCP connection protection against misrouted segments. This information is carried in IP headers and is transferred across the TCP/network interface in the arguments or results of calls by the TCP implementation on the IP layer.

最後の一文です。規格自身が「その情報は TCP とネットワークのインタフェースを越えて渡される」と書いています。 実装の都合ではなく規定として書かれているわけです。しかも同じ §3.1 に The TCP checksum is never optional. とあり、送信側は MUST generate、受信側は MUST check です。

RFC 9293 は2022年8月の Standards Track で、STD 7、RFC 793 を廃止しています。§3.7.1 には TCP obtains MMS_R and MMS_S from the IP layer ともあり、上の層が下の層から値を取得すると明記されています。§3.4.1 の ISN 生成式は SHOULD として ISN = M + F(localip, localport, remoteip, remoteport, secretkey) を示します。

規格で分けられた層ですら、越えて渡る値の一覧が文書に書いてあるのです。

✓ ここまでの確認

TCPのチェックサムがIPアドレスを計算範囲に含むことについて、RFC 9293 §3.1 が書いているのはどれですか。

抽象原理

人が引いた線には、その文書が無い

第2期では「規格に定められた層でさえ完全には独立していない」ことを確かめました。今回はその先を問います。自分たちがモジュールやサービスの間に引いた線には、何が越えるかを書いた紙がありますか。

RFC 9293 を読めば、チェックサム・MSS・ISN の3件が越えることは節番号つきで読み取れます。しかし「注文サービス」と「在庫サービス」の間の線に、同じものを書いた文書は普通ありません。インタフェース定義は形しか書けないからです。

同じ「境界」でも、越えるものが文書化されているかが違う
境界を引く変更の影響を内側に閉じ込めるつもりで
誰が引いた線か
規格が引いた層RFC 9293 の TCP と IP
人が引いた線モジュール境界・サービス境界
越えるものが書いてある§3.1 / §3.7.1 / §3.4.1 の3件
書いた文書が無い定義書に速さの欄が無い
  • ・どちらも遮断は完全ではない。違うのは、何が越えるかを読める文書の有無
  • ・本教材で実測できたのは速さだけ。時刻は分散編『順序は与えられていない — 壁時計と、因果だけが分かる関係』が扱う
具体実物

呼ぶ側を1文字も変えずに、応答の桁が変わる

書いた紙が無いなら、動かして見ます。

環境: Docker Engine 28.4.0(Docker Desktop) / linux/arm64 / kernel 6.10.14-linuxkit / Node v24.21.0 / curl 8.22.0。ホストは Apple Silicon の macOS。すべて 127.0.0.1 の自前サーバで、各1回の計測です。

作ったのは2つ。依存先は常に {"ok":true} を返し、環境変数で遅延だけを変えます。呼ぶ側は依存先を1回呼んで返すだけで、同時に張れる接続を2本に制限してあります(new http.Agent({ maxSockets: 2 }))。

呼ぶ側のコードは A と B で1文字も違いません。

呼ぶ側へ同時6本を投げた結果(起動確認の行のみ省略。ほかは生出力)コマンド出力
########## A 依存先が速い(遅延 50ms) ##########
自己検査(依存先を直接1本): 0.065765s
req6 status=200 total=0.073756s
req3 status=200 total=0.076358s
req2 status=200 total=0.129318s
req1 status=200 total=0.130875s
req4 status=200 total=0.181281s
req5 status=200 total=0.181747s
 
########## B 依存先が遅い(遅延 1000ms) ##########
自己検査(依存先を直接1本): 1.018296s
req2 status=200 total=1.023848s
req5 status=200 total=1.026023s
req6 status=200 total=2.036487s
req1 status=200 total=2.037469s
req3 status=200 total=3.041255s
req4 status=200 total=3.041881s

A も B も、6本が2本ずつ3つの波に分かれています。 構造は同じで、違うのは波の間隔だけ。その間隔は依存先の遅延です。

最後に返った要求は 0.18秒から 3.04秒。呼ぶ側のコードも、受け取った要求も、返した JSON も変わっていません。書けるのは「桁が変わった」「波の間隔が依存先の遅延になった」まで。各1回の計測なので、倍率を法則としては扱えません。

✓ ここまでの確認

本教材の実測で、依存先を遅くしたときに呼ぶ側の応答が伸びた理由として、書ける範囲はどれですか。

⇄ 2つを見比べる
具体例 A

到着を増やした場合(第13期で見た)

1件あたりの仕事は増やさず、単位時間に届く要求の数だけを増やします。自分の側が混んで応答が伸びます。第13期『余力は、待ち時間の購入代金』の構造です。

何を動かしたか表
項目 変えたか
届く要求の数 変えた
1件あたりの処理 変えない
線の向こうの速さ 変えない
具体例 B

依存先を遅くした場合(今回の実測)

届く要求は A も B も6本で固定し、依存先の応答時間だけを 50ms から 1000ms にしました。呼ぶ側のコードは同一。最後に返った要求は 0.18秒から 3.04秒。各1回の計測です。

何を動かしたか表
項目 変えたか
届く要求の数 変えない(6本)
1件あたりの処理 変えない
線の向こうの速さ 変えた

どちらも「仕事は増えていないのに遅くなった」という見え方をします。切り分けるには、何を確かめればよいでしょうか。

グラフの形ではなく、「自分は今回、何を変えていないか」を先に数えてみてください。

この章のまとめ

  • インタフェースが固定するのは名前・引数・戻り値の形まで。相手の速さを書く欄が無い。
  • RFC 9293 §3.1 は、疑似ヘッダの情報が transferred across the TCP/network interface だと明記。チェックサムは never optional。
  • 規格が引いた層には、越えるものを書いた文書がある。人が引いた線には無い。 これが今回の差分。
  • 実測(各1回)では、呼ぶ側を1文字も変えず依存先を 50ms→1000ms にしただけで、最後に返る要求が 0.18秒→3.04秒。上限2本は意図的な設定で、既定は Infinity。
  • 「仕事は増えていないのに遅い」でも、到着を増やした場合(第13期)と依存先を遅くした場合は別。まず何を変えていないかを数える。

自分の現場で確かめる

読んだだけでは身につきません。手元の環境で実際に見てみてください。

環境:

理解度チェック

読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。

問題 1 / 4正解: 0

「インタフェースを固定したので、向こう側の実装が変わっても呼ぶ側には影響しない」という説明の評価はどれですか。

用語集(3語)
境界(この教材での言い方)
変更の影響を内側に閉じ込めるつもりで引いた線。関数・モジュール・サービスのどれでも同じで、形だけを固定して他を素通りさせる。
疑似ヘッダ (pseudo header)
TCPのチェックサム計算のために組み立てる、送信元・宛先IPアドレスを含む仮想的なヘッダ。RFC 9293 §3.1 が規定し、IPv4 で96ビット、IPv6 で320ビット。
maxSockets
Node.js の `http.Agent` が、同一の相手に同時に持てるソケット数の上限。公式の既定値は `Infinity` で、超過分は保留の列に入ると規定されている。有限にすると、相手の遅さが呼ぶ側の応答に何倍にも現れる経路になる。