lsyncd と rsnapshot でリアルタイム Time Machine 風バックアップを組もう

Apple 社の [Time Machine](https://ja.wikipedia.org/wiki/Time_Machine_(%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2)) に似た増分バックアップの仕組みに、さらにリアルタイム性を付加したバックアップシステムについて検討します。用途はホームディレクトリの世代バックアップやサーバ上の重要なデータの保全などです。

思考実験以上・運用レポート以下といったところです、長期の本運用はしていないため何か落とし穴があるかもしれません。

目指すこと
-------------

「データのリアルタイムなコピー（レプリカ）をローカルに保全」しつつ、かつそれの「１時間ごとの世代バックアップを作成」することを目指します。

典型的には、ローカルマシンにマウントした外付けディスクや NAS をバックアップ先に指定することになるでしょう。別マシンへ直接バックアップする方法についてものちほど検討しますが、ひとまずローカルディスクに保存し、そのデータをさらに他のサーバへ定期的にコピーする運用を考えます。

![全体の構成図・概略](https://qiita-image-store.s3.amazonaws.com/0/1298/fc4e7ecb-9f1d-9c54-b28f-3f91eca62ca5.png )

* 変更のあったファイルを数秒の遅れで別ディスクに保全する
  * 変更のあったファイルの増分のみ保存し容量を節約する 
  * 容量の大きいファイルや特定のディレクトリを除外する
  * 古いバックアップファイルを自動的に消し込む
* Time Machine 風の世代バックアップを行う
  * 世代の間隔や数を自由に設計する
  * 任意の長期間で保存する（設定上の上限なし）

これらの機能を `lsyncd` と `rsnapshot` という２つのソフトウェアを組み合わせて実現します。

lsyncd とは
-----------

[lsyncd (Live Syncing Daemon)](https://axkibe.github.io/lsyncd/) は、指定したディレクトリ以下に含まれるファイルを [inotify (Linux) や kqueue (BSD)](https://www.ibm.com/developerworks/jp/linux/library/l-ubuntu-inotify/) を使ってリアルタイムにウォッチし、更新されたファイルがあれば**即時に**転送を開始するツール です。分単位のダウンタイムすら許容できない状況に威力を発揮する、攻めのツールと言えます。

デーモンとして常駐し、裏では `rsync` を使ってファイルを同期します。データを他のマシンへ転送したり、Amazon S3 に上げることなども可能です。ただし、今回はローカルマシン上のファイルコピーに使います。

なお、動作が若干不安定という話もあるようですから、定期的に再起動したり動作をチェックするような仕組みを導入することを検討するとよさそうです。

rsnapshot とは
-------------

[rsnapshot](http://rsnapshot.org/) は、macOS の Time Machine に似た設計思想に基づいた[増分バックアップ](https://ja.wikipedia.org/wiki/%E5%A2%97%E5%88%86%E3%83%90%E3%83%83%E3%82%AF%E3%82%A2%E3%83%83%E3%83%97)システムを実現するためのスクリプトです。

Time Machine の設計とは、まず初回実行時にフルバックアップを取得し、以降は１時間毎に前回から変更されたファイルのみバックアップを取り、３０日を過ぎたバックアップは週ごとに１つに間引いて、ディスクの容量がある限りデータを保存する方式です。

スクリプトは Perl で書かれており、mkdir/mv 等の基本的なコマンドと rsync を組み合わせて cron で実行する、シンプルな動作が特長です。バックアップされたそれぞれのファイルはハードリンクとして存在し、変更があったファイルのみ実ファイルとして保持されます。

### rdiff-backup との比較

同じような目的のソフトウェアに [rdiff-backup](http://rdiff-backup.nongnu.org/) がありますが、以下の欠点のため今回は採用しません。

* rsync コマンドを直接呼び出さないので、細かい動作の指定ができない
* 過去のデータを取り出すのにコマンドを実行する必要がある
* 古いデータを消し込むタスクを別途仕込む必要がある

対して `rsnapshot` は Unix コマンドを組み合わせて実現しているのでシンプルで分かりやすい作りになっています。問題が起きても調査が容易な点は、安定した長期運用を目指す際に大いにプラスとなります。

環境を構築する
-------------------

### lsyncd をインストールする

yum や brew で入ります。

	yum install lsyncd

デーモンとして動作するので、今回は root 権限で起動し、設定ファイルはグローバルのものを使います。

### rsnapshot をインストールする

同じく yum や brew で入ります。perl が必要です。

	yum install rsnapshot

cron からコマンドを呼び出す形で使用します。今回は各ファイルのオーナーなどの情報を保持したいので root 権限で起動し、設定ファイルはグローバルのものを使います。

### 監視ファイルの上限を緩和する

Linux では上限数がそれほど多くないので、あらかじめ上限を緩和しておくとよいと思います。`/etc/sysctl.conf` に書き込むのも忘れずに。

	sysctl -w fs.inotify.max_user_watches=65536

他の OS の対応方法は見つけられませんでした。FreeBSD の [kqueue(2)](https://www.freebsd.org/cgi/man.cgi?query=kqueue&sektion=2) まわりを調べる必要があるかもしれません。情報をお待ちしています。

OS が同時に開けるファイル数もチェックしたほうがよいでしょう。linux なら `fs.file-max`、macOS なら `kern.maxfiles` や `kern.maxfilesperproc` の[ようです](http://krypted.com/mac-os-x/maximum-files-in-mac-os-x/)。

システムの構成を設計する
------------------------------

ここでは、ローカルマシンのディレクトリ `/app/myproject` 以下を、同じくローカルマシンの `/mnt/rsnapshot` 以下にバックアップすることにしましょう。`/mnt` は別のファイルシステム（外付け HDD、NAS 等）をマウントする想定ですが、もちろん同一ディスク上でも（冗長性は下がりますが）運用やテストは可能です。

![ローカルの構成図](https://qiita-image-store.s3.amazonaws.com/0/1298/5eafc156-7201-5a15-1e57-d88987c854fa.png)

### 確保すべき空き容量

初回バックアップ時には、`/app/myproject` の**２倍の容量**が必要です。これは `lsyncd` によるリアルタイムバックアップ用の領域 (a) と、`rsnapshot` の実ファイル格納用の領域 (b) がそれぞれ別に必要だからです。

* (a) /mnt/rsnapshot/current/
* (b) /mnt/rsnapshot/.sync/

さらに日々の増分の保持に必要となるディスク容量は、どう運用したいかによって大きく変わります。

* どの程度頻繁にファイルを更新するか（また、それらのファイル容量）
* データを何ヶ月・何年くらい保持したいか

もしあまりデータを更新しないのであれば 1.5 倍程度あればひとまず試すには充分かと思います。実運用される場合は、ディスクが満杯にならないよう要監視です。

まとめると、もし `/app/myproject` が 10GB あるなら、`/mnt` は少なくとも 30GB は確保したいところです。長期運用するなら大ざっぱに 100GB くらいでしょうか。

lsyncd を設定する
-----------------------

`rsnapshot` のバックアップ先にデータを同期することになるので、まず転送先にディレクトリを作ります。

	mkdir -p /mnt/rsnapshot/current/app/myproject/

設定ファイルはデフォルトで `/etc/lsyncd.conf` です。ちなみに [Lua 互換の形式](https://www.lua.org/manual/5.3/manual.html#3)です。

	vi /etc/lsyncd.conf

ここでは root で実行することを前提としています。

```lsyncd.conf
settings {
  logfile = "/var/log/lsyncd.log",
  statusFile = "/tmp/lsyncd.status",
  maxDelays = 1,
}

sync {
  default.rsync,
  source = "/app/myproject/",
  target = "/mnt/rsnapshot/current/app/myproject/",
  delete = true,
  rsync = {
    archive  = true,
    hard_links = true,
  }
}
```

sync セクションは複数書くことできます。詳しいオプションは[マニュアル](http://axkibe.github.io/lsyncd/manual/config/layer4/)をどうぞ。

### 削除の扱い

もし手元で削除したファイルも残しておきたいなら `delete = false` を指定します。より安全ですが、たまたま間違えて手元にコピーした巨大なファイルが残り続けるかもしれません。基本的には true でよいでしょう。実運用する時は `delete = running` を指定することを[検討してみて下さい](https://cloudpack.media/11060)。

### 起動する

	systemctl start lsyncd

手元で動作をみたい場合は settings セクションに  `nodaemon = true` を指定するか、あるいは次にように実行します。

	lsyncd -nodaemon /etc/lsyncd.conf	

rsnapshot を設定する
---------------------------

設定ファイルはデフォルトで `/etc/rsnapshot.conf` です。brew で入れた方は `/usr/local/etc/rsnapshot.conf` ですが、うまく読み込んでくれない場合は都度 `-c /usr/local/etc/rsnapshot.conf` などと指定してください。

	vi /etc/rsnapshot.conf

ここでは root で実行することを前提としています。個人のホームディレクトリ以下が対象なら一般ユーザでも動かせると思います、その場合はログファイルの場所などを調整してください。

```rsnapshot.conf
config_version	1.2
snapshot_root	/mnt/rsnapshot/

cmd_rsync	/usr/bin/rsync
cmd_rsnapshot_diff	/usr/bin/rsnapshot-diff

retain	hourly	24
retain	daily	7
retain	weekly	4
retain	monthly	12
retain	yearly	10

logfile	/var/log/rsnapshot.log
lockfile	/var/run/rsnapshot.pid

rsync_short_args	-a
rsync_long_args		--delete-after --numeric-ids --relative --delete-excluded
link_dest	1
sync_first	1

backup	/mnt/rsnapshot/current/app/myproject/	myproject
```

項目の区切りはスペースではなくタブのみ使えます。[マニュアル](http://rsnapshot.org/rsnapshot/docs/docbook/rest.html#modifying-the-config-file)も参照してみてください。

なお、`backup` はいくつでも指定できます（`lsyncd.conf` の sync セクションと合わせてください）。

世代を設計する
-------------------

上記の設定だと、１時間毎に前回からの変更点のバックアップを取り、そのうち最古の１つを残し「その日」を代表するバックアップとすることで毎日のバックアップとし、そのうち最古の１つを残し「その週」を代表するバックアップとし……　という仕組みで、直近であるほどたくさんのバックアップを残し、最終的に１年に１つの代表データを保持することになります。

これを超えたデータは最終世代の実行時、つまり `rsnapshot yearly` によって消去されます。

### Time Machine に合わせる例

次のようになるでしょう。個人のデータ（とくにクリエイティブ系）が対象だとこちらのほうが実用的かもしれませんね。

```rsnapshot.conf
retain	hourly	24
retain	daily	30
retain	weekly	520    # １０年分
```

ただし、実際の Time Machine と違って、**ディスク容量がいっぱいになる前に古いものが自動的に消えたりはしない**ので weekly の数は慎重に調整してください。

### 世代を新設する例

たとえば季節ごとに年４回保存する世代を間に挟むこともできるでしょう。なお、`retain` を書く順序が大切ですのでこの場合は必ず `monthly` と `yearly` の間に入れて下さい。

```rsnapshot.conf
retain	seasonally	4
``` 

そして、３か月に１回 `rsnapshot seasonally` を実行するよう cron に仕込みます（後述）。早い話、cron で実行する間隔にマッチしていればいいわけです。

データの同期をテストする
--------------------------------

これで cron を仕込めば稼働はしますが、まずは手作業で動作を理解するのがよいでしょう。

テスト用のフラグ `-t` を指定して、同期時に実際にどういったコマンドが実行されるのかチェックします。

	rsnapshot -t sync

```stdout
echo 75394 > /var/run/rsnapshot.pid
mkdir -m 0755 -p /mnt/rsnapshot/.sync/
rsync -a --delete-after --numeric-ids --relative \
    --delete-excluded /mnt/rsnapshot/current/app/myproject/ \
    /mnt/rsnapshot/.sync/myproject
touch /mnt/rsnapshot/.sync/
```

（注：コマンド形式で出力されますが、これとまったく同一のコマンドが発行されるわけではなく、実際には同等の動きをする Perl のコードが実行されます。つまり論理的な動きを表している、というわけです。以下同じ）。

問題なさそうなら、初回の同期を実行してみます。ついでにデバッグフラグ `-v` を付けましょう。全データをコピーしますから、初回はかなり時間がかかるかもしれません。

	rsnapshot -v sync

無事に完了すれば `/mnt/rsnapshot/.sync` の中にデータがコピーされているはずです。


世代のローテートをテストする
------------------------------------

最小単位（＝設定ファイルにある複数の `retain` 行の一番上の世代）である `hourly` を実行してみます。

	rsnapshot -v hourly

```stdout
echo 75977 > /var/run/rsnapshot.pid
mv /mnt/rsnapshot/.sync/ /mnt/rsnapshot/hourly.0/
rm -f /var/run/rsnapshot.pid
```

`.sync` が mv され `hourly.0` ディレクトリになりました。

次に、バックアップ元の `/app/myproject/` の中を適当に変更してから、再度手動で実行してみます（なお `rsnapshot sync` した結果に変更がなければディレクトリ作成はスキップされます）。

その前に忘れず `sync` しましょう。

	rsnapshot -v sync && rsnapshot -v hourly

出力からローテートのところだけ抜き出しました。

```stdout
mv /mnt/rsnapshot/hourly.0/ /mnt/rsnapshot/hourly.1/
mv /mnt/rsnapshot/.sync/ /mnt/rsnapshot/hourly.0/
```

上記のステップがあと２２回繰り返されて  `hourly.23` ディレクトリが作成されると次のような動きになります。

```stdout
mv /mnt/rsnapshot/hourly.22/ /mnt/rsnapshot/hourly.23/
mv /mnt/rsnapshot/hourly.21/ /mnt/rsnapshot/hourly.22/
mv /mnt/rsnapshot/hourly.20/ /mnt/rsnapshot/hourly.21/
...
mv /mnt/rsnapshot/hourly.2/ /mnt/rsnapshot/hourly.3/
mv /mnt/rsnapshot/hourly.1/ /mnt/rsnapshot/hourly.2/
mv /mnt/rsnapshot/hourly.0/ /mnt/rsnapshot/hourly.1/
mv /mnt/rsnapshot/.sync/ /mnt/rsnapshot/hourly.0/
```

次の世代である `daily` が動作できる状況になります。`sync` は `hourly` が一時間に一度実行すれば充分ですから、コマンドは単に次のようになります。

	rsnapshot -v daily

```stdout
mv /mnt/rsnapshot/hourly.23/ /mnt/rsnapshot/daily.0/
```

シンプルな仕組みであることがよくわかります。実行した時点でもっとも古いものが `hourly` 世代の代表として `daily` 世代へと「引き渡され」るわけですね。

rsnapshot のタスクを登録する
--------------------------------------

上記の動きをふまえ、次のような設定を cron に仕込みます。ログの出力先などは適宜変更してください。

	vi /etc/cron.d/rsnapshot

```/etc/cron.d/rsnapshot
10 * * * * root /usr/bin/rsnapshot sync && /usr/bin/rsnapshot hourly >> /mnt/rsnapshot/cron.log 2>&1
15 3 * * * root /usr/bin/rsnapshot daily   >> /mnt/rsnapshot/cron.log 2>&1
20 3 * * 0 root /usr/bin/rsnapshot weekly  >> /mnt/rsnapshot/cron.log 2>&1
25 3 1 * * root /usr/bin/rsnapshot monthly >> /mnt/rsnapshot/cron.log 2>&1
30 3 1 1 * root /usr/bin/rsnapshot yearly  >> /mnt/rsnapshot/cron.log 2>&1
```

Time Machine と同じ世代設計にしたい場合は、もちろん monthly や yearly の行は必要ありません。

特に日々の作業領域をバックアップする場合は、`daily` 以下の世代は実行する時間や曜日を注意深く設計して下さい。もっともデータの変更がなさそうな落ち着いた時間や曜日のバックアップを次の世代に引き渡すことで、たとえば日中にデスクトップに誤って作成した（そして即時コピーされてしまった）一時的で巨大なファイル等を持ち回らなくて済む確率が上がるはずです。

ついでに [whenever](http://morizyun.github.io/blog/whenever-gem-rails-ruby-capistrano/) をお使いの方のための例も[置いておきます](https://gist.github.com/ryochin/7be534d61c26b43d694349e4e9e44642)。

別サーバへのバックアップについて検討する
----------------------------------------------------

今回の構成では、別マシン上への直接のバックアップ作成は仕組み上できません（が、のちほど検討します）。`rsnapshot` は *pull* が前提のツールであるのに対し`lsyncd` は *push* が前提のツールであるため、両者の設計思想が見事にバッティングするためです。

ローカルマシン（の環境）が物理・論理問わず吹っ飛んだ時のためにリアルタイムバックアップをしたい場合がほとんどでしょうから、どのように他のマシン（ここではバックアップマシンと呼びます）へデータを転送し冗長性を確保するかが大きな課題となります。

他のマシンへデータをなるべくリアルタイムに転送するためのいくつかアイデアを挙げておきます。

### 定期的に別サーバへ転送する（rsync 版）

単純にバックアップデータディレクトリ `/mnt/rsnapshot` 以下 をなるべく短い間隔で別途 rsync を走らせて他のサーバへ転送する方法です。若干消極的で冗長ですが、シンプルで安全確実です。

```/etc/cron.d/backup-rsnapshot-repository
*/3 * * * * root /usr/bin/nice -10 /usr/bin/rsync -azq --del --hard-links /mnt/rsnapshot/ you@exmaple.com:/path/to/backup/dir/
```

キモは `--hard-links` フラグの指定で、これを省くと２０倍くらいの（ハードリンクではなく）実データが転送されてしまいます。反面、結果的に冗長性はすごく確保できるのでディスク障害によるデータロストにはめっぽう強くなるでしょうから、データの重要度によってはそういった運用もアリかもしれません。

同期元（ローカルマシン）上でローテートされる関係上、`--del` スイッチを付けておかないと整合性が取れなくなるような気がします。いずれにしてもバックアップの総容量はローカル側で調整すべきでしょうから、付けておくほうがよいと思います（未検証）。

### 定期的に別サーバへ転送する（lsyncd 版）

もちろん、どうせならこの転送自体も `lsyncd` に行わせ、ダウンタイムを短くすることも可能です。`lsyncd.conf` に新しい `sync` セクションを追加します。

```lsyncd.conf
sync {
  default.rsyncssh,
  source = "/mnt/rsnapshot/",
  host = "you@exmaple.com",
  targetdir = "/path/to/backup/dir/",
  delete = true,
  ssh = {
    identityFile = "/home/you/.ssh/id_rsa",
  },
  rsync = {
    binary   = "/usr/bin/rsync",
    archive  = true,
    hard_links = true,
  }
}
```

root で実行する想定なので、`identityFile` で一般ユーザの秘密鍵を指定しています。

ただ、個人的には安定性を考えると上記の rsync 版を採用するほうが良いようにも思います。というか、どうせならどちらも仕込んでおくのが賢い運用となりそうです。

なお、current ディレクトリを `exclude` ディレクティブで省かないでください。ハードリンクの関係か、データが正しくコピーされないようです。

### バックアップマシンからデータを取り出す

過去の（つまり `hourly.1` 以降の）ファイルを参照する場合は、ふつうにアクセスすればよいので問題ありません。すべての世代ディレクトリ以下は整合性が取れているはずです（厳密には、バックアップ用の rsync/lsyncd プロセスが起動から１時間以内に完了していることが前提）。

問題は、最新のファイルを取り出したい時です。データのバックアップ転送のプロセスが完了しない限り、バックアップマシン上の最新のデータは整合性が取れていない状態にある可能性が高いと推測できます。

バックアップマシン上から**最新の**データ一式を**整合性を保った状態で**サルベージする時には、次のような手順を踏むべきでしょう。

#### ローカルマシンが生きている場合

- 転送のプロセスの起動設定をオフにする
  - rsync なら cron を停止
  - lsyncd なら設定ファイルの該当行をコメントアウトして再起動
- 転送のプロセスが動作していないことを確認する（あるいは kill で落とす）
- 転送のプロセスを手動で実行し、正常に完了することを確認する
- データを取り出す
-  転送のプロセスの起動設定をオンにする

`hourly.0` から取り出します。

#### ローカルマシンが吹っ飛んだ場合

`hourly.0` の整合性が取れている保障が得られなくなったわけですから、安全性からみると `hourly.1` から取り出すことになるでしょう。

運用する
-----------

### 特定のファイルを対象から除外したい

例えば個々のファイルのサイズにより制限したい時は `lsyncd.conf` の rsync セクションに `--max-size` を指定します。ほかにも rsync コマンドが受け付けるものならその多くは指定できるようです。

```lsyncd.conf
  rsync = {
    ...
    _extra = {
      ...
      "--max-size=102400000",
    }
  }
```

### 特定のディレクトリを対象から除外したい

`lsyncd.conf` の exclude セクションに指定できます。

```lsyncd.conf
  exclude = {
    "secrets/",
    "downloads/"
  }
```

### 同期 cron タスクの多重起動を防止したい

データの同期に要する時間が最小ローテート間隔（上記の設定では１時間）を上回るとタスクが多重起動しやっかいな状況に陥るので、これを防止します。

いくつか方法があると思いますが、私は timeout(1) と、[daemontools](http://cr.yp.to/daemontools.html) に含まれる [setlock](http://www.emaillab.org/djb/tools/daemontools/setlock.html) を組み合わせて使っています。

```
15 3 * * * root /usr/bin/timeout --preserve-status --kill-after=5m 3600 /usr/local/bin/setlock -nx /var/tmp/rsnapshot_daily.lock /usr/bin/rsnapshot daily >> /mnt/rsnapshot/cron.log 2>&1
```

実際には、毎時の同期に常に３０分以上かかるような状況で運用するのはやめたほうが安全でしょう。

### バックアップが正しく取れているかどうか監視したい

Nagios で監視する例を検討します。

#### lsyncd

実際にファイルが転送されているかどうかを日々チェックしたいわけですが、これがなかなかやっかいです。なにしろファイルが変更されない限りはなにも転送されない（のが正しい動作）わけですから、必ず定期的に更新されるファイルの最終更新時間をチェックするほかありません。

ホームディレクトリ以下をバックアップするなら、アイデアとして次のようなファイルが使えそうです。

* ~/.bash_history
* ~/Desktop/.DS_Store

これらの最終更新時刻をチェックすればひとまずよさそうです。が、信頼性は低いですね。

	/path/to/check_file_age -w 43200 -c 86400 /mnt/rsnapshot/current/app/myproject/something

確実性を求めるのなら、面倒ですが５分に１回くらい適当なファイルを更新するタスクを cron に仕込んで、そのファイルを監視する手が考えられます。容量をケチるために 0 バイトのファイルにしましょう。

```/etc/cron.d/lsyncd-touch
*/5 * * * * you /bin/touch /app/myproject/latest
```

	/path/to/check_file_age -w 330 -c 600 /mnt/rsnapshot/current/app/myproject/latest

その他の手としては、`/mnt/rsnapshot/current/app/myproject/` 以下を find(1) を使って最近更新されたファイルがあるかどうか探す方法も考えられますが、あまりスマートではありませんね。

#### rsnapshot

方針としては、`hourly.0` ディレクトリの作成時刻が約１時間以内であれば、`rsnapshot`のローテートが動いていると期待できます。

	/path/to/check_file_age -w 3660 -c 7200 /mnt/rsnapshot/hourly.0

### ディスクの残り容量を勘案して古いバックアップファイルを自動的に消したい

この問題は真面目に取り組むとかなり大変な気がします、簡単な解決法はないでしょう。ディスク容量を日頃からチェックし、世代の数を調整したり大きなファイルを除外するなど、地道な調整を行う必要がありそうです。次項も参考にどうぞ。

### ディスクの残り容量を気にせずにバックアップしたい

AWS EC2 のインスタンス上で運用する場合に限定されますが、ひとつのアイデアとして Amazon EFS (Elastic File System) の利用が考えられます。EFS は容量無制限の NFS ディスクをマウントできるサービスです。が、私はまだ使ったことはありません（2017/01 時点で北米のリージョンでのみ提供）。

最初のバックアップ先 `/mnt/rsnapshot` を EFS でマウントしたパスに指定することにより耐障害性が上がることを期待できるとともに、事実上ディスク容量の残りを気にする必要が永久になくなるでしょう（その代わりに費用を気にする必要はありますが）。

### lsyncd が拾いきれなかったときにリカバリしたい

lsyncd はウォッチできるファイルの最大数を超えたりファイル名が長すぎたりすると失敗したり、ちょっとナイーブです。うまく転送しきれなかった場合に備えて、別途リカバリ用の rsync タスクを定期的に走らせると少し安心度が高まるかもしれません。

```/etc/cron.d/lsyncd-recovery
*/10 * * * * root /usr/bin/rsync -a --del --hard-links /app/myproject/ /mnt/rsnapshot/current/app/myproject/
```

転送エラーを検知するには lsyncd のログを監視する方法が考えられます。もちろん、対処療法に過ぎませんから環境や設定を見直すべきでしょう。

### １分ごとに全データを残したい

真に剛の者なら１分ごとにすべてのバックアップを残したいかもしれませんね。`minutely` を新設し、これ１本で回します。これぞ最強の世代バックアップシステムと言えましょう。

```rsnapshot.conf
retain	minutely	5259600    # １０年分！

```

```/etc/cron.d/rsnapshot
* * * * * root /usr/bin/rsnapshot sync && /usr/bin/rsnapshot minutely  >> /mnt/rsnapshot/cron.log 2>&1
```

ただ、同一ディレクトリの中に最大で 500 万ディレクトリも作るとたいへんにアクセスが遅くなると思いますので、せめて１か月より古いものは hourly 以下に落としたほうがよいでしょう。

え、１分なんて無限にも等しいからせめて１秒ごとに残したい？　あなたはもしや AI かなにかですか。健闘を祈ります！

### その他メモ

よく知られた落とし穴ですが、rsync でディレクトリを指定する時は末尾のスラッシュの有無で意味が変わるので注意してください。慣れないうちは「必ずスラッシュをつける」に統一しておくとトラブルは少ないと思います。

新しい lsyncd は新しめの rsync を要求します。バックアップ先マシンの rsync バージョンに気をつけて下さい（サーバ用 OS のパッケージでは古い可能性があります）。

最初から他のサーバへ直接データを転送する（探求者向け）
---------------------------------------------------------------------

そもそも `lsyncd` が直接 `/mnt/rsnapshot/.sync` ディレクトリ以下にデータを放り込めれば、とてもスマートです。中間データ置き場として `current/` をわざわざ用意するのは容量の無駄ですからね。

なぜこれができないかというと、`rsnapshot` が `.sync` ディレクトリを毎回 mv (rename) するからです。そのため、世代の数だけ実ファイルがコピーされ（そして１時間ごとに `.sync` へフルに再転送され）、ディスクの使用量（とネットワーク帯域）がすごいことになってしまいます。

逆に考えると、「最初の世代の最新へのコピー」だけ、うまくハードリンクをコピーするよう `rsnapshot` を改造してやれば実現できることになります。やっつけパッチを[作ってみました](https://gist.github.com/ryochin/556ca9f916afcc13ee6a4996d2d6a3b0)ので、チャレンジャーな方はお試し下さい。

![理想の構成図](https://qiita-image-store.s3.amazonaws.com/0/1298/04f2afa6-c480-a306-6055-0a57582cff75.png)

rename の代わりに次のようなコマンドを実行するようになります。理論上１回しか実行されませんので（毎回が新規フルコピー）、`--del` 系オプションは意味が無いので付けません。

```
rsync -a --link-dest=../.sync/ /mnt/rsnapshot/.sync/ /mnt/rsnapshot/hourly.0/
```

`lsyncd.conf` は次のようになります。rsync+ssh でネットワーク越しに転送し、リモートの `/var/rsnapshot` 以下に格納することにしましょう。

```lsyncd.conf
sync {
  default.rsyncssh,
  source = "/app/myproject/",
  host = "you@example.com",
  targetdir = "/var/rsnapshot/.sync/app/myproject/",
  delete = true,
  ssh = {
    identityFile = "/home/you/.ssh/id_rsa",
  },
  rsync = {
    binary   = "/usr/bin/rsync",
    archive  = true,
    hard_links = true,
  }
}
```

lsyncd の実行前に、転送先のディレクトリを作成しておきます。

	ssh you@example.com mkdir -p /var/rsnapshot/.sync/app/myproject

`rsnapshot.conf` の変更点は次の通りです。ローテート機能しか使わなくなるので、バックアップ元は適当にダミーを設定しておきます（ただし、存在しないパスだと rsnapshot が動作しません）。

```rsnapshot.conf
...
backup	/dev/null	.
```

また、crontab に書いている `rsnapshot sync` は要らなくなりますので削除します。

```/etc/cron.d/rsnapshot
10 * * * * root /usr/bin/rsnapshot hourly  >> /var/rsnapshot/cron.log 2>&1
15 3 * * * root /usr/bin/rsnapshot daily   >> /var/rsnapshot/cron.log 2>&1
20 3 * * 0 root /usr/bin/rsnapshot weekly  >> /var/rsnapshot/cron.log 2>&1
...
```

嬉しい副作用として、初期容量を元データの**等倍**しか必要としないシステムにアップグレードできました。ついでに `ln -s .sync current` しておくと多少利便性が上がるでしょう。

おわり
--------

数秒前に更新したデータから数年前のデータまでがひとつのディレクトリ以下に格納され、通常の操作ですぐに参照できる環境というのはなかなか万能感がありますよね。

実際に運用するとなるとまだ検討と経験が必要そうではありますが、今回の構成はひとまずのスタート地点になり得るのではと思います。

それでは、よいバックアップライフを！