純規の暇人趣味ブログ

首を突っ込んで足を洗う

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

      2017/05/02    HimaJyun

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

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

rsync

流石にrsyncを知らない人は居ないだろ、ということでこれの説明は省略

rsyncは、例えば、「hoge」ディレクトリがあるとして、その中身を「fuga」ディレクトリにコピー

数日経ってから再び「hoge」ディレクトリの中身を「fuga」ディレクトリにコピー

みたいにした場合に、変更のあったファイルだけを探し出してコピーしてくれます。(--deleteオプションを付ければ削除されたファイルを消すことも可能=同期)

世代管理なら--link-dest

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

そうです、宛先が違う時です。

先ほどの例では「hoge」を「fuga」に移す、という動作でしたが、これが「piyo」に移す、だとpiyoは中身が空のため全てのファイルをコピーします。

結果として、バックアップにとてもとてもとても長い長い長い時間が必要になります。

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

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

こうする事で、ディスク容量を無駄にする事無く、なおかつ変更されたファイルのみをコピーするので高速にバックアップを取る事が可能なのです。

ハードリンク

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

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

プログラミング的に言えば、「hoge」変数の参照(ポインタ)を「fuga」変数に代入するのと同じ(のはず)です。

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

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

ハードリンクは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)

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

ちなみに、${基準にするディレクトリ}は相対パスだとややこしいので絶対パスで書く方が分かりやすいはず

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

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

#!/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行目を切り出しです。(findとか使えばもっとシンプルに書けるかも)

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

更新日時が最新のものを基準にするので、常に最小限のコピーで済みます。

--link-destを指定しただけで、今まで毎回数Gをコピーしてたのがほんの少しのコピーで済むようになるかも知れません。

そうなればディスクに与える負荷も減りますし、より高速にバックアップが出来ます。

使わない理由が無いので、ぜひ使った方が良いでしょう。

 - サーバ運営