LuceneとかSolrを使っている時に、インデックスファイルを見て状況を把握すると手っ取り早いケースなんかがたまにあります。
そうした時に適切な判断ができるように、小規模なインデックスファイルを目視で確認して、それらがどういったファイルなのか軽く確認してみた。
さらっと見ただけのメモ書きなので、適当なことを書いている可能性があります。
各インデックスファイルの概要はここに載ってる
http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/fileformats.html
例として「content」というフィールドを持つ2つのドキュメントを登録してみます。登録した文字列は以下の2つ。
i have a dream
it is a dream deeply
このマーチン・ルーサー・キングには夢がある。
下記のようなコードでインデックスを生成。
IndexWriterConfig conf = new IndexWriterConfig(Version.LUCENE_36,
new WhitespaceAnalyzer(Version.LUCENE_36));
IndexWriter writer = new IndexWriter(FSDirectory.open(new File("index")), conf);
Document doc1 = new Document();
doc1.add(new Field("content", "i have a dream", Field.Store.YES, Field.Index.ANALYZED));
Document doc2 = new Document();
doc2.add(new Field("content", "it is a dream deeply", Field.Store.YES, Field.Index.ANALYZED));
writer.addDocument(doc1);
writer.addDocument(doc2);
writer.close(true);
上記のコードを実行すると、以下のようなファイルが生成されます。
_0.fdt _0.fdx _0.fnm _0.frq _0.nrm _0.prx _0.tii _0.tis segments.gen segments_1
それぞれのファイルの内容をodコマンドで見ながら、どういった情報が格納されているのか見ていってみる。
実際のファイルは「_0」のところの文字列が生成するたびに変化するけど、拡張子は常に一緒。
fdtはフィールドデータが入っているファイル。なので中身は非常にわかりやすい。
$ od -c _0.fdt \0 \0 \0 003 001 \0 001 016 i h a v e a d r e a m 001 \0 001 024 i t i s a d r e a m d e e p l y
見ての通り、登録した文字列がそのまま入っている。016と024は文字列長だと思われる。データをストアしない場合はこれらの文字列は出力されない。
fdxはfdtと2つ合わせて意味を為すらしい。fdxに各フィールドの場所を記録しておくことで、fdtに対するランダムアクセスが可能になるとか。
$ od -td1 _0.fdx 0 0 0 3 0 0 0 0 0 0 0 4 0 0 0 0 0 0 0 22
fdtの位置を表す情報は8bitで指定しているらしい。上記の内容だと、1つ目の要素は4byte目、2つ目の要素は22byte目に登場するという意味だろうか。
フィールド名などのフィールドの情報が入っているらしい。
$ od -c _0.fnm 375 377 377 377 017 001 \a c o n t e n t 001
バージョン、カウント、名前、インデックスに入れられるか、normの設定はどうか、offsetの設定はどうか、みたいな情報が入っているらしい。目視ではとりあえず名前が入っていることだけ分かった。あとは知らない。
the lists of documents which contain each term along with the frequency of the term in that document と説明文には書いてある。
$ od -td1 _0.frq 1 3 3 1 3 1 1 3 3
9byte。
登録した単語は、i have a dream it is a dream deeplyの9つ。これをソートすると、a a dream dream deeply have i is it。ということは、こういう意味だろうか。
a(1) a(3) dream(3) dream(1) deeply(3) have(1) i(1) is(3) it(3)
単語のポジションを覚えておくファイルらしい。
$ od -td1 _0.prx 2 2 4 3 3 1 0 1 0
Frequenciesの情報と合わせて考えると、こういうことになる。
a(1-2) a(3-2) dream(3-4) dream(1-3) deeply(3-3) have(1-1) i(1-0) is(3-1) it(3-0)
これを並び替えると、こうなる。
i(1-0) have(1-1) a(1-2) dream(1-3) it(3-0) is(3-1) a(3-2) deeply(3-3) dream(3-4)
検索の際のスコアに影響するNromの情報。
$ od -c _0.nrm N R M 377 x w
最初の3byteはNRM固定、次の1byteはバージョンを表すらしい。残りの2byteがなんらかの意味を持ってるのだろうけど、未調査。
Termの情報を記述しているらしい。
$ od -c _0.tis 377 377 377 374 \0 \0 \0 \0 \0 \0 \0 \a \0 \0 \0 200 \0 \0 \0 020 \0 \0 \0 \n \0 001 a \0 002 \0 \0 \0 006 d e e p l y \0 001 002 002 001 004 r e a m \0 002 001 001 \0 004 h a v e \0 001 002 002 \0 001 i \0 001 001 001 001 001 s \0 001 001 001 001 001 t \0 001 001 001
a, deeply, dreamなどの文字列が入っている。あとはそのTermの長さや数が入っているらしい。このファイルは単独で扱っても便利そう。
例えば下記のようにTermInfosReaderを直接(publicじゃないので同一パッケージから)呼ぶと、さらっと各Termのfrequencyが取れるとか。
// TermInfosReaderの初期化
Directory dir = FSDirectory.open(new File("index"));
TermInfosReader termInfosReader = new TermInfosReader(dir, "_0",
new FieldInfos(dir, "_0.fnm"), 2048, 1);
// 中のTermsの情報を出力
SegmentTermEnum terms = termInfosReader.terms();
while (terms.next())
System.out.println(terms.term() + " - " + terms.docFreq());
以下、実行結果。
content:a - 2 content:deeply - 1 content:dream - 2 content:have - 1 content:i - 1 content:is - 1 content:it - 1
まぁ、こんな使い方はしないか。
tis(Term Infos)をランダムアクセスで読むためのファイルらしい。
$ od -td1 _0.tii -1 -1 -1 -4 0 0 0 0 0 0 0 1 0 0 0 -128 0 0 0 16 0 0 0 10 0 0 -1 -1 -1 -1 15 0 0 0 24
1, 16, 10, 24などの数値が見えます。きっとこの辺の数値とtisの内容が絡んでいるのだと思われる。
セグメントの情報を記載している。
$ od -td1 segments.gen -1 -1 -1 -2 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1
上記はセグメントが1個だった場合。ここでもう1回データを投入してcommitすると、下記のように1だった箇所が2変化する。
$ od -td1 segments.gen -1 -1 -1 -2 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 2
セグメントの情報を記載している。ファイル名の_1の部分はcommitするたびに変化する。