rsync のアルゴリズム ~単純コピーのcp/scpコマンドとはどのように違うのか?~
rsync は元々は『低速回線を使って高速にファイルを転送・同期する仕組み』として生まれました。そのアルゴリズムはこの目的に対して極めて優れており、1996年に Andrew Tridgell と Paul Mackerras に発表されて以来、今までに広く使われています。
現在の rsync コマンドは以下のアルゴリズムで動作します。
1. コピー元からコピー先へのファイルリストの提示
コピー元はコピー先に対し、コピー対象の「ファイル名」「タイムスタンプ」「ファイルサイズ」の 3 つの情報を記載したファイルリストを提示します。
rsync を実行したサーバがファイルコピー先の場合は "receiving incremental file list"、コピー元の場合は "sending incremental file list" と表示されますが、それがこのフェーズです。
2. コピー先がファイルリストを3つに分類
コピー先はコピー元から受信したファイルリストを以下のように分類します。
①同一ファイル
3 つの情報が全て一致した場合は「同一ファイル」と見做し、コピー不要の旨を返信します。
②存在しないファイル
ファイル名がコピー先に無い場合、「存在しないファイル」として、コピーが必要な旨を返信します。
③差分がありそうなファイル
ファイル名が同じだけどタイムスタンプもしくはファイルサイズが異なるファイルについては、そのファイルを "chunk (チャンク)" という単位 (2018 年現在は32KB *1) に小間切れにし、chunk 単位で "rolling checksum" という簡易なハッシュ値と、より強力なハッシュ関数 (もともとは md4, 2007 年から md5 *2) 値を計算をして順次コピー元に送ります。
*1 https://github.com/AndyA/rsync/blob/master/rsync.h
*2 https://github.com/AndyA/rsync/commit/a0456b9c4635be8832fc5712454a75ec102b1176
なお、3 つの情報だけの場合、例えばたまたまタイムスタンプとサイズが同じだが中身が異なるファイルになってしまった場合 (例えばコピー元とコピー先で同時に同じ1文字だけ変更した場合等) は通常は検知できません。
-c オプションを使えば、このファイル分類のときに 3 つの情報に加え、チェックサムも見ることができ、検知できます (当然負荷が上がりコピー速度は遅くなります)。
# rsync -c /etc/chrony.conf user1@192.168.1.1:/backup/settings/chrony.conf
-v オプションを付けている場合、この分類において差分があると判定されたファイルが標準出力されていきます。また、--log-file=オプションを付けている場合は指定するログファイルにも出力されます。ログの見方については後述します。
3. コピー元からコピー先へファイル転送
「2-①同一ファイル」についてはコピーを実行せずスキップし、「2-②存在しないファイル」については丸ごとコピーを実行します。
「2-③差分がありそうなファイル」についてはコピー元でも同様に chunk 毎に "rolling checksum" を計算し、受信した rolling checksum と差異を確認します。もし差異があればすぐ様そのchunk分だけコピー先へ転送します。
もし rolling checksum に差異が無い場合、本当に一致していないかを確認するために、より強力なハッシュ関数で確認します。そこで差異があればやはり chunk 分のみ転送しますし、差異がなければ転送しません。
このように、ファイル全体をコピーするのではなく、ファイルの中でも一致する部分が多ければそれだけコピー量 (つまりネットワーク転送量) を減らすことができます。
この事実から、rsync が本領を発揮するのは「低速回線を跨ぐ同期」かつ「ファイル数は少ないが大容量ファイルの割合が多い」かつ「コピー元とコピー先で差分が少ない」ケースです。
逆に rsync が遅くなるのは「(高速だが) 遅延の大きい回線 (WAN 回線等) を跨ぐ同期」または「ファイル数が多く、小容量ファイルの割合が多い」または「コピー元とコピー先で差分が大きい」といったケースです。
このようなケースでは、その用途がバックアップなのであれば (ファイルシステムを意識しない) ボリューム単位でのバックアップデータを転送する方式も検討に入れた方がよいでしょう。
ログの見方
>f..t......
>f.st......
rsyncでは上記のようなログが出力されますが、この11個の文字はそれぞれ以下のような意味を持ちます。
1番目[Y]=type of update
更新された向きを示しています。
> (だいなり) はローカルホストがファイルを受信した場合。
< (しょうなり) はリモートホストへファイルを送信した場合。
c はローカルの情報が変わった、という意味。
.(ドット)は何も更新しなかったという意味。
h は対象ファイルがハードリンクであるという意味。
*(アスタリスク)は--deleteオプションを付けた時などのファイル削除を意味する"deleting"等のメッセージを含んでいることを意味します。
2番目[X]=type of file
f は通常のファイル、d はディレクトリ、L はシンボリックリンク、D はデバイスファイル、Sは sockets や fifos 等の特殊なファイルという意味です。
3番目[c]=different checksum
通常のファイルについてはchecksumが異なる場合に c が表示されます。その他のタイプのファイルについては値が違う場合に c が表示されます。
※チェックサムの計算には「ファイル名やタイムスタンプやパーミッション」等のメタ情報は含まれません(例えば異なるファイル名の同じ"test"とだけ書き込まれたファイルのsha512sum等のハッシュ値は必ず一致します)。通常のファイル以外はデータ領域が0Byteなのでチェックサムが計算できませんので、そのようなファイルについてはメタ情報の差分の確認を行います。
4番目[s]=size is different
サイズが異なる場合に s が表示されます。
5番目[t]=timestamp is different
タイムスタンプが異なる場合に t が表示されます。
6番目[p]=permission are different
パーミッションが異なる場合に p が表示されます。
7番目[o]=owner is different
所有者が異なる場合に o が表示されます。
8番目[g]=group is different
所有グループが異なる場合に g が表示されます。
9番目[u]=reserved for future use.
将来用の拡張領域です。
10番目[a]=ACL information changed
POSIX ACL が設定された状態で、その情報が変わった場合に a が表示されます。
11番目[x]=extended attribute information changed
拡張属性の情報が変わった場合に x が表示されます。
特殊なケースとして
>f+++++++++
これはローカルホストにファイルが新規に生成された、という意味です。
cd+++++++++
これはローカルホストにディレクトリが新規に生成された、という意味です。
参考
プロトコルは rsync?ssh?
rsyncコマンドの構文は以下の通り単純です。
# rsync [Options] [Src File or Directory] [Dst File or Directory]
ですがここでややこしいのが、rsync には 2 つの方式があることです。
1 つは rsh (リモートシェル) を使った方式、もう 1 つは rsyncd を使った方式です。
rsh は 具体的には ssh を使うのがデフォルトであり、こちらのほうがよく使われます。この場合、必要なTCPポートはssh のポートであり、rsync のポート tcp:873 は必要ないのです。
rsh (ssh) を使う場合はリモートホストの指定で : (コロン 1 つ) で区切ります。例えばローカルの /etc 配下全てを 192.168.1.1 ホストの /backup/host-A 配下に同期したい場合は、以下のコマンドを使います。(192.168.1.1 には user1 というユーザで ssh ログインできる前提です)
# rsync -r /etc user1@192.168.1.1:/backup/host-A
※ -r はディレクトリを下の階層も含め丸ごと(再帰的)にコピーするオプションです。
このコマンドで必要となるポートは TCP:22 になります。
一方、rsyncd を使う場合は::(コロン2つ) もしくは rsync:// を使います。
# rsync -r /etc user1@192.168.1.1::/backup/host-A # rsync -r /etc rsync://user1@192.168.1.1/backup/host-A
このコマンドで必要となるポートは TCP:873 になります。ただし、これ以外にも /etc/rsync.conf の設定等が必要ですので、ssh が使えるのであれば、わざわざこれらの設定をしなくとも ssh の方式を使えば良いでしょう。
また、セキュリティのために ssh でポートを変更していたり公開鍵認証を使っている場合もあるかと思います。例えば tcp ポート 22222 を利用し、秘密鍵を /home/user1/.ssh/id_rsa に配置している場合は以下のコマンドを使います。
# rsync -r -e "ssh -p 22222 -i /home/user1/.ssh/id_rsa" /etc user1@192.168.1.1:/backup/host-A
オプション
標準系は以下です。
# rsync -av /etc user1@192.168.1.1:/backup/host-A
目的・シチュエーションに応じてオプションを付け加えます。
-a ( --archive)
-r, -l, -p, -t, -o, -g, –devices, –specialsの8つのオプションを同時に指定したのと同義。
-u (--update)
このオプションが無い場合、前述の通り、"タイムスタンプ"もしくは"サイズ"が異なればコピーされるが、このオプションがある場合は、"コピー元のタイムスタンプがコピー先よりも新しい"場合にコピーされる。
-v (--verbose)
コピーしたファイル名やバイト数などの転送詳細情報を表示する。
-r (--recursive)
指定パスのファイルだけでなく、その配下にあるディレクトリおよびファイルを全てコピーする。
-l (--links)
シンボリックリンクをシンボリックリンクのままコピーする。
-p (--perms)
パーミッションをコピーする。ただし、SELINUXのコンテキスト情報は含まれないので注意。必要な場合は後述の拡張属性コピーオプション-Xを使う。
-t (--times)
タイムスタンプ情報をコピーする。
-o (--owner)
ファイル所有者情報をコピーする。
-g (--group)
ファイルグループ情報をコピーする。
--devices
ブロックデバイスファイルは、ブロックデバイスの中の実データをコピーするのではなく、ブロックデバイスファイルのままコピーする。
--specials
名前付きパイプ等の特殊ファイルをコピーする。
--delete
コピー元に存在しないファイルが、コピー先に存在する場合、コピー先のそのファイルを削除する(コピー元とコピー先を完全一致させる)。
-S
スパースファイルをスパースのままコピーする。
-n
実際にはコピーせず、コピーした時と同じ出力を表示させる(動作確認用, dry run.)
--log-file=[dir]
rsyncではログの場所は決まっていないので、このオプションでログフォイルの出力先を決める。
-b
もしコピー先で上書きや削除される条件に該当した場合、そのファイルを消さずにリネームする。デフォルトでは同一ディレクトリ内で、ファイル名の最後に~を付ける。
--backup-dir=[dir]
-b でリネームする際に、指定したディレクトリ [dir] に mv する。
--suffix=[suffix]
-b でリネームする際に、~ではなく [suffix] を付ける。
--exclude=
コピー対象から除外する。例えば --exclude='*.old' と指定すれば .old のファイル/ディレクトリをコピーしない。(-r を使っている場合、下位のディレクトリにも効果あり)
--bwlimit=[rate]
速度制限する。例えば --bwlimit=1.5m と指定すれば1.5M Byte/sec となる。
--rsync-path=[program on remote server]
sshで接続した後のリモートサーバ上でのコマンド実行指定。これは主に相手先へのssh接続としては一般ユーザでログインし、sudoでrsyncをルート権限で実行したい時によく使われる。
詳しい実装は以下のページを参照。
-X
拡張属性もコピーする。SELinux のコンテキストは拡張属性に含まれるため、コンテキストも移行するときはこのオプションを付ける。なお、このオプションを付けると比較対象として拡張属性も見るようになる。つまり、コピー元とコピー先でサイズとタイムスタンプが同じであっても、拡張属性が異なる場合はコピーされる。
コメント
文中に記載されているGitHubリンクですが、著作者によるファイル移動によりリンク切れしました。現在のリンクはこちらです。
https://github.com/philsinatra/docs/blob/master/shell/rsync.md
また、更新が不要であればコミットIDを指定したpermalinkの使用をお勧めします↓
https://github.com/philsinatra/docs/blob/030b823e8db6329f7cb99858369cd2dd7d734979/shell/rsync.md
@z8napi さん、コメントありがとうございます。
また、リンク切れとコミットID指定のリンクまでご丁寧にありがとうございます。更新させて戴きます。
取り急ぎお礼まで。