作成に至る経緯

事務所の事件のデータは、以下のようなディレクトリ構成に従って保存しています。

case/
  0000-0050/
    0001-山田太郎/
    0002-山田花子/
    0003-佐藤太郎/
    ...
  0051-0100/
    0051-井上太郎/
    0052-井上花子/
    ...
  0101-0150/
    ...
  ...

これは全データを保存するストレージ内の話なのですが、作業用のノートパソコンには、進行中の事件に関するデータしか格納しないようにしています。全データを持ち運ぶ必要性はありませんし、いくらSSDの容量が増えてきているとはいっても、そこまでの容量的な余裕はありません。

そこで、進行中の事件に関するディレクトリだけ、母艦となるストレージと作業用の環境で同期するようにしているのですが、どのディレクトリを同期するかの指定は手動で行わなければなりません。そのため、作業用の環境が増えたとき、その環境と同じ数だけ、同期指定を手動で行わなければならないことになります。

しかし、どのディレクトリを同期すべきかは最初の環境で判明しているのですから、同じ作業を他の環境で繰り返すことは時間の無駄です。また、そこにヒューマンエラーが入り込む可能性もあるため、システム全体の安全性を脅かす可能性も否定できません。

そこで、進行中の事件に関するデータだけを抜き出した、いわばワーキングセットを表すディレクトリを作成し、そのディレクトリを同期すれば進行中の事件に関するデータを全て取ってくることができるようにしたいと考えました。

母艦ストレージとワーキングセット、これら二つを同期するための方法が必要になります。これはOS標準の機能で実現できないため、何らかのソフトウェアを使うか、自分でスクリプトを書かなければなりません。そして、そのソフトウェアあるいはスクリプトは、ファイルを管理しているNAS上で動作するものでなければなりません。NASはLinuxで動いていますが、ミニマムな構成となっているので、使えるものは限られてきます。

rsyncを検討する

rsyncという、ディレクトリの同期に使われるプログラムがあります。本来はネットワークで接続された遠隔地のファイルやディレクトリを同期するためのプログラムで、データ転送量を最小化するという特徴があります。GNU General Public Lisenceでリリースされているため、フリーで使用することができます。ディレクトリのコピーをしようと思ったとき、真っ先に思いつくソフトウェアであるほど有名です。

ファイルのコピーと削除は極めて重要で安全性が求められる処理であるため、自分で作るよりも、信頼と実績のあるソフトウェアを使った方が安全で簡単です。NASにも標準で組み込まれていました。

しかし、実際に使ってみると、このrsyncには致命的な問題があり、利用できないことが分かりました。

それは、ファイルのコピーをすることはできるのですが、ファイルの削除をすることができないという点です。もともとrsyncはファイルのコピーを目的に作られているものなので、主となるディレクトリの情報を、従となるディレクトリにコピーすることしかできません。そのため、片方で必要のなくなったファイルを削除すると、もう片方に残ったファイルが転送されてきてしまうのです。ファイルの削除はとても危険な行為なので、安全と言えば安全なのですが、これではファイルが増えていく一方で、とても使いにくくなってしまいます。

スクリプトの自作を検討する

そこで、ディレクトリの同期を行うスクリプトを自分で作成することを検討してみます。

スクリプトが備えなければならない機能は、ファイルの作成と削除を検出することです。しかし、これはそう簡単な話ではありません。というのも、ある時点だけを見た場合、はたしてファイルが作成されたのか、削除されたのか、決められない場合があるからです。

例として、ディレクトリaとディレクトリbがあり、ファイルa/zだけがある場合を考えます。この状態を作り出すのは、次の二つの場合です。

  1. b/zが削除された。その結果、ディレクトリaだけにファイルが存在する。
  2. a/zが作成された。その結果、ディレクトリaだけにファイルが存在する。

そして、現在の状態だけでは、いずれの場合であるのかを決定することはできません。そのため、rsyncではファイルをコピーするしかなかったのです。この問題を解決するには、直前の状態を記憶しておくか、ファイル操作を監視しておく方法があります。

すると、ディレクトリの状態を各時点について記憶しておき、比較して、ファイルの作成と削除を検出するような機能を実装する必要があります。また、その検出結果に応じて、ファイルの複製と削除の処理を行う必要があります。各ディレクトリでファイルの変更が行われて衝突が発生した場合は、ファイル内容の比較やファイル名の変更を行わなければなりません。

これらの処理を自分で実装するとなると、かなりの労力が必要になります。また、このような要望は他の方々も抱く可能性が高く、既に同じような機能が実装されていると考えた方が良いと思われます。おそらく自分だけしか望まないであろう特殊な仕様もありますが、何らかの手段で実現できるものであるため、ディレクトリの同期を行ってくれるソフトウェアを探した後で別に方法を考えれば良いものです。

unisonを検討する

そこでunisonというソフトウェアの利用を考えます。これはファイルを同期するためのツールとして作成された、歴史のあるプログラムです。OCamlというプログラミング言語で書かれており、こちらも歴史のある言語のようなのですが、まったく馴染みがないので、ちょっと不安になります(使い慣れている方々には申し訳ありませんが)。

事務所ではSynologyのNAS(一応セキュリティのためモデル名は秘密です。)を導入しており、CPUはIntel Celeron、OSはLinuxを使っています。この環境上でunisonを動かさなければならないのですが、このNASですと、GithubのReleaseにアップロードされているLinux用のバイナリがそのまま動きます。このバイナリはOCamlも同梱されているので、大変なビルド処理をせずとも、tarファイルを展開するだけで大丈夫なのです。NASにOCamlの導入やunisonのコンパイルも試みてみたのですが、様々な条件に制約のあるNASの環境では、ビルドすることができませんでした。そのため、ビルド済みのバイナリを使うことにします。

unisonを利用することで、ファイルの削除も検出することができるようになり、母艦ストレージとワーキングセットの同期をすることができるようになりました。

しかし、ここで新たな問題が生じます。unisonはルートディレクトリ以下をごっそりと同期するタイプのソフトウェアであり、とあるルートディレクトリの下にある「このディレクトリ」「あのディレクトリ」は同期したい(現在進行形の事件なのでワーキングセットに置きたい)が、「そのディレクトリ」「あっちのディレクトリ」は同期したくない(終結済の事件なのでワーキングセットには置く必要がない)、という場合の処理がとてもとても大変になってしまうのです。設定ファイルを頑張って書けば不可能ではないのですが、ワーキングセットだけを見ても、どのディレクトリが使用中で、どのディレクトリが使用中ではないのか、判別することができず、時間が経つとワーキングセットがぐっちゃぐっちゃになっていき、とても使いにくくなります。(使い終わったディレクトリの削除をunisonはやってくれないため)

この問題を解決する方法として、ワーキングセットと母艦ストレージの間に、ワーキングセットに置くべきディレクトリへのシンボリックリンクを配置した、名付けるならばリンクディレクトリを置くことにしました。シンボリックリンクはWindowsのショートカットを強力にしたような仕組みで、ソフトウェアからはシンボリックリンクがリンク先のファイルであるかのように扱われます(もちろん特別な方法で別個のものとして扱うこともできます)。Windowsのショートカットは、リンク先のファイルとは別物として扱われます(NTFSでは特別扱いされない)。

イメージとしては、次のような構成になります。

case/ # 母艦ストレージ
  0000-0050/
    0001-山田太郎/
    0002-山田花子/
    0003-佐藤太郎/
    ...
  0051-0100/
    0051-井上太郎/
    0052-井上花子/
    ...
  0101-0150/
    ...
link/ # リンクディレクトリ
  0003-佐藤太郎 -> case/0000-0050/0003-佐藤太郎/ # シンボリックリンク
  0052-井上花子 -> case/0051-0100/0052-井上花子/
  ...
working/ # ワーキングセット
  0003-佐藤太郎/ # 実体のあるディレクトリ
  0052-井上花子/
  ...

リンクディレクトリとワーキングセットをunisonで同期すれば、母艦ストレージとワーキングセットも同期されることになります。

あとは、いかにしてリンクディレクトリを構成するか、なのですが、これをやってくれるソフトウェアは見つかりませんでした。そのため、ここについては自作をする必要があります。

unisonの使い方や、リンクディレクトリを構成するスクリプトについては、また別の記事にしたいと思います。