こんにちは。ネットワークの深淵を愛するインフラエンジニアの皆さん。
日頃、私たちはWeb APIの設計やクラウドのコンテナオーケストレーション、あるいはモダンなマイクロサービスアーキテクチャの構築において、レイヤー7(アプリケーション層)の美しさやRESTfulの作法に夢中になりがちです。JSONのスキーマ設計や、HTTP/2やHTTP/3のストリーム多重化に一喜一憂する。それ自体はエンジニアとして非常に楽しい作業です。
しかし、ふと立ち止まって考えてみてください。その私たちが書き上げた数キロバイトのJSONペイロードは、最終的にどこを流れているでしょうか?
そう、物理的な銅線やガラスのファイバーの上を、泥臭い「電気信号」や「光の点滅」として駆け抜けているのです。現代のデータセンターやオフィスでは、フルデュプレックス(全二重)のスイッチド・イーサネットが当たり前になり、フレームの衝突(コリジョン)なんて言葉は、もはやCCNPやCCIEの試験勉強の暗記項目でしか見かけなくなったかもしれません。
しかし、CSMA/CD(Carrier Sense Multiple Access with Collision Detection)の思想とメカニズムは、分散システムにおける「競合制御」の原点であり、イーサネットの歴史そのものです。今回は、この古典にして最強のメディアアクセス制御方式について、パケットの鼓動を感じながら、現場のシニアの視点で紐解いていきましょう。
—
1. なぜ今、CSMA/CDなのか? ~半二重の物理世界と衝突の物理法則~
現代のネットワークエンジニアにとって、ハブ(Hub)は「歴史的遺物」であり、スイッチ(Switch)やルーターが世界の主役です。スイッチは内部にメモリとASICを持ち、各ポートが独立したコリジョンドメイン(衝突ドメイン)を形成するため、基本的にフレームが衝突することはありません。
しかし、無線LAN(Wi-Fi)におけるCSMA/CAや、分散型の排他制御アルゴリズムの根底には、このCSMA/CDのDNAが脈々と受け継がれています。まずは、CSMA/CDがどのような世界でどう振る舞うのか、その基本原則を分解してみましょう。
CSMA/CDの3つの要素
1. CS (Carrier Sense:キャリアセンス):
送信を行いたいノードは、まずメディア(ケーブル)上に他のノードの信号(キャリア)が流れていないか「聴聞」します。静かであれば送信権ありとみなします。
2. MA (Multiple Access:多重アクセス):
1つの共有媒体(バス型ネットワークなど)に多数のノードが接続されており、どのノードも平等にアクセス権を狙っています。中央集権的な調停役はいません。
3. CD (Collision Detection:衝突検出):
運悪く、2台のノードが同時にキャリアセンスを行い「静かだ」と判断して送信を開始した場合、ケーブル上で信号同士が激突します。送信しながらも自分自身の受信回路で「送信した信号と違う波形が返ってきていないか」を監視し、衝突をリアルタイムで検知します。
—
2. 衝突発生からバックオフ制御までの通信フロー
もしあなたが共有媒体上でデータを送ろうとし、運悪く他のノードとタイミングが被って衝突が発生したとき、ネットワーク上では何が起きているのでしょうか。そのドラマチックなシーケンスを追ってみましょう。
[Node A] [Shared Medium] [Node B]
| | |
|--- 1. キャリアセンス (アイドル) ---->| |
|--- 2. フレーム送信開始 ---------->| |
| |<--- 2. フレーム送信開始 --------|
| | |
| [ 3. 信号の衝突発生! ] |
| | |
|--- 4. ジャム信号(Jam)送信 ------->| |
| |<--- 4. ジャム信号(Jam)送信 -----|
| | |
|--- 5. バイナリ指数バックオフ算定 ->| |
| (待機タイマー: 例 2スロット) | |
|--- 6. タイマー満了後再送トライ --->| |
衝突検出時の詳細なステップ
1. ジャム信号(Jam Signal)の送出:
衝突を検知したノードは、ネットワーク上の他のすべてのノードに「今、衝突が起きたぞ!」と知らせるため、意図的に32ビット(あるいは48ビット)の特定パターンのノイズ(ジャム信号)を送出します。これにより、ネットワーク全体の全ノードが衝突を確実に認識します。
2. バイナリ指数バックオフ(Binary Exponential Backoff)アルゴリズム:
衝突を起こしたノードたちは、即座に再送信を試みると再び衝突(再衝突の嵐)を起こしてしまいます。これを防ぐため、ランダムな時間だけ待機(バックオフ)します。
待機時間のスロット数 $k$ は、以下のアルゴリズムで決定されます。
- 試行回数 $c$(1回目、2回目…)に対し、 $0$ から $2^c – 1$ までの整数からランダムに $k$ を選びます。
- ただし、$c$ が10を超えた場合は $c = 10$ で頭打ちになります(最大1023スロット)。
- 待機時間は「スロットタイム(10Mbpsイーサネットの場合は512ビット分の送信時間にあたる51.2マイクロ秒)」の整数倍となります。
もし再び衝突が発生した場合、試行回数 $c$ がインクリメントされ、待機時間の範囲が倍々(指数関数的)に広がっていきます。これが「バイナリ指数バックオフ」と呼ばれる所以です。
—
3. 実務的な視点:現代のインフラにおけるコリジョンとトラブルシュート
「全二重のスイッチド・ネットワークの時代に、なぜこんな昔話を?」と思われるかもしれませんが、現場の現場で遭遇する「L1/L2の幽霊」のようなトラブルにおいて、この知識が決定的な手掛かりになります。
現場で遭遇する「デュプレックス・ミスマッチ」の悲劇
現代でも時折、古いレガシー機器(産業用PC、古いPLC、特殊な測定器など)を最新のL2スイッチに接続する際や、オートネゴシエーションの不具合によって発生するのがデュプレックス・ミスマッチ(Duplex Mismatch)です。
- スイッチ側: 全二重(Full-Duplex)で動作
- 端末側: 半二重(Half-Duplex)で強制設定(あるいはネゴシエーション失敗)
この状態になると、スイッチ側は相手が送信中でもおかまいなしにデータを送りつけますが、端末側は「キャリアセンスと衝突検出」の世界で生きているため、スイッチからの送信を一方的に「自分の送信に対する衝突(コリジョン)」と誤認するか、あるいはCRCエラーの山を生み出します。
現場でのデバッグ手順(Linuxホストの例)
もしサーバーやルーターのインターフェースで、パケットロスや奇妙なパフォーマンス低下に悩んだら、ipコマンドやethtoolを使って物理層・リンク層の状態を確認してください。
# インターフェースの物理ステータスとリンクモードを確認する
$ ethtool eth0
Settings for eth0:
Supported ports: [ TP MII ]
Supported link modes: 10baseT/Half 10baseT/Full
100baseT/Half 100baseT/Full
1000baseT/Full
Supported pause frame use: No
Supports auto-negotiation: Yes
Advertised link modes: 10baseT/Half 10baseT/Full
100baseT/Half 100baseT/Full
1000baseT/Full
Advertised pause frame use: No
Speed: 100Mb/s
Duplex: Full # <-- ここが重要!対向機器と一致しているか確認
Port: MII
PHYAD: 1
Transmitter rect: 0
Auto-negotiation: on
Link detected: yes
もし、ここで Duplex: Half なのにスイッチ側が Full になっている場合、トラフィックが増えた瞬間にパケットロス率が跳ね上がり、TCPの再送制御(Retransmission)が頻発してWeb APIの応答速度が数秒〜数十秒に悪化するという、非常に厄介な現象を引き起こします。
—
4. Pythonでシミュレートする「バックオフアルゴリズム」
理論をより深く理解するために、CSMA/CDの心臓部である「バイナリ指数バックオフ」の振る舞いをPythonの簡単なコードでシミュレートしてみましょう。衝突が連続したときに、待機スロットがどのように広がっていくのかを直感的に把握できます。
import random
import time
def binary_exponential_backoff(attempt_count, slot_time_sec=0.0000512):
"""
バイナリ指数バックオフによる待機時間を計算する関数
:param attempt_count: 衝突回数 (1回目、2回目...)
:param slot_time_sec: 1スロットの時間 (10Mbpsイーサネットの場合は約51.2マイクロ秒)
:return: 待機時間 (秒)
"""
# 試行回数は最大10回までに制限 (RFC 894 / IEEE 802.3準拠の概念)
c = min(attempt_count, 10)
# 0 から (2^c - 1) までのランダムなスロット数を選択
max_slots = (2 ** c) - 1
k = random.randint(0, max_slots)
backoff_time = k * slot_time_sec
return k, backoff_time
# シミュレーション実行例
print("=== バイナリ指数バックオフ・シミュレーション ===")
attempts = 5
for attempt in range(1, attempts + 1):
slots, wait_time = binary_exponential_backoff(attempt)
print(f"衝突回数: {attempt}回目 | 選択スロット数: {slots:4d} | 待機時間: {wait_time*1000:.3f} ms")
# 実際のバックオフ待機を模倣(デモ用に短縮)
# time.sleep(wait_time)
このコードを実行すると、衝突回数が増えるにつれて max_slots が 1 -> 3 -> 7 -> 15 -> 31 と爆発的に増え、ランダムな待ち時間が長くなっていく様子がわかります。これが、過負荷になったネットワークにおいてトラフィックの雪崩(トランスミッション・ストーム)を防ぐための美しい数学的調停メカニズムです。
—
5. まとめ
今回は、イーサネットの基礎であり、分散制御の原点であるCSMA/CDのメカニズムについて解説しました。
- キャリアセンスと衝突検出は、中央集権的な制御なしにノード同士が協調するための物理層のアプローチ。
- 衝突が発生したときはジャム信号で全域に通知し、バイナリ指数バックオフでランダムなディレイを入れて再送を分散させる。
- 現代のスイッチド環境では滅多に意識しませんが、デュプレックス・ミスマッチなどの現場のトラブルシューティングにおいて、このレイヤー1/L2の挙動を知っているかどうかがエンジニアとしての真価を問うポイントになります。
私たちが日々当たり前のように叩いているAPIの裏側には、こうした何十年も前に洗練されたパケットの礼儀作法が息づいています。トラブルシューティングの現場で行き詰まったときは、ぜひレイヤーの原点に立ち返ってみてください。そこには必ず、パケットたちの声なきメッセージが隠されているはずです。
コメント