純規の暇人趣味ブログ

首を突っ込んで足を洗う

[Bash]rsyncの--link-destでの差分バックアップ

      2016/03/14    HimaJyun

自分の管理運営するマイクラ鯖のバックアップスクリプトを見直していた時、rsyncに「--link-dest」なるオプションがあるのに気付いた

このオプションを使えば指定したディレクトリを基準に差分バックアップを取る事ができ、高速化が望めそうだったので試してみました

rsync

rsyncはマトモなLinuxディストリなら普通に付属している超優秀なバックアップ用のコマンドです。

例えば、「hoge」ディレクトリがあるとして、その中身を「fuga」ディレクトリにコピー
数日経ってから再び「hoge」ディレクトリの中身を「fuga」ディレクトリにコピー

と言った動作を行った際に、変更のあったファイルだけを探し出してコピーしてくれます。

世代管理なら--link-dest

先程ご紹介したrsyncの差分バックアップ機能には少し弱点があります。
そうです、宛先が違う時です。

「hoge」ディレクトリを「fuga」ディレクトリにコピーする際には、中に何もないためまた全てのファイルをコピーします。
結果として、バックアップにとてもとてもとても長い長い長い時間が必要になります。

例えば、1日おきに「2016-03-13」、「2016-03-14」……などと新しいディレクトリを作成して保存して行く世代管理な方法を採用している場合などがそれです。

そこで登場したのが「--link-dest」オプション、このオプションでベースとなるディレクトリを指定すれば、そのディレクトリと比較して変化があったファイルのみコピー、変化のないファイルはハードリンクを行う、と言う動きをします。

こうする事で、ディスク容量を無駄にする事無く、かつ高速にバックアップを取る事が可能なのです。

ハードリンク

先程、ハードリンクと言う言葉が出たので軽く説明します。

ハードリンクとは、要は「hoge」と言うデータがあるとして、「fuga」と言うハードリンクを作成した際に、ディスク上の同じ領域を指し示す様にするのがハードリンクです。

プログラミング的に言えば、新しい「fuga」変数に、「hoge」変数が持つ参照(ポインタ)を代入するのと同じ(だと思う)です。

両方が同じデータを指し示すため、片方を書き換えればもう片方にも影響します。

これだとシンボリックリンクと変わらないのですが、ハードリンクの良い所は元データを削除しても問題ない事です。

ハードリンクはinode番号と言う物で管理され、普通にrmコマンドで削除したりした場合はこのinodeのリンクを解除する感じです。

inodeは何ヵ所からリンクされているかカウントしており、このカウントが0になった時に本当に削除されます。
そう、参照カウント型のガーベージコレクションみたいにね。

実際にやってみる

例えばですが、以下の様なバックアップスクリプト(と言うかただコマンド書いてるだけ)を毎日実行しているとして、これを先程の「--link-dest」を利用した形に置き換えたいと思います。

#!/bin/bash
rsync -a ${バックアップ元} ${バックアップ先}$(date +%Y-%m-%d)

以下の様な感じになります。

#!/bin/bash
rsync -a --link-dest=${基準にするディレクトリ} ${バックアップ元} ${バックアップ先}$(date +%Y-%m-%d)

こうすると、${基準にするディレクトリ}を見て、変更があったファイルのみコピー、変更のないファイルはハードリンク、と効率的な動作が行われます。

ちなみに、${基準にするディレクトリ}は相対パスだとややこしいので絶対パスで書くことをおススメします。

定期的にフルバックアップを取りたい

${基準にするディレクトリ}は前回のバックアップを指定したので良いのですが、「1週間に一度はフルバックアップを取りたい」と思うかも知れません。

その場合は以下の様にすれば良いのです。

#!/bin/bash
if [ $(date +%u) = 0 ]; then
 rsync -a ${バックアップ元} ${バックアップ先}$(date +%Y-%m-%d)
else
 rsync -a --link-dest=${バックアップ先}$(date +%Y-%m-%d --date "$(date +%u) days ago") ${バックアップ元} ${バックアップ先}$(date +%Y-%m-%d)
fi

$(date +%u)は曜日を数字で(日曜日を0として)返してくれるので、それをifで分岐、0ならフルバックアップ、それ以外なら差分バックアップです。

さて、このdays ago周りが意味不明かも知れないので説明すると、dateで表示する日付を$(date +%u)日前にしています。

$(date +%u)は曜日を数字で……なので、月曜日なら1日前、火曜日なら2日前、水曜日なら3日前……と、何曜日に実行しても日曜日をベースに差分を取得します。

なかなか良いアイデアだと思いません?、アイデアだけは……
本当はこっちが言いたかったんですよ、ただね、書いてる途中で「あれ、これ普通に前回のバックアップを指定したので良くね?」と言う至極当然の事に気付いてしまったのでこの仕掛けはオジャンになりました、乙!!

前回のバックアップを基準にする

と言う訳で、前回のバックアップを基準に差分を取りたい場合は以下の様にすると良いでしょう。

#!/bin/bash
previous=$(ls -1t ${バックアップ先} | head -1)
if [ -z "${previous}" ]; then
  rsync -a ${バックアップ元} ${バックアップ先}$(date +%Y-%m-%d)
else
  rsync -a --link-dest=${バックアップ先}${previous} ${バックアップ元} ${バックアップ先}$(date +%Y-%m-%d)
fi

$(ls -1t ${バックアップ先} | head -1)はディレクトリ内のファイルを更新日時が新しい順でファイル名だけ取り出し、その中の1行目を切り出しです。

一応、空のディレクトリで実行した時用に空だったら普通にコピーする様にしています。

更新日時が最新のものを基準にするので、常に最小限のコピーで済みます。
--link-destを指定しただけで、今まで毎回数Gをコピーしてたのがほんの少しのコピーで済むようになるかも知れません。

そうなればディスクに与える負荷も減りますし、より高速にバックアップが出来ます。
使わない理由が無いので、ぜひ使った方が良いでしょう。

 - サーバ運営 ,