← シリーズに戻る

scp と rsync を実測で使い分ける

Part 3 / 6 SSH接続の5方式を7軸比較(4方式実測)

同じ場所へ繰り返し送るなら rsync、1回きりなら scp です。判断の根拠は速度ではありません。送信バイト数を見ると、2回目から桁が変わります。

1.2MB のディレクトリを同じ相手へ3回送って測りました。scp は3回とも全量 (約1.2MB) を送り、rsync は2回目が 94 バイト、1ファイルだけ変えた3回目が 200,185 バイトでした。

送信バイト数を対数軸の横棒で比べた図。1回目 (向こうは空) は scp 1,205,036 バイト、rsync 1,200,471 バイトでほぼ同じ長さ。2回目 (何も変えていない) は scp 1,205,076 バイトに対して rsync 94 バイトで、rsync の棒が4桁短い。3回目 (200KBの1つだけ変更) は scp 1,205,128 バイト、rsync 200,185 バイト。目盛りは 10 から 1000万まで1つで10倍。rsync は踏み台経由、scp は別の実行で localhost へ踏み台なしで測った値

測った条件

送るもの: 1,200,000 バイト / 2ファイル
  a.bin  1,000,000 バイト
  b.bin    200,000 バイト
経路: クライアント → 踏み台 → 送信先 (すべて OpenSSH 10.0p2)

3回の内訳は、1回目が相手側に何も無い状態、2回目が何も変えずにもう一度送った状態、3回目が b.bin の中身だけを作り直して送った状態です。実際の運用でいえば、初回のデプロイ、変更が無いまま流した再デプロイ、1ファイルだけ直した再デプロイに当たります。

scp は毎回おなじだけ送る

1回目 (向こうは空)        : 0.246 秒
2回目 (何も変えていない)  : 0.227 秒
3回目 (200KBの1つだけ変更): 0.220 秒

3回ともほぼ同じ時間です。scp は相手側に何があるかを問い合わせないので、そもそも差分という概念を持ちません。 2回目に何も変えていなくても、全量をもう一度送ります。

この計測では scp の送信バイト数を取っていなかったので、別に測り直しました。同じ大きさの2ファイルを localhost へ3回送り、scp -v の最後に出る Transferred: 行を見ると、3回とも約 1,205,000 バイトを送っていました。

1回目: Transferred: sent 1205036, received 5408 bytes
2回目: Transferred: sent 1205076, received 5344 bytes
3回目: Transferred: sent 1205128, received 5404 bytes

これは欠陥ではありません。相手の状態を問い合わせない分だけ往復が少なくて済むので、小さなファイルを1回送るだけの用途では rsync より素直に速く終わります。

rsync は差分だけを送る

1回目 (向こうは空)         転送 2ファイル / sent 1,200,471 バイト
2回目 (何も変えていない)   転送 0ファイル / sent        94 バイト
3回目 (200KBの1つだけ変更) 転送 1ファイル / sent   200,185 バイト

読み方は3つあります。

  • 1回目は全量 (1,200,471 バイト) を送っていて、scp と同じ桁です。空の相手に対しては差分が存在しないので当然です
  • 2回目の 94 バイトは、ファイル一覧を突き合わせただけの往復です。中身は1バイトも流れていません
  • 3回目の 200,185 バイトは、変えた b.bin の 200,000 バイトとほぼ一致します。変わっていない 1,000,000 バイトの a.bin は流れていません

転送後に両側で md5 を取って、中身が一致していることも確認しました。

手元   bcbba71aa361c747af5a8f03610d8a6d  a.bin
向こう bcbba71aa361c747af5a8f03610d8a6d  a.bin

どちらも同じ SSH 設定に乗る

rsync は自前の転送路を持たず、-e で渡した ssh をそのまま実行します。つまり ~/.ssh/config に書いた設定がそのまま効きます。

rsync -a -e ssh site/ target:~/site/

踏み台越しでも、ssh 側が通っていれば追加の設定は要りませんでした。転送の設定を別に持つ必要はありません。 設定の一本化は次の章で扱います。

scp が非推奨という話の正体

scp について「非推奨」と言われるのは、コマンドではなく中で使っていたプロトコルのほうです。

OpenSSH 9.0 の変更点にこうあります。

This release switches scp(1) from using the legacy scp/rcp protocol to using the SFTP protocol by default.

旧来の scp/rcp プロトコルは、送ったファイル名をリモート側のシェルが解釈する設計だったため、空白や記号を含む名前を送るときには二重引用符で囲んでシェルに食わせない作法が要りました。SFTP に切り替わったことで、その作法は不要になっています。

scp の旧プロトコルと SFTP を左右に並べた図。左の旧 scp/rcp プロトコル (9.0 より前の既定。今は -O を付けたときだけ) は、手元の scp が ssh を呼ぶ → 相手側のシェルにファイル名入りのコマンド行を渡す → ファイル名をリモート側のシェルが解釈する (赤)。例として scp -O "相手 b" . は相手側で「a」と「b」の2つの名前に割れてどちらも見つからない。空白や記号を含む名前には引用の作法が要った。右の SFTP プロトコル (OpenSSH 9.0 からの既定) は、手元の scp が ssh を呼ぶ → 相手側の SFTP サブシステムと話す → ファイル名はシェルを通らず SFTP のデータとして渡る。同じ scp "相手 b" . なら「a b」が1つのファイルとして届き、引用の作法は不要になった。下に「非推奨になったのはプロトコルで、scp コマンドはそのまま使える。"a b" の例は OpenSSH 10.0p2 のコンテナから localhost に向けて確かめた挙動」

ただし、プロトコルが変わっても変わらない挙動があります。scp -r site 相手:site を2回打つと、2回目は site/site の入れ子になります。OpenSSH 10.0p2 のコンテナから localhost に向けて試すと、既定の SFTP でも、旧プロトコルを強制する -O でも同じように入れ子になりました。

$ scp -r site localhost:site     # 1回目
$ scp -r site localhost:site     # 2回目
$ find site -type f | sort
site/a.bin
site/b.bin
site/site/a.bin
site/site/b.bin

送り先のディレクトリが既にあれば、その中にコピーする。cp -r と同じ決まりで、プロトコルとは関係がありません。同じ場所へ繰り返し送るなら、中身だけを送る rsync -a site/ 相手:site/ のほうが入れ子の事故が起きません。 送り元の末尾のスラッシュは「中身だけ」という意味です。

同じ場所へ2回送ったときの相手側の中身を比べた図。左の scp -r site 相手 は、1回目のあとが site/a.bin と site/b.bin、2回目のあとはそれに加えて site/site/a.bin と site/site/b.bin が入れ子でできている (赤)。送り先のディレクトリが既にあればその中にコピーする、cp -r と同じ決まりで、既定の SFTP でも -O でも入れ子になった。右の rsync -a site/ 相手/ は、2回目のあとも site/a.bin と site/b.bin のまま。送り元の末尾のスラッシュは「中身だけ」という意味

この章の結論

1回きりなら scp、繰り返すなら rsync。境目は回数です。2回目からは送信バイト数が桁で変わるので、同じ場所へ1日に何度も送る使い方をしているなら、そこだけでも rsync に替える価値があります。どちらも ~/.ssh/config に乗るので、経路の設定は1箇所で済みます。その1箇所を次の章で作ります。