データ転送におけるボトルネックの例と対処

システムリプレースなどで避けて通れないデータのコピーですが、基本的な解決策は「転送するべきデータ容量」と「制限時間」に見合ったインフラの増強になります。但し、廃止が予定されている現在のシステムに投資を行うことは現実的には厳しいことが想定されます。このため、理論的な限界値を把握し、可能なまで限界値に引き上げるための各種チューニングが必要になるケースが多々あります。1GbpsのWANが必要な要件を100MbpsのWANで満たすことは「ストレージスナップショットの差分同期」や「世代ごとの段階的なコピー」、「データ重複排除」、「LTOによる物理搬送」など別な手段が取れる場合を除いて不可能です。本稿はrsyncやrobocopyなどの同期コマンド(コピーコマンド等)を使用する前提で記載しています。

データ転送におけるハードウェアの主なボトルネックはおおよそ以下の通り。
1. ネットワーク帯域幅(帯域不足)
2. レイテンシ(遅延)
3. TCPウィンドウサイズ(バッファサイズ)が小さい
4. パケットロスの増加(送信ホストと受信ホストで3.の値が異なると増加)
5. CPU リソースの枯渇
6. ディスク I/O の性能不足
7. メモリの枯渇

1. 2. 3. 4.に関する説明
TCPスループットの最大理論値
スループット(bps) = TCPウィンドウサイズ (bit) / RTT (秒)
ちょっとだけわかりやすく
[データ帯域(bit/s)] = [TCP受信ウィンドウサイズ(bit)]/[往復遅延時間(sec)]

1セッションのスループット(bps) = TCPウィンドウサイズ(KB) * 8 / RTT(ms)
Mbpsに変換したいとき、
((TCPウィンドウサイズ(bit) * 8)/ (RTT{ms}/1000)) / 1000000
計算例
TCPウィンドウサイズ = 1MB 
1MBは1*1024*1024*8 = 8,388,608 bits
RTT=100ms(ping応答の速度)
100msは100/1000 = 0.1秒
8,388,608 /0.1=83,886,080(bps)
83,886,080(bps)/1024/1024=80Mbps

おおよそ1セッション辺り80Mbpsの通信速度の環境となる。
Mbps単位としたのは、通信規格がMbpsやGbpsの単位で提供されることが多いことを考慮しました。
ディスクデータの転送は80Mbpsを8で割った数値(10㎆/s)が転送能力です。10MB(1秒)が最大論理値になります。(本来はMiB、GiB、TiBに変換することが望ましいです。)
WAN回線が10Mbpsの場合、上記の通り設定しても無駄になります。ドロップパケットが増えて再送遅延の原因になる可能性があります。
「回線帯域」 < 「1セッション辺りのスループット」のため
受信側が固定されている場合、送信側は自動調整して受信側に合わせる"はず"です。受信側で受け取れずにあふれたパケットはTCPでは再送されます。OSのデフォルトでは受信ウィンドウは送信ウィンドウより大きくなっています。
※最後にExcelファイルの計算機を添付してあります。

ネットワークの最適化
1.RTT(往復遅延時間)の確認
Windows:pingコマンドで「時間=の値」
Redhat Linux:pingコマンドの「time=の値」
Solaris:ping -s (-sオプションをつけないと「Time=の値」が表示されない)

2.TCPウィンドウサイズの確認
当然ですが、データ転送のことだけ考えると送信ホスト(send)と受信ホスト(rev)のウィンドウサイズは同じであることが再送や遅延の低下になりますが、データ転送以外も様々な通信は発生するため、与えられたスケジュールで間に合わない場合、何度か設定変更&テストを実施することが望ましいと思われます。

Windows cmdコマンド
netsh int tcp show global
受信ウィンドウ自動チューニングレベルがnormal または highlyrestricted だとWindowsがTCPウィンドウサイズを自動調整します。
disabled     自動調整オフ。手動で固定サイズにする場合に使用
highlyrestricted 最小限の自動調整
restricted     制限付きで調整
normal         デフォルト
experimental     最大限調整(大概再送遅延増えるので非推奨)
・PowerShell(ローカルの管理者権限がないとエラーになる)
Get-NetTCPSetting | Select-Object SettingName, MinRto, InitialCongestionWindow, CongestionProvider, ScalingHeuristics, ReceiveWindowAutoTuning

Redhat Linux コマンド
受信:sysctl net.ipv4.tcp_rmem
送信:sysctl net.ipv4.tcp_wmem
例:値は左から最小受信バッファサイズ、初期(デフォルト)受信バッファサイズ(自動拡張の上限)です。
net.ipv4.tcp_rmem = 8192 87380 16777216
net.ipv4.tcp_wmem = 8192 65536 16777216

Redhat Linux9のsysctl net.ipv4.tcp_rmemとsysctl net.ipv4.tcp_wmemの出力例 ※単位はバイト

Solaris11のipadm show-prop -p recv_buf tcpとipadm show-prop -p send_buf tcpの出力例 ※単位はバイト

Solaris11 コマンド
受信(recv):ipadm show-prop -p recv_buf tcp
送信(send):ipadm show-prop -p send_buf tcp
PROTO     対象のプロトコル(この場合は TCP)
PROPERTY 表示しているプロパティ名(recv_buf)
PERM     パーミッション(rw = 読み書き可能)
CURRENT     現在の値(実行中のシステムにおける値)
PERSISTENT 永続設定(再起動後も適用される値。-- は未設定を意味)
DEFAULT     デフォルトの初期値
POSSIBLE 設定可能な値の範囲

TCPウィンドウサイズの手動設定
基本は自動でいいはずですが、小容量ファイルを大量に送受信(ウィンドウサイズを小さく)や大容量ファイルを送受信(ウィンドウサイズを大きく)するケースでは確かに効果はあります。

・Windowsは以下のレジストリを変更します。
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters
値の追加例:TcpWindowSize(DWORD 値)を追加 単位:バイト 例:65535(≒ 64KB)
再起動時も消えまでので、初期値に戻したい場合には「TcpWindowSize」自体を削除してください。
※レジストリを修正する場合はバックアップを推奨します

・Redhat linux コマンド (管理者権限必須)
sysctl -w net.ipv4.tcp_rmem="**** ***** ********"
sysctl -w net.ipv4.tcp_wmem="**** ***** ********"
***は任意の数値になります。
左から最小受信バッファサイズ、初期(デフォルト)受信バッファサイズ(自動拡張の上限)です。
ちなみに上記のコマンド結果は再起動時に消えてデフォルトに戻ります。

・Solaris11 コマンド (管理者権限必須)
ipadm set-prop -p recv_buf=****** tcp
ipadm set-prop -p send_buf=****** tcp
***は任意の数値になります。
デフォルトに戻すコマンドは以下の通り
ipadm reset-prop -p recv_buf tcp
ipadm reset-prop -p send_buf tcp

設定目安(参考値として参照ください)
回線帯域 RTT 推奨ウィンドウサイズ(最小)
100Mbps 10ms   125 KB
100Mbps 100ms   1.25 MB
1Gbps     10ms   1.25 MB
1Gbps     100ms   12.5 MB
10Gbps     50ms   62.5 MB

5.CPU
リソースの枯渇
十分なネットワーク帯域がない場合、もしくはあったとしてもCPUの高負荷のため想定よりデータ転送時間がかかる場合があります。
・ソフトウェアによるプロトコル処理負荷
TCP/IPスタックや暗号化(TLS/SSL)や圧縮処理がソフトウェア(CPU)で行われる場合にCPUのオーバーヘッドが発生しやすいです。但しネットワーク機器で暗号化を実施している環境でも、暗号化によりパケット量は増加するので暗号化強度によりますが10%~15%程度転送時間は非暗号に比べると超過します。また、多くの小パケットを処理する場合もCPUが高負荷になりやすい(数KBファイルを1000万ファイルなど)傾向があります。
・割り込み過多(interrupt storms)
高速ネットワーク環境で大量のパケットが届くと、CPUがNIC(ネットワークカード)からの割り込みで過負荷状態に陥ります。特に古いNICやドライバがマルチキュー未対応だと、CPUコア1つに負荷が集中して転送効率が落ち込みます。
2025年現在ではマルチキュー非対応ドライバはまずないかと思いますが、NICドライバが古い場合には最新のドライバに更新しておくことをお勧めします。
・パケット処理の非最適化
TSO(TCP Segmentation Offload)や LRO(Large Receive Offload) が無効の場合
TSOが無効な場合、TCPセグメンテーションをCPUが行います。このことから大量のパケットを処理する場合、CPUが高負荷状態になる恐れがあります。
2025年現在、最新OSのTSOはONだとは思いますが、まれに仮想環境ではOFFの場合があります。
TSOの確認方法
・Windows
[Windows + X] → [デバイスマネージャー]
[ネットワーク アダプター] → 使用中のNICを右クリック → [プロパティ]
[詳細設定] タブ → 以下のような項目を探して有効化(ON)する
Large Send Offload (IPv4)
Large Send Offload (IPv6)
TCP Segmentation Offload
上記の値がOFFであれば無効状態

・Redhat Linux コマンド
ethtool -k <インターフェース名> | grep tcp-segmentation-offload
戻り値がONなら有効、OFFなら無効です。
一時的に無効化
ethtool -K <インターフェース名> tso off
一時的に有効化
ethtool -K <インターフェース名> tso on
<インターフェース名>はethなど

・Solaris11 コマンド
NICの名前<インターフェース名>を確認
dladm show-phys
TSOの状態を確認
dladm show-linkprop -p tso <インターフェース名>
出力例:
LINK PROPERTY PERM VALUE EFFECTIVE DEFAULT POSSIBLE
net0 tso rw on on on on,off
VALUE:設定されている値
EFFECTIVE:実際に有効かどうか(NICが非対応だとoffになる)
DEFAULT:デフォルト設定
POSSIBLE:設定可能な値(通常は on,off)
一時的に無効化
sudo dladm set-linkprop -p tso=off <インターフェース名>
一時的に有効化
sudo dladm set-linkprop -p tso=on <インターフェース名>

LROの確認方法
LROはネットワーク受信時のパフォーマンスを向上させるためのオフロード技術です。
・Windows
LROの名称を使用していないため、デバイスドライバから以下の類似項目を探してON・OFFします。
[Windows + X] → [デバイス マネージャー]
[ネットワーク アダプター] → 使用中のNICを右クリック → [プロパティ]
[詳細設定] タブを開く
以下のようなプロパティを探す:
Receive Side Coalescing:LROに類似した動作
Large Receive Offload:実質的にLRO(ある場合)
Rx TCP checksum offload:関連する受信オフロード

・Redhat Linux コマンド
LROの状態確認
ethtool -k <インターフェース名> | grep large-receive-offload
戻り値がONなら有効、OFFなら無効
一時的に無効化
sudo ethtool -K <インターフェース名> lro off
一時的に有効化
sudo ethtool -K <インターフェース名> lro on

・Solaris11 コマンド
NICの名前<インターフェース名>を確認
dladm show-phys
LROの状態確認
dladm show-linkprop -p lro <インターフェース名>
例:
LINK PROPERTY PERM  VALUE EFFECTIVE DEFAULT POSSIBLE
net0   lro rw   on on on on,off
VALUE:現在の設定値
EFFECTIVE:実際に有効かどうか
DEFAULT:デフォルト設定
POSSIBLE:設定可能な値(通常は on,off)
一時的に無効化
sudo dladm set-linkprop -p lro=off <インターフェース名>
一時的に有効化
sudo dladm set-linkprop -p lro=on <インターフェース名>

6.ディスクI/Oの性能不足
ディスクI/Oがボトルネックになっている兆候としては、データ転送が遅いのにCPU利用率が極端に低い状態であることが兆候としてあります。またiostatでI/Oの待ち時間が10msを超えて100msになるとディスクI/Oの遅延が疑われます。
手段と対策
・I/Oスケジューラの最適化 書き込み順序の効率化
I/Oスケジュールの確認方法
Redhat Linux
cat /sys/block/sdX/queue/scheduler
[]の値が現在の設定値
[mq-deadline] kyber bfq none

I/Oスケジューラの設定方法
Redhat Linux
echo mq-deadline > /sys/block/sdX/queue/scheduler

ディスクタイプによる変更例
ディスクタイプ(用途)    パラメータ
NVMe SSD        none または mq-deadline
SATA SSD mq-deadline または kyber
HDD bfq または mq-deadline
データベース用途 mq-deadline
メディア再生・ bfq

・非同期I/Oやバッファ活用 ディスク待ちを減らす
rsyncを使用している場合には以下のオプション使用を検討する
--inplace: キャッシュが効きやすくなる
--write-batch: 書き込みをバッファして後から実行

・ファイルシステムの最適化 書き込み遅延やメタデータI/O削減
ext4ならマウントオプションで noatime や data=writeback を有効化してやる
例:mount -o remount,noatime /mnt/target

・圧縮・アーカイブ転送     データ量とファイル数削減
圧縮スピードとのバーターになるが何百万ファイルの数㎅ファイルが対象の場合、tarで圧縮してからデータ転送したほうが結果として短時間の場合がある。

7.メモリの枯渇
・大量のファイル転送(特に小容量ファイル)
ファイル一覧、メタデータ、キャッシュをすべてメモリに展開するため、メモリ使用量が急増する可能性がある。
特に rsync, scp, tar などで大量ファイルを扱う場合に顕著になる場合が多い
対応:一度にデータ転送するファイル数を検証してチューニングする

・バッファサイズの大きすぎる
TCPウィンドウサイズやアプリの送受信バッファを大きく設定しすぎた場合、NICキューやアプリのバッファでメモリを多く消費する
対応:TCPウィンドウサイズを最適化(小さく)する

・カーネルキャッシュの肥大化
LinuxやSolarisでは、ファイルI/Oのパフォーマンス向上のためにデータをメモリにキャッシュするため、転送対象が多いとページキャッシュを圧迫し、スワップアウトやI/O待ちが発生する
対応:カーネルキャッシュを定期的にクリアする(sync; echo 3 > /proc/sys/vm/drop_caches)

・同時転送プロセス・スレッドの増加
並列転送(rsync + GNU parallel など)で平行処理を行うため、プロセス数を増やしすぎると、メモリ使用量も比例して増加する
対応:並列処理数を減らす
rsync + parallelを使用している場合には-jオプションの値を減らして同時転送プロセスを減らす

・メモリ帯域の枯渇(特に NUMA システム)
高速ネットワーク(10Gbps以上)+ 高速SSD構成で、CPUとメモリ間の転送帯域が足を引っ張ることがある
対策:複数CPUにまたがるホスト(仮想マシンに多い)を使用する場合にはNUMA設計を厳にする。IAサーバはCPUとメモリは対の関係になるので、NUMA構成を意識して適切なメモリ容量になるように割り当てメモリを増やす。

ディスクの書き込みスピードの調査
ネットワーク帯域とセッションの最適化を実施しても上記のようにメモリ・CPU・ディスク能力にボトルネックがあるとデータ転送は思惑通りに進みません。
このため、設計時に最低でも転送元(現在のシステム)の読み込みディスクスピードと転送先(新規システム)のディスク書き込みスピードは計測して置くことを推奨します。
1セッション辺りのネットワーク帯域とディスク書き込みスピードは非常に重要な要素です。
ネットワーク帯域が十分でも書き込み速度が足りなければバッファリングされメモリ不足に陥る可能性がありまうす。
数百GB程度のデータ容量の転送ならさして問題にならないかもしれませんが数100TBクラスのデータとなると数十時間経過の後、失敗するとリカバーができない可能性もあります。

・Windows
ディスク読み込み速度ディスク書き込み
Powershell ISE(管理者権限で起動)
winsat disk -drive c > c:\temp\read.txt
※結果をc:\temp\read.txtに出力(予めc:\tempフォルダを作成しておく)
Windows システム評価ツール
> 実行中: 機能の列挙 ''
> 実行時間 00:00:00.00
> 実行中: 記憶域の評価 '-drive c -ran -read'
> 実行時間 00:00:00.19
> 実行中: 記憶域の評価 '-drive c -seq -read'
> 実行時間 00:00:02.30
> 実行中: 記憶域の評価 '-drive c -seq -write'
> 実行時間 00:00:01.92
> 実行中: 記憶域の評価 '-drive c -flush -seq'
> 実行時間 00:00:00.53
> 実行中: 記憶域の評価 '-drive c -flush -ran'
> 実行時間 00:00:00.50
> Dshow ビデオ エンコード時間 0.00000 s
> Dshow ビデオ デコード時間 0.00000 s
> メディア ファンデーション デコード時間 0.00000 s
> Disk Random 16.0 Read 454.18 MB/s 8.2
> Disk Sequential 64.0 Read 519.32 MB/s 8.1
> Disk Sequential 64.0 Write 496.96 MB/s 8.1
> シーケンシャル書き込みでの平均読み取り時間 0.149 ms 8.7
> 待ち時間: 95 パーセンタイル 0.397 ms 8.7
> 待ち時間: 最大 3.342 ms 8.7
> ランダム書き込みでの平均読み取り時間 0.159 ms 8.9
> 合計実行時時間 00:00:05.52

・Redhat Linux
ディスク読み込み
キャッシュなし: dd if=testfile of=/dev/null bs=1G count=1 iflag=direct
キャッシュあり: dd if=testfile of=/dev/null bs=1G count=1
if=testfile:読み込むファイル(十分なサイズのもの)
of=/dev/null:読み込み結果は破棄
iflag=direct:ページキャッシュを通さず直接ディスクから読む
ディスク書き込み
キャッシュなし: dd if=/dev/zero of=testfile bs=1G count=1 oflag=direct
キャッシュあり: dd if=/dev/zero of=testfile bs=1G count=1
if=/dev/zero:読み込み元は全ゼロのデータ
of=testfile:出力先ファイル
bs=1G:ブロックサイズ
count=1:1回書き込み
oflag=direct:キャッシュを通さず直接ディスクへ書き込む
実行た場所に1GBのtestfileが作成されるのでrmコマンド等での削除を忘れずに
ディスクキャッシュのクリア:
sync
echo 3 | tee /proc/sys/vm/drop_caches
単位は出力結果のMB/SやGB/sを使用してください。ネットワーク帯域と比較する場合には単位を合わせるのを忘れずに
・Solaris
OSとしてキャッシュをクリアするコマンドがないのがしんどい。OSを再起動すればキャッシュはクリアされる。
1㎇のファイルを作成(/rootにtestfileを作成)
dd if=/dev/zero of=testfile bs=1M count=1024
ディスクの読み込み速度
/usr/bin/time dd if=/root/testfile of=/dev/null bs=1M count=1024
1024+0 records in
1024+0 records out
real 2.30
user 0.01
sys 0.10
自分で計算:読み込み速度 = 1GB ÷ 2.30秒 ≒ 446 MB/s

ディスクの書き込み速度
/usr/bin/time dd if=/dev/zero of=./testfile bs=1M count=1024
1024+0 records in
1024+0 records out
real 2.35
user 0.01
sys 0.18
自分で計算:書き込み速度 1024 MB ÷ 2.35 秒 = 約 435 MB/s

Solaris11のddは速度値を返さないため、timeコマンドと組み合わせています。
/usr/xpg4/bin/dd コマンド(SUNWgnu-coreutilsかSUNWxpg4のインストールが必要)がインストールされて入ればこちらのddコマンドを使用すれば、速度値が返ります。
/usr/xpg4/bin/dd if=/root/testfile of=/dev/null bs=1M count=1024
/usr/xpg4/bin/dd if=/dev/zero of=./testfile bs=1M count=1024

ご参考まで。

関連記事

TOP