rsync で pdumpfs みたいなことをする
いままで履歴つきのバックアップは pdumpfs*1 でとっていたのですが,rsync のオプション(--link-dest
)を使うと同等のことをできるらしいと知りました。
- バックアップにrsync --link-destを使うと良い場合もあるよ | rutoの日記 | スラド
- rsyncで差分バックアップを行うための「--link-dest」オプション - ITmedia エンタープライズ
サンプル
バックアップ元のファイル群をサンプルとして作成します。
$ mkdir -p work/src/foo $ echo "baz" > work/src/foo/bar
これで,
- work/ - work/src/ - work/src/foo/ - work/src/foo/bar
のような構造ができました。これのバックアップをとっていきます。
まずは普通に rsync
履歴つき(差分)バックアップをとろうにも,まずはフルバックアップがないとお話にならないので,初回はふつうに rsync でとります((じっさいは後述するように --link-dest
は対象ディレクトリの存在有無は確認しないので,この段階から --link-dest
を指定してもいいのですが。))。
$ mkdir -p work/dst $ rsync -avv --delete work/src/ work/dst/1/ building file list ... done created directory work/dst/1 deleting in . delta-transmission disabled for local transfer or --whole-file ./ foo/ foo/bar total: matches=0 hash_hits=0 false_alarms=0 data=4 sent 163 bytes received 54 bytes 434.00 bytes/sec total size is 4 speedup is 0.02
ソースディレクトリとなる work/src/
の末尾にスラッシュ(/
)をつけ忘れないよう注意。
地雷だらけのrsyncを理解する。 - こせきの技術日記
- SRCの末尾に
/
をつける。たいてい必要。
- SRCスラッシュの有無は、
mv SRC DEST
とmv SRC/* DEST
の違いと一緒。スラッシュの後ろに*
が省略されているものと考える。- DESTのスラッシュの有無は関係なし。
この説明がとてもしっくりきました。
上記引用のように宛先ディレクトリ(DEST)の末尾スラッシュは必要ありません。が,対称性を重視していちおうこちらにもつけてあります。ちなみに,上記のように work/dst/1/
がターゲットディレクトリとなる場合,work/dst/
までのディレクトリは存在している必要があります(なので事前に作成している)。ターゲット自身となる 1/
ディレクトリについては,存在しない場合に rsync が作成してくれます。mkdir -p
的にはやってくれないってことですね。
いよいよ --link-dest
つきで rsync
ベースとなるフルバックアップがとれたので,いよいよ差分バックアップをおこないます。
めんどうなのでファイルの追加・編集はとりあえずなしで。
$ rsync -avv --delete --link-dest=../1 work/src/ work/dst/2/ building file list ... done created directory work/dst/2 deleting in . delta-transmission disabled for local transfer or --whole-file ./ foo/ foo/bar is uptodate total: matches=0 hash_hits=0 false_alarms=0 data=0 sent 120 bytes received 39 bytes 318.00 bytes/sec total size is 4 speedup is 0.03
ログをみるとわかりますが,
foo/bar is uptodate
のように,bar
というファイルはすでに最新版であると判断されています(work/dst/2/
にはもともと存在していなかったのにね)。
実際にハードリンクされているかどうか,i-node 番号をみてみましょう。
$ find work/dst -ls 3605251 4 drwxr-xr-x 4 dayflower users 4096 May 13 11:08 work/dst 3605252 4 drwxr-xr-x 3 dayflower users 4096 May 13 11:07 work/dst/1 3605254 4 drwxr-xr-x 2 dayflower users 4096 May 13 11:07 work/dst/1/foo 3605256 4 -rw-r--r-- 2 dayflower users 4 May 13 11:07 work/dst/1/foo/bar 3605257 4 drwxr-xr-x 3 dayflower users 4096 May 13 11:07 work/dst/2 3605258 4 drwxr-xr-x 2 dayflower users 4096 May 13 11:07 work/dst/2/foo 3605256 4 -rw-r--r-- 2 dayflower users 4 May 13 11:07 work/dst/2/foo/bar
foo/bar
の i-node 番号は 1/
下も 2/
下も 3605256
となっており,同一です。またリンクカウントも 2 になっています。
ディレクトリエントリ(foo/
)の i-node 番号は異なりますが,これは Unix のファイルシステムではディレクトリにハードリンクは貼れないためです。
なお,ソースディレクトリ側の i-node 番号をみてみますと,
$ find work/src -ls 3605248 4 drwxr-xr-x 3 dayflower users 4096 May 13 11:07 work/src 3605249 4 drwxr-xr-x 2 dayflower users 4096 May 13 11:07 work/src/foo 3605250 4 -rw-r--r-- 1 dayflower users 4 May 13 11:07 work/src/foo/bar
3605250
となっており,異なります。これはローカルであろうと実体コピーをするという rsync の仕様です。そもそもバックアップ用途ですしね。
--link-dest
を相対パスで指定するときの注意
さきほど --link-dest=../1
のように指定しました。普通の感覚だと --link-dest=work/dst/1
のように,カレントディレクトリからの相対パスで書きそうなものですが,
rsyncで差分バックアップを行うための「--link-dest」オプション - ITmedia エンタープライズ
--link-dest
の利用で特に注意が必要なのは、--link-dest
で指定するディレクトリは、コピー先ディレクトリからの相対パスで指定する必要があるということです。
If DIR is a relative path, it is relative to the destination directory.
rsync (1)
のように,相対パスで指定する場合は,宛先ディレクトリからの相対パスで記述する必要があるのです*2。--link-dest=1/
でもないことに注意。
(なお ITmedia の記述では相対パスで書かなきゃいけないように感じますが,絶対パスで指定することもできます; インタラクティブに実行する場合などは,絶対パスで指定しておくと間違いがないかと思います)
カレントディレクトリからの相対パスで実際にやってみましょう。
$ rm -rf work/dst/2 $ rsync -avv --delete --link-dest=work/dst/1 work/src/ work/dst/2/ building file list ... done created directory work/dst/2 deleting in . delta-transmission disabled for local transfer or --whole-file ./ foo/ foo/bar total: matches=0 hash_hits=0 false_alarms=0 data=4 sent 163 bytes received 54 bytes 434.00 bytes/sec total size is 4 speedup is 0.02
先ほどと異なり,
foo/bar is uptodate
とはなりませんでした。これは,--link-dest
オプションが指定されなかった場合の挙動と同じです。つまり work/dst/2/
が空なので,まるごとコピーしようとしているということです。
i-node 番号を確認してみます。
$ find work/dst -ls 3605251 4 drwxr-xr-x 4 dayflower users 4096 May 13 11:08 work/dst 3605252 4 drwxr-xr-x 3 dayflower users 4096 May 13 11:07 work/dst/1 3605254 4 drwxr-xr-x 2 dayflower users 4096 May 13 11:07 work/dst/1/foo 3605256 4 -rw-r--r-- 1 dayflower users 4 May 13 11:07 work/dst/1/foo/bar 3605257 4 drwxr-xr-x 3 dayflower users 4096 May 13 11:07 work/dst/2 3605258 4 drwxr-xr-x 2 dayflower users 4096 May 13 11:07 work/dst/2/foo 3605259 4 -rw-r--r-- 1 dayflower users 4 May 13 11:07 work/dst/2/foo/bar
見事に i-node 番号が異なって(3605256
⇔ 3605259
)いますね。リンクカウントも双方 1 となっていますし。
以上のように,--link-dest
で指定したディレクトリが存在しなかった場合でも,rsync はなんの文句もいわずに実行してしまいます。気をつけましょう。
その代わり,フルバックアップの際と差分バックアップの際とで同一のコマンドラインを使うことができます(具体例は後述します)。
なお rsync 2.6.4 以降では,--link-dest
には複数のディレクトリを指定することができます。そのようにすると,指定した順に同一のファイルを探し,みつかったところにハードリンクを貼ることになります。適用可能な局面がちょっと思いつきませんが。
注意しておくこと
ハードリンクを利用しているので履歴を保存していってもあまり容量を食わないというメリットがある半面,デメリットももちろん存在します*3。
i-node が枯渇する
さきにも触れましたが,ディレクトリエントリ自体にハードリンクは貼れません。このため,たとえ変更がなくても履歴をどんどんとっていくと,ディレクトリエントリの数だけ i-node が消費されていきます。
もちろんファイル実体と異なり,ディレクトリエントリに占めるディスク容量なんてたいしたことはありません。しかし,i-node の領域を静的に確保しているファイルシステム(ext2/3/4)では,ディスク容量が枯渇するよりさきに,i-node が枯渇してしまいます。
なお,XFS や ZFS など,よりモダンなファイルシステムでは i-node 用領域は動的に確保されるので,i-node が枯渇することはありません((といっても i-node 番号の数値がいつかは枯渇するかもしれません。これは OS 側だと ino_t
という型のサイズによります。手元の 32bit 環境(Linux 2.6.18 i686)では 32bit でした。ファイルシステム側はファイルシステムによるんだと思います。ext3 では 32bit ぽい。))。⇒ ファイルシステム諸元 - 詳解ファイルシステム
バックアップしたファイルを書き換えると……
バックアップされたファイルはハードリンクされているので,(変更がない限り)同じ実体をさしています。ですから,この実体を書き換えると,(同一のハードリンクとなる)履歴すべてに影響がでます。
ちょっと実験してみましょう。
$ echo "foo" > foo.txt $ ls -i foo.txt 3605239 foo.txt $ echo "bar" >> foo.txt $ ls -i foo.txt 3605239 foo.txt $ echo "baz" > foo.txt $ ls -i foo.txt 3605239 foo.txt
シェル上からファイルの「追記」や「上書き」をしてみましたが,i-node 番号は変わりませんでした(なお vi など一般的なエディタ等のアプリケーションで内容を書き換えると,コピーをとってから変更しているので i-node 番号が変わっていきます)。
もしファイルシステムが一部壊れた,とか,ウィルスに感染して一部ファイルがかわってしまった,とか,悪い人がバックアップファイルを直接いじった,とかいうことがあると「履歴」の部分はまったく強みをもちません。
その他利点と欠点について [http://slashdot.jp/~ruto/journal/362588:title] がとてもよくまとまっているので一読をおすすめします。
おまけ
4 世代の履歴つきバックアップをとるシェルスクリプトです。
#!/bin/sh if [ $# -lt 2 ]; then echo "usage: $0 <SOURCE> <DESTINATION>" exit 1 fi force_trailing_slash() { case $1 in */) echo -n "$1" ;; *) echo -n "$1/" ;; esac } SRC=`force_trailing_slash $1` DST=`force_trailing_slash $2` [ -d "${DST}3" ] && rm -rf "${DST}3" [ -d "${DST}2" ] && mv "${DST}2" "${DST}3" [ -d "${DST}1" ] && mv "${DST}1" "${DST}2" [ -d "${DST}latest" ] && mv "${DST}latest" "${DST}1" [ -d "${DST}latest" ] || mkdir -p "${DST}latest" #[ -d "${DST}1" ] && \ # missing --link-dest dir will be ignored LINK_DEST="--link-dest=../1" echo "rsync -vvaHz --delete $LINK_DEST ${SRC} ${DST}latest/" exec rsync -vvaHz --delete $LINK_DEST "${SRC}" "${DST}latest/"
rm -rf "${DST}3"
の部分が,旧来の差分バックアップの観点からするとぎょっとしますが,ハードリンクの特性上,削除してしまっても構わないんです。
pdumpfs のように日付入りのフォルダ名にするには結局なんらかのスクリプトを書く必要がでてくると思います。が,単一の(C で書かれた)プログラムで更新状況を確認しつつガッとコピーしてくれるので,こちらのほうが pdumpfs より軽いのではないかなぁと思います。もちろんローカルなら pdumpfs どころか cp -al
でもいいんでしょうけど,リモートのファイル群をバックアップするならどうせ rsync することになるので,一つのコマンドで完結しているのはうれしい。