こんにちは。鎌田です。
今回は高速にファイルを同期する"rsync"というコマンドを使用した際に、一部ファイルが同期されないという問題が発生したため、その解決策をご紹介いたします。
rsyncはファイルを同期する前に転送元と転送先のファイルをチェックし、差分があるものだけを同期します。デフォルトの挙動では、ファイルサイズとタイムスタンプをチェックして、転送対象のファイルを判断しているのですが、それだとファイルが同期されない可能性があります。
原因
rsyncでは転送元ファイルと転送先ファイルのタイムスタンプに秒単位に違いがなくファイルサイズが同じだと同期されません。そのため、rsyncを使用して更新の激しいファイルをコピーする場合、ミリ秒・ナノ秒単位の違いで書き込まれたファイルは正しく同期されない可能性があります。
解決策
-c オプションをつけチェックサムによる差分確認をすれば、ミリ秒・ナノ秒単位の違いで書き込まれたファイルを同期させることができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ ls --full a/foobar b/foobar -rw-rw-r-- 1 kamada.yuhei kamada.yuhei 3 2016-01-01 11:30:45.999999999 +0900 a/foobar -rw-rw-r-- 1 kamada.yuhei kamada.yuhei 3 2016-01-01 11:30:45.000000000 +0900 b/foobar $ md5sum a/foobar b/foobar #a/foobarとb/foobarのチェックサムを計算 94364860a0452ac23f3dac45f0091d81 a/foobar dc88404407f0718c106c38f60e0cd35b b/foobar $ rsync -av -c a/foobar b/foobar sending incremental file list #同期されたファイルのリストを表示 foobar #foobarが同期されている sent 122 bytes received 31 bytes 306.00 bytes/sec total size is 3 speedup is 0.02 |
以下でタイムスタンプに焦点を当てて、rsyncの挙動を調査してみました。
環境
- OS :Oracle Linux Server release 7.2
- rsync バージョン: 3.0.9
また rsyncのバージョン3.1.0でも同様の問題で、ファイルが同期されません。
(3.1.0では、同期するとタイムスタンプをナノ秒精度でコピーするため、下記の例外のようなことは起きません。)
執筆時点で開発が進行しているrsyncでは、オプションに --modify-window=-1 をつけることで、ナノ秒精度でファイルの更新確認が行われるようになります。こちら
※ただし、使用しているファイルシステムがナノ秒精度のタイムスタンプをサポートしていることが必要になります。
どのような場合に同期 される/されない のか?
調査のためにファイルサイズが同じファイルを2つ用意しました。
- 同期元ファイル:a/foobar
- 同期先ファイル:b/foobar
rsyncの挙動を見やすくするために -av オプションをつけております。
□同期元ファイルと同期先ファイルのタイムスタンプが同じ場合
1 2 3 4 5 6 7 8 9 10 |
$ touch -d "2016-01-01 11:30:45.000000000 +0900" a/foobar b/foobar #タイムスタンプを同じにする $ ls --full a/foobar b/foobar -rw-rw-r-- 1 kamada.yuhei kamada.yuhei 0 2016-01-01 11:30:45.000000000 +0900 a/foobar -rw-rw-r-- 1 kamada.yuhei kamada.yuhei 0 2016-01-01 11:30:45.000000000 +0900 b/foobar $ rsync -av a/foobar b/foobar sending incremental file list #同期されたファイルのリストを表示 sent 60 bytes received 12 bytes 144.00 bytes/sec total size is 0 speedup is 0.00 |
同期されません。
a/foobarとb/foobarはタイムスタンプが同じため同期されません。
比較のために、ファイルサイズが違う場合のrsyncの結果を載せます。
1 2 3 4 5 6 7 8 9 10 |
$ ls --full a/foobar b/foobar -rw-rw-r-- 1 kamada.yuhei kamada.yuhei 3 2016-01-01 11:30:45.000000000 +0900 a/foobar -rw-rw-r-- 1 kamada.yuhei kamada.yuhei 0 2016-01-01 11:30:45.000000000 +0900 b/foobar $ rsync -av a/foobar b/foobar sending incremental file list #同期されたファイルのリストを表示 foobar #foobarが同期されている sent 106 bytes received 31 bytes 274.00 bytes/sec total size is 3 speedup is 0.02 |
同期されます。
a/foobarとb/foobarのファイルサイズが違うと、このように同期されます。
さらに比較のために、ファイルサイズは同じだが、中身が違う場合の結果を載せます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
ls --full a/foobar b/foobar -rw-rw-r-- 1 kamada.yuhei kamada.yuhei 3 2016-01-01 11:30:45.000000000 +0900 a/foobar -rw-rw-r-- 1 kamada.yuhei kamada.yuhei 3 2016-01-01 11:30:45.000000000 +0900 b/foobar $ md5sum a/foobar b/foobar #a/foobarとb/foobarのチェックサムを計算 94364860a0452ac23f3dac45f0091d81 a/foobar dc88404407f0718c106c38f60e0cd35b b/foobar $ rsync -av a/foobar b/foobar sending incremental file list #同期されたファイルのリストを表示 sent 60 bytes received 12 bytes 144.00 bytes/sec total size is 3 speedup is 0.04 |
同期されません。
このように、ファイルサイズが同じでタイムスタンプも同じだと中身が違う場合でも同期されません。
□同期元ファイルのタイムスタンプを999,999,999ナノ秒(ほぼ1秒)進めた場合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ touch -d "2016-01-01 11:30:45.999999999 +0900" a/foobar #タイムスタンプを999,999,999ナノ秒進める $ ls --full a/foobar b/foobar -rw-rw-r-- 1 kamada.yuhei kamada.yuhei 0 2016-01-01 11:30:45.999999999 +0900 a/foobar -rw-rw-r-- 1 kamada.yuhei kamada.yuhei 0 2016-01-01 11:30:45.000000000 +0900 b/foobar $ rsync -av a/foobar b/foobar sending incremental file list #同期されたファイルのリストを表示 sent 60 bytes received 12 bytes 144.00 bytes/sec total size is 0 speedup is 0.00 $ ls --full a/foobar b/foobar -rw-rw-r-- 1 kamada.yuhei kamada.yuhei 0 2016-01-01 11:30:45.999999999 +0900 a/foobar -rw-rw-r-- 1 kamada.yuhei kamada.yuhei 0 2016-01-01 11:30:45.000000000 +0900 b/foobar |
同期されません。
a/foobarのタイムスタンプを999,999,999ナノ秒(ほぼ1秒)進めてみたのですが、ミリ秒・ナノ秒単位での更新では同期されないようです。
□同期元ファイルのタイムスタンプを1秒進めた場合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$ touch -d "2016-01-01 11:30:46.000000000 +0900" a/foobar #タイムスタンプを1秒進める $ ls --full a/foobar b/foobar -rw-rw-r-- 1 kamada.yuhei kamada.yuhei 0 2016-01-01 11:30:46.000000000 +0900 a/foobar -rw-rw-r-- 1 kamada.yuhei kamada.yuhei 0 2016-01-01 11:30:45.000000000 +0900 b/foobar $ rsync -av a/foobar b/foobar sending incremental file list #同期されたファイルのリストを表示 foobar #foobarが同期されている sent 99 bytes received 31 bytes 260.00 bytes/sec total size is 0 speedup is 0.00 $ ls --full a/foobar b/foobar -rw-rw-r-- 1 kamada.yuhei kamada.yuhei 0 2016-01-01 11:30:46.000000000 +0900 a/foobar -rw-rw-r-- 1 kamada.yuhei kamada.yuhei 0 2016-01-01 11:30:46.000000000 +0900 b/foobar |
同期されます。
a/foobarのタイムスタンプをさらに1ナノ秒(差分が1秒ちょうど)進めると同期されました。
□同期元ファイルのタイムスタンプを1,999,999,999ナノ秒(ほぼ2秒)進めた場合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$ touch -d "2016-01-01 11:30:46.999999999 +0900" a/foobar #タイムスタンプを1,999,999,999ナノ秒進める $ ls --full a/foobar b/foobar -rw-rw-r-- 1 kamada.yuhei kamada.yuhei 0 2016-01-01 11:30:46.999999999 +0900 a/foobar -rw-rw-r-- 1 kamada.yuhei kamada.yuhei 0 2016-01-01 11:30:45.000000000 +0900 b/foobar $ rsync -av a/foobar b/foobar sending incremental file list #同期されたファイルのリストを表示 foobar #foobarが同期されている sent 99 bytes received 31 bytes 260.00 bytes/sec total size is 0 speedup is 0.00 $ ls --full a/foobar b/foobar -rw-rw-r-- 1 kamada.yuhei kamada.yuhei 0 2016-01-01 11:30:46.999999999 +0900 a/foobar -rw-rw-r-- 1 kamada.yuhei kamada.yuhei 0 2016-01-01 11:30:46.000000000 +0900 b/foobar |
同期されます。
a/foobarの転送元ファイルのタイムスタンプを1,999,999,999ナノ秒(ほぼ2秒)進めると同期されます。
a/foobar 11:30:46.999999999
b/foobar 11:30:46.000000000
しかしタイムスタンプは一致せず、秒精度で切り詰められていることが確認できました。
□同期元ファイルと同期先ファイルのタイムスタンプを1ナノ秒ずらした場合
1 2 3 4 5 6 7 8 9 10 11 12 |
$ ls --full a/foobar b/foobar -rw-rw-r-- 1 kamada.yuhei kamada.yuhei 0 2016-01-01 11:30:46.000000000 +0900 a/foobar -rw-rw-r-- 1 kamada.yuhei kamada.yuhei 0 2016-01-01 11:30:45.999999999 +0900 b/foobar $ rsync -av a/foobar b/foobar sending incremental file list foobar #foobarが同期されている sent 99 bytes received 31 bytes 260.00 bytes/sec total size is 0 speedup is 0.00 $ ls --full a/foobar b/foobar -rw-rw-r-- 1 kamada.yuhei kamada.yuhei 0 2016-01-01 11:30:46.000000000 +0900 a/foobar -rw-rw-r-- 1 kamada.yuhei kamada.yuhei 0 2016-01-01 11:30:46.000000000 +0900 b/foobar |
同期されます。
a/foobar 11:30:46.000000000
b/foobar 11:30:45.999999999
1ナノ秒差でも同期されました。どのくらいタイムスタンプに差があるか、ではなく上記のように秒単位に違いがあると、ファイルが同期されることが確認できました。
(例外)タイムスタンプが秒精度に切り詰められない場合がある
以下のような場合にrsyncをすると、b/foobarのタイムスタンプが 11:30:48.00000000 に更新されるはずです。
1 2 3 |
$ ls --full a/foobar b/foobar -rw-rw-r-- 1 kamada.yuhei kamada.yuhei 0 2016-01-01 11:30:48.999999999 +0900 a/foobar -rw-rw-r-- 1 kamada.yuhei kamada.yuhei 0 2016-01-01 11:30:47.000000000 +0900 b/foobar |
しかし、rsyncを実行したタイミングのシステム時刻によってタイムスタンプが秒精度に切り詰められない現象が発生します。
1 2 3 4 5 6 7 8 9 10 11 |
$ sudo date -s "2016-01-01 11:30:48.0000000000" && rsync -av a/foobar b/foobar 2016年 1月 1日 金曜日 11:30:48 JST sending incremental file list #同期されたファイルのリストを表示 foobar #foobarが同期されている sent 99 bytes received 31 bytes 260.00 bytes/sec total size is 0 speedup is 0.00 $ ls --full a/foobar b/foobar -rw-rw-r-- 1 kamada.yuhei kamada.yuhei 0 2016-01-01 11:30:48.999999999 +0900 a/foobar -rw-rw-r-- 1 kamada.yuhei kamada.yuhei 0 2016-01-01 11:30:48.002000004 +0900 b/foobar |
rsyncをするとb/foobarのタイムスタンプが
b/foobar 11:30:48.00000000 ではなく、
b/foobar 11:30:48.00200004
となり期待とは違うタイムスタンプに更新されました。
おそらくパフォーマンスを意識しての実装です。
rsyncはファイル受信側に転送ファイルを書き込んだ後、タイムスタンプの更新を行います。このとき、受信側で転送時に書き込んだ時のタイムスタンプと送信側のファイルのタイムスタンプを比較します。比較の結果、タイムスタンプが秒精度で一致していた場合、タイムスタンプの更新処理を省略します。
つまりrsyncが受信ファイルを書き込んだ時の、タイムスタンプのままになる場合があります。そのため同期されたファイルのタイムスタンプが、必ず秒精度で切り詰められるわけではありません。
おわりに
便利なコマンドも理解して使わないとデータの損失や書き換えミスにつながることを学びました。特にLinux OSではファイルの操作や書き換えをする際は、コマンドの挙動を理解してから使用します。
また機会があれば、別のコマンドについてもご紹介させていただきます。