読者です 読者をやめる 読者になる 読者になる

HHeLiBeXの日記 正道編

日々の記憶の記録とメモ‥

文書管理システムにおける文書の管理構造

前置き

タイトルから早速、「文書」とか「文書管理システム」とか「管理構造」とかなんだろうという疑問が(自分の中でも(待て))湧いてきそうなのでその辺の整理から。

  • 「文書」と言っているのは、WindowsUNIX/Linuxにおける「ディレクトリやファイル」であったり、ブログサイトにおける「記事」であったり、(Web)メールシステムにおける「メール」であったり、といったもののこと。データベースレベルまで落とし込むと、たとえばRDBMSにおけるレコードであったり(もちろん、データの持ち方によっては複数のレコードにまたがっていたり、レコードというくくりでは表せない場合もあるだろうが)。
  • 「文書管理システム」というのは、「文書」を何らかの形で保持、管理しているもので、各種OSなどで使用される「ファイルシステム」であったり、「ブログサイト」であったり、「(Web)メールシステム」や「メーラー」であったり、DBMSであったり、といったもののこと。
  • 「管理構造」というのは、たとえば「ファイルシステム」が「ディレクトリに子ディレクトリ/子ファイルを保持することで階層を形成」していたり、「ブログサイト」が「記事と作成者の関係や記事につけられたタグを管理することで記事の分類分け」をしていたり、そういった「文書」の管理に使用しているデータ構造のこと。

まぁ、厳密に定義することが目的ではなく、自分がふと考えたことをダンプすることが目的なので、この辺にしておく。

はじめに

前置きが長くなったが、数年前に某文書管理システム(と自分は思っていたもの(何))があり、ざっくり分類すると階層構造で文書を管理するものなのだが、ルートからある文書にたどり着くためのパスが複数あるような構造。それに対して、「実パスがどうこう‥」という話をされて『実も虚もないような‥』というモヤっとした思いを抱きながら今まで過ごしてきたらしい。
それが、ここのところLinuxに触れる機会が再増(何)したためか、何かに気づいたらしい。

UNIX/Linuxファイルシステム

UNIX/Linuxファイルシステムにおいて、以下のようなディレクトリ/ファイル構造があるとする。(以下は「find / -exec ls -ld {} \;」の結果を簡略化したものと考えてもらえれば‥)

drwxr-xr-x  /
drwxr-xr-x  /hoge
drwxr-xr-x  /hoge/foo
drwxr-xr-x  /hoge/foo/bar
-rw-r--r--  /hoge/foo/bar/hoge.txt
-rw-r--r--  /hoge/foo/bar/foo.txt
drwxr-xr-x  /hoge/fuga

ここで、以下のコマンドを実行してみる。

cd /hoge/fuga
ln ../foo/bar/hoge.txt hoge.txt
ln -s ../foo/bar/foo.txt foo.txt

すると、構造は以下のようになる。

drwxr-xr-x  /
drwxr-xr-x  /hoge
drwxr-xr-x  /hoge/foo
drwxr-xr-x  /hoge/foo/bar
-rw-r--r--  /hoge/foo/bar/hoge.txt
-rw-r--r--  /hoge/foo/bar/foo.txt
drwxr-xr-x  /hoge/fuga
-rw-r--r--  /hoge/fuga/hoge.txt
lrwxrwxrwx  /hoge/fuga/foo.txt -> ../foo/bar/foo.txt
ハードリンク型

上記のhoge.txtは、「/hoge/foo/bar/hoge.txt」と「/hoge/fuga/hoge.txt」という2つが存在するように見えるが、実体は同じである。もちろん「rm /hoge/foo/bar/hoge.txt」としても、hoge.txt自体は削除はされない(「/hoge/fuga/hoge.txt」で参照できるため)。
なので、「/hoge/foo/bar/hoge.txt」と「/hoge/fuga/hoge.txt」のどちらが実パスなのかを論じるのは無意味である。だって区別できないもの。(せいぜい、各親ディレクトリの更新日時が異なるくらい)

RDB上で表現するとしたら以下のような感じか。

<documents>
+----+----------+------------+--------------------+
| id |   name   |    type    |      contents      |
+----+----------+------------+--------------------+
|  0 | /        | directory  | --                 |
|  1 | hoge     | directory  | --                 |
|  2 | foo      | directory  | --                 |
|  3 | bar      | directory  | --                 |
|  4 | hoge.txt | file       | HOGE.TXT           |
|  5 | foo.txt  | file       | FOO.TXT            |
|  6 | fuga     | directory  | --                 |
|  7 | foo.txt  | sym-link   | ../foo/bar/foo.txt |
+----+----------+------------+--------------------+

<parents>
+----+----------+
| id | child_id |
+----+----------+
|  0 |        1 |
|  1 |        2 |
|  1 |        6 |
|  2 |        3 |
|  3 |        4 |
|  3 |        5 |
|  6 |        4 |
|  6 |        7 |
+----+----------+

「rm /hoge/foo/bar/hoge.txt」という操作は、から{3, 4}というレコードを削除する操作に対応する。(さらにこのとき、id=4(hoge.txt)を子として持つディレクトリがなければ、から{4, hoge.txt, file, HOGE.TXT}というレコードが削除されるのだが、id=6(fuga)が親になっているのでに対する操作は行われない)
先に書いた「数年前に某文書管理システム(と自分は思っていたもの(何))」というのは、正にこのデータ構造を採用していた。

シンボリックリンク

上記のfoo.txtは、「/hoge/foo/bar/foo.txt」と「/hoge/fuga/foo.txt」の2つのパスを持ち、hoge.txtと同様に見えるが、hoge.txtの場合とは微妙に異なる。それは「rm /hoge/foo/bar/foo.txt」をしたときに、foo.txtの実体が存在しなくなるという点。hoge.txtの場合と違って、「/hoge/fuga/foo.txt」は単なるタグとかラベルとかいうものに過ぎない。
この場合は、どっちが実パスなのかについて論じる意味が出てくる。

先のRDBで表した例でいうと、「rm /hoge/foo/bar/foo.txt」で{3, 5}というレコードが削除されると、id=5(foo.txt)を子として持つディレクトリがいなくなるので、からもid=5が削除される。id=7が参照していてもそれは考慮されない。
「/hoge/fuga/foo.txt」が指すものがなくなり、ゴミとなってしまうわけだが、ゴミ処理をちゃんとやろうと思ったら以下のようなテーブルを追加することになるだろうか。

<sym_links>
+----+-----------+
| id | target_id |
+----+-----------+
|  7 |         5 |
+----+-----------+

ただ、注意したいのは、UNIX/Linuxにおけるファイルシステムではここまでは管理していないということ。それは「mv /hoge/fuga /」を実行したときに分かるが、「/fuga/foo.txt」(元 /hoge/fuga/foo.txt)が指すのは「/fuga/../foo/bar/foo.txt」であり、「/hoge/foo/bar/foo.txt」ではない。

ちなみに、ブログサイトやソーシャルブックマークなどにおける「タグ」は階層のないシンボリックリンク型であると言えるのではないだろうか。

終わりに

「はじめに」で書いたケースでは、結局のところ上記のハードリンク型とシンボリックリンク型の2つをちゃんと区別した作り/使い方になっていなかったために起こった問題のような気がする。(というのは当時もなんとなく言っていたような気がするが‥)