HexVMのエンジニアリング実践

クラウドMacで低品質な回線を安全に再現する手順

クラウドMacで低品質な回線を安全に再現する手順

iOSクライアントをリモートでデバッグするとき、最も再現しにくいのは完全なオフライン状態ではなく、「接続はできるが応答が極端に遅い」状態です。ハンドシェイクの遅延、アップロード速度の低下、一部リクエストの損失によって、二重送信、無限リトライ、画面の長時間待機などが発生します。HexVMのクラウドMacでこの種のテストを行う場合、最初に帯域を制限するのではなく、アプリのテスト通信をSSHやVNCなどの管理通信から分離することが最優先です。対象範囲の広すぎるルールを設定すると、自分の操作経路まで遮断しかねません。

合否判定できる低品質回線シナリオを定義する

「ネットワークが悪い」といった曖昧な条件は使わないでください。再現できず、修正が有効だったかどうかも判断できません。まずシナリオを固定パラメータとして記述し、それぞれについてクライアントに期待する動作を定義します。

シナリオ 下り・上りの制限 追加遅延 パケット損失率 主な確認項目
ベースライン 制限なし 0 ms 0 通常リクエストの所要時間と成功率
高遅延 8 Mbit/s 120 ms 0 読み込み状態、キャンセル、タイムアウト
低帯域 1 Mbit/s 80 ms 0 アップロード進捗とバックグラウンド移行
不安定な回線 4 Mbit/s 100 ms 2% リトライ上限と冪等性の処理

ここで指定する値はテスト入力であり、ノードのネットワーク品質を示すものではありません。APIのタイムアウト、ファイルサイズ、ユーザーが利用するネットワークに応じてチームで調整してください。ただし、同じ回帰テストの実行中はパラメータを統一する必要があります。

低品質回線テストの目的は、最終的にリクエストが成功することを証明することではありません。失敗時にクライアントが速やかに待機を終了し、理解しやすい状態を表示し、重複データを生成しないことを確認するためのテストです。

帯域制限のないベースラインを取得する

トラフィック制御ルールを一切読み込んでいない状態で、DNS、ルーティング、テスト先の応答を記録します。テスト先はチーム自身が管理し、本番データを書き込まないヘルスチェック用のパスを用意してください。

export TEST_URL="https://test-endpoint.invalid/health"
scutil --dns | grep 'nameserver\[[0-9]*\]'
route -n get default
networkQuality
curl -sS -o /dev/null \
  -w 'code=%{http_code} connect=%{time_connect} tls=%{time_appconnect} total=%{time_total}
' \
  "$TEST_URL"

例示したドメインは、実際に管理しているテスト用アドレスへ置き換える必要があります。少なくとも3回リクエストし、その中央値を記録してください。最良値だけを残してはいけません。ベースラインの時点でDNS障害、証明書エラー、経路異常が発生している場合は、先に基礎的な問題を解決します。リトライ回数を増やして隠してはいけません。

テスト先が複数のIPアドレスで提供されているかどうかも確認します。1つのIPだけを制限すると、別のアドレスへ名前解決されたリクエストには制限が適用されず、同じテストでも速度が安定しません。低品質回線テスト専用の固定された接続先を用意し、実行前に名前解決の結果を保存しておく方が確実です。

テスト先への通信だけを制御する

macOSでは、dnctlで帯域と遅延を制御するパイプを作成し、pfで一致する通信をそのパイプへ送ります。ルールでは対象IP、プロトコル、ポートをすべて限定し、すべての外向き接続に一致するワイルドカード条件を指定してはいけません。

次のスクリプトは、8 Mbit/s、遅延120 ms、パケット損失2%のテスト用パイプを作成します。TARGET_IPには、管理下にあるテスト先の固定アドレスを指定してください。

set -euo pipefail

TARGET_IP="${TARGET_IP:?set TARGET_IP first}"
PIPE_ID=310
ANCHOR="com.apple/hexvm-nettest"

cleanup() {
  sudo pfctl -a "$ANCHOR" -F all >/dev/null 2>&1 || true
  sudo dnctl delete "$PIPE_ID" >/dev/null 2>&1 || true
}

trap cleanup EXIT INT TERM

sudo dnctl pipe "$PIPE_ID" config bw 8Mbit/s delay 120 plr 0.02
printf 'dummynet out quick proto tcp from any to %s port 443 pipe %s
' \
  "$TARGET_IP" "$PIPE_ID" |
  sudo pfctl -a "$ANCHOR" -f -

sudo pfctl -E >/dev/null
sudo pfctl -a "$ANCHOR" -sr -v

curl -sS -o /dev/null \
  -w 'code=%{http_code} connect=%{time_connect} total=%{time_total}
' \
  "$TEST_URL"

最初に読み取り専用のヘルスチェックでルールが適用されていることを確認し、その後でデータを書き込む業務テストを実行します。クリーンアップのためにpfctl -dを実行してはいけません。マシン上で別のファイアウォールルールが使われている可能性があるためです。このスクリプトは自身のanchorだけを空にし、自身のpipeだけを削除するため、並行して動作しているタスクへの影響を避けられます。

リモートセッションの巻き込みを防ぐ

実行前に2つ目の管理用ターミナルを開いたままにし、ルールに管理ポート、デフォルトゲートウェイ、任意の宛先に一致するワイルドカードが含まれていないことを確認します。テスト先と管理接続が同じIPを共有している場合は、ルールの順序に期待するのではなく、独立したテスト用接続先を用意してください。

クライアントの動作を自動化されたアサーションにする

低品質回線の合否判定では、リクエストが「返ってきたか」だけを確認してはいけません。テストコードには、試行回数、最終的なエラー種別、総待機時間、リクエスト識別子を記録します。書き込み処理では、サーバーとクライアントの両方で安定した冪等キーを使用してください。タイムアウト後は、クライアントが先に処理結果を照会し、その結果を基にリトライするかどうかを判断します。

ネットワーク層を観測可能なラッパーに置き換え、次の境界条件を確認できます。

  1. 1回の接続がしきい値を超えたらキャンセルでき、無期限に待機しない。
  2. リトライ回数に上限があり、間隔にバックオフが含まれ、リクエストストームを引き起こさない。
  3. ユーザーが明示的にキャンセルした後、バックグラウンドタスクが密かに再送しない。
  4. アップロードが中断された場合は明確な状態を維持し、失敗を完了として表示しない。
  5. ネットワーク復旧後は操作を継続できるが、レコードを重複作成しない。

シミュレーターテストではデバイス識別子を明示して実行し、マシン上に偶然存在するデバイス名へ依存しないようにします。

export SIMULATOR_UDID="replace-with-booted-simulator-udid"

xcodebuild test \
  -scheme NetworkBehaviorTests \
  -destination "platform=iOS Simulator,id=${SIMULATOR_UDID}" \
  -resultBundlePath build/NetworkBehavior.xcresult

ネットワークシナリオごとに結果バンドルを分けて保存し、シナリオのパラメータをテストログに記録します。これにより、失敗記録から「どの遅延と損失率で失敗したか」を特定できます。再現できない赤いステータスだけが残ることもありません。

ルールを削除して回帰テストを完了する

スクリプトは正常終了時または割り込みシグナルの受信時にクリーンアップを実行します。ただし、リモートセッションが異常終了した場合は、手動での確認が必要です。再接続後に次を実行します。

sudo pfctl -a com.apple/hexvm-nettest -sr
sudo dnctl list
curl -sS -o /dev/null \
  -w 'code=%{http_code} connect=%{time_connect} total=%{time_total}
' \
  "$TEST_URL"

専用anchorは空で、一覧に番号310のpipeが残っていない状態でなければなりません。その後、ベースラインのリクエストを再実行し、接続時間と総所要時間がテスト前の正常範囲へ戻っていることを確認します。

最後に、トラフィック制御を適用しない状態でもう一度回帰テストを行います。低品質回線向けの修正によって、通常のネットワーク環境でのリクエスト順序、キャッシュヒット、画面の応答性が変わっていないかを重点的に確認してください。シナリオのパラメータ、クライアントのバージョン、テスト先のバージョン、結果バンドルのパス、クリーンアップ結果を同じ実行記録にまとめます。これにより、低品質回線テストは一度限りの手動デモではなく、監査可能で、再現性があり、安全に終了できるエンジニアリングプロセスになります。

よくある質問

Mac全体の送信通信を制限してはいけないのはなぜですか?

SSH、VNC、依存関係の取得まで同時に劣化し、管理接続を失う可能性があるためです。テスト先のIP、プロトコル、ポートだけを条件にします。

一時的な回線制御が解除されたことを確認する方法は?

専用pfアンカーを空にし、対応するdnctl pipeを削除します。アンカーのルールが空であることを確認し、通常時の計測を再実行します。

弱い回線で最低限確認すべきアプリの動作は何ですか?

接続タイムアウト、回数制限付き再試行、キャンセル、オフライン表示、二重送信防止、回線復旧後の正常な再開を確認します。

専用Apple Silicon

次のビルドには専用クラウドMacを選択

ワークロードに合わせてMac miniの機種、利用期間、リージョンを選択できます。各注文には専用の物理ノードが割り当てられ、実際の利用可否はコンソールからリアルタイムで確認できます。

クラウドMacプランを選択