Unison を使って実現する大量データの双方向同期

1. 実現したい事





数百 GB から数 TB に及ぶ大量のデータを、複数の計算機環境で Dropbox のように同期したい。しかも無料で、かつ Dropbox よりは転送速度の速いものが良い。
実験データとか計算データとか解析データとか、職業柄いっぱいデータを扱う日常でして、しかもそれが手元の MacBook Pro の中にあったり、どっかの研究所の計算機 server にあったり、果ては昔の所属機関の RAID に詰まったりしているわけです。遠隔地にデータがあったとしても、計算させるだけだったら SSH で job を投げるだけで済みます。しかし X を飛ばしてちょっと解析したくなってくると、どうにも SSH じゃやっていられません。

そんな中、512 GB の SSD も値段がこなれてきて、また 1 TB の 2.5" HDD なんかも出てきました。僕の仕事用の MacBook ProOWC の Data Doubler というのを載せているため、SSD と HDD の二台載せが可能です。合計で 1.5 TB もあれば各地に散らばったデータを収容する能力があるわけで、是非ともこれらを集約したくなります。

2. rsync の問題点

しかし rsync で同期しちゃえば済むじゃないか、と単純には行きません。なぜなら rsync の同期は一方通行だからです。MacBook Pro 側でデータを部分的に変更して、遠隔地側でも変更してなんて作業をしてしまうと、両者の整合性を取るのが面倒になります。

また、不要な file を片方で消去した時に、rsync で同期してしまうとそれが復活するなんてことも発生します。.log を消したり、*~ を消したりした場合、こういうものは同期先 (or 同期元) でも同時に消えてもらいたいものです。

3. Dropbox の問題点

じゃあ、もっと便利な Dropbox を使えば良いじゃないか、となるわけです。rsync が古くから Un*x で使われてきたのにも関わらず、Un*x 使いの中でも Dropbox が人気なのは、双方向同期を気軽に、しかも background でやってくれるからでしょう。

しかし、最大の問題が発生する料金です。僕の場合は「Dropbox の容量を無料で 20GB まで増やす方法」などを使って、無料で 24.9 GB まで使えるようになっています。しかし、100 GB を Dropbox で確保するには年間 $40 が発生し、また 1 TB を使うには年間 $795 も必要です。これはちょっと研究費で賄う気にはなりません。

さらに、Dropbox は転送速度が遅いため、即座に大量のデータを同期させたい場合には向かないらしいと聞きます。

4. Unison を使おう

ということで、色々と悩んで Twitter で助言をいくつか頂いた結果、Unison を使うことにしました。先に Unison の長所と短所を書いておきます。

4.1 Unison の長所
  1. rsync と異なり、双方向の同期を実現してくれるため、同期先、同期元の更新情報を同時に反映してくれる。
  2. rsync と異なり、file の移動や削除にも対応している。
  3. 無料。
  4. MacLinuxWindows に対応している。
  5. Command line 一発なので、Un*x に慣れている人には馴染みやすい。
4.2 Unison の短所
  1. Dropbox とは異なり、background で自動的に同期ができない (自分でゴニョゴニョする必要あり)。
  2. 同期の度に file を全部見に行くようなので、数十万〜数百万 file あると、NFS など disk access の遅い環境では実用的でなくなる。
  3. Symbolic link には対応しているが、hard link には未対応。
  4. 導入するのが面倒と言えば面倒 (build したりとか)。
  5. ユーザ数が多くないので、はまったときに検索しても情報が乏しい。
  6. 英語力必要。

まあ、こういう記事を書くことで、後半 3 つの短所が少しでも軽減すれば良いなと。

5. Unison の導入

5.1 Mac の場合

MacPorts なりを使って簡単に導入しましょう。僕の Lion 環境では ver. 2.40.63 が入りました。

$ sudo port install unison
$ unison -v
unison version 2.40.63
5.2 Linux の場合

これは使っている環境にもよると思いますが、僕の場合は管理者権限のない計算機に導入したため、自分で build しました。prefix は $HOME/opt です。環境は RHEL 5 (64 bit) です。

まずは OCaml を落としてきて build します。3.12.1 を使いました。これがないと Unison の build ができません*1

$ tar zxvf ocaml-3.12.1.tar.gz
$ cd ocaml-3.12.1
$ ./configure -prefix $HOME/opt
$ make world
$ make opt
$ make opt.opt
$ make install

次に、Unison の build ですが、version 番号を全ての環境で統一する必要があります。Mac で導入した 2.40.63 を入れます。

$ tar zxvf unison-2.40.63.tar.gz
$ cd unison-2.40.63
$ emacs Makefile #ここで$(HOME)/bin を $(HOME)/opt/bin に書き換えた (好みで)
$ make
$ make install

これだけです。unison -v で、Mac のものと同じ version 番号かを確認しましょう。

5.3 遊んでみる

この後は、Unison の基本的な使い方を学ぶために、manual を読んで動作確認をしてみましょう。英語の公式か、有志の方の日本語訳を参照して下さい。「Tutorial」のところを読んで基本的な動作を身に付けましょう。

5.4 自分が使っている引数
  1. -owner 所有者の情報を維持します。これをつけないと、UID が同一になるようになるのかな? login name が違う環境同士でやるとどうなるんでしょうね。試していません。
  2. -prefer newer 更新日時の新しいものを優先します。同時編集している場合などには注意してください。
  3. -times これをつけないと、file の time stamp がメチャクチャになるそうです。
  4. -batch 変更が衝突した場合に、"-prefer newer" に従って自動的に衝突を解決します。勝手にやられるのが嫌な場合は、-batch は外して手動で解決しましょう (衝突した file ごとに、ちゃんと確認が出ます)。
  5. -path 特定の下の階層だけを同期したい時に使います。

実際には、以下のような使い方をしています。

$ unison . ssh://YOUR_REMOTE_SERVER/THE_DIRECTORY_WHICH_YOU_SYNC -owner -prefer newer -times -batch

今のところ、名古屋で使用している MacBook Pro、アメリカ西海岸にある SLAC の計算機環境 (100 CPU 使える)、千葉県にある宇宙線研究所の計算機環境 (これも 100 CPU 使える) の三箇所で同期しています。同時に 200 CPU を使いきりたいときには、両方の計算機で並行して job を投入するため、両者の出力結果を同期させ、さらに手元の MacBook Pro に持ってくるという用途には、Unison は最善の解決方法だなと実感しています。

5.5 注意点

Unison は background で更新を調べてくれたりはしません。ユーザが unison を実行するたびに、同期元、同期先の directory 階層を調べに行って更新箇所を調べます。そのため、数十万や数百万の file を同期しようとすると時間がかかります。特に、接続先が NFS などを使っている場合は負荷がかかります。可能な限り、データを少ない数の file にまとめるようにしましょう。

海や大陸を跨いだ同期をする場合、「遅延時間」のせいで scp や rsync と同様に非常に時間がかかります。特に初回同期時には何十時間もかかってしまうでしょう。これを解決するためには、Unison でいきなり同期するのではなく、あらかじめ rsync や scp などで同期できる部分は同期しておきましょう。その際には「scp の複数同時接続」のような手法を使うことで、長距離でも 100 Mbps を超えるデータ転送が可能になります。Unison を使って同期するのは、初回同期が済んで、両者で更新をするようになってからでも問題ないでしょう。

Unison は実行時に、SSH を使って同期先の unison の version を調べます。僕は .zshrc の設定で login 時に色々と標準出力へ吐き出すようにしているのですが、これがあると version 番号が正しく出力されなかったと見なされてしまいます。unison への PATH を通すだけの .zshenv などを用意してやると、これを解決できます (参照)。

*1:64 bit の Cent OS 上で 32 bit で build したい場合、ここと同じ引数で configure するとできました。