あいつの日誌β

働きながら旅しています。

groonga のソースコードリーディングに参加してきた

行ってきました。

https://groonga.doorkeeper.jp/events/23042

会場の提供は株式会社 ビジネスバンクグループさまです。 ありがとうございます。なんかすごい本棚に本がびっしりでした。

様子

勉強会というよりも groonga の転置インデックスを作る処理はどこだ!?というクイズに参加者がもくもくと立ち向かう会、と言った方がわかりやすいかも。

開発環境

Windows, Linux, MacOSX と各自バラバラの開発環境で作業していました。 私は MacOSX を使いました。gdb ではなく lldb で作業しましたが問題なかった。

% sw_vers
ProductName:    Mac OS X
ProductVersion: 10.9.5
BuildVersion:   13F34

% lldb --version
lldb-320.4.124.10

準備

% cd /tmp
% curl -O http://packages.groonga.org/source/groonga/groonga-5.0.2.zip
% unzip groonga-5.0.2.zip
% cd groonga-5.0.2/
% ./configure --enable-debug  --prefix=/tmp/local
% make
% make install

動作確認する

% /tmp/local/bin/groonga --version
groonga 5.0.2 [darwin13.4.0,x86_64,utf8,match-escalation-threshold=0,nfkc,msgpack,onigmo,zlib,kqueue]

configure options: < '--enable-debug' '--prefix=/tmp/local'>

本編開始

転置インデックスご存知無い方いますか?と主催者がおっしゃったので挙手しました。 ええ、挙手したの私だけですけど?

という事で簡単に説明をうけました。

その後 gdb 使ったことない人いませんよね?と主催者がおっしゃったので挙手しました。 もちろん、挙手したの私だけですけど?

という事でお隣さんに gdb の使い方をレクチャーうけて戦闘開始。 どうやら俺は本日の最低スペックらしいがあまり気にしない。

まあそんな私でも楽しめる勉強会でした。

今日やること

groonga で以下の処理を行います。

  • データベース作成
  • スキーマ作成
  • データインサート

コマンドにするとこんな感じ

table_create --name Site --flags TABLE_HASH_KEY --key_type ShortText
column_create --table Site --name title --type ShortText
table_create --name Terms --flags TABLE_PAT_KEY|KEY_NORMALIZE --key_type ShortText --default_tokenizer TokenBigram
column_create --table Terms --name blog_title --flags COLUMN_INDEX|WITH_POSITION --type Site --source title

load --table Site
[
{"_key":"http://example.org/","title":"This is test record 1!"},
]

で最後の load --table Site するときに Terms テーブルにデータが作られるのですがそこが実行される関数を debug しながら探します。

やり方

上記のコマンドを /tmp/test.grn として保存します。そして以下を実行します。

% rm -rf /tmp/db && mkdir -p /tmp/db && lldb -- /tmp/local/bin/groonga --file /tmp/test.grn -n /tmp/db/db

lldb が起動するのでとりあえず grn_load にブレイクポイントを仕込みます

(lldb) b grn_load
(lldb) r
Process 55502 launched: '/tmp/local/bin/groonga' (x86_64)
[[0,1428929612.0168,0.000658988952636719],true]
[[0,1428929612.01753,0.000371932983398438],true]
[[0,1428929612.01792,0.00933480262756348],true]
[[0,1428929612.02729,0.00754904747009277],true]
Process 55502 stopped
* thread #1: tid = 0x3c74d3, 0x000000010008db79 libgroonga.0.dylib`grn_load(ctx=0x00007fff5fbfeb18, input_type=GRN_CONTENT_JSON, table=0x0000000101c73f68, table_len=4, columns=0x0000000101c73f98, columns_len=0, values=0x0000000101c73f20, values_len=0, ifexists=0x0000000101c73fd0, ifexists_len=0, each=0x0000000101c74030, each_len=0) + 89 at db.c:12266, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x000000010008db79 libgroonga.0.dylib`grn_load(ctx=0x00007fff5fbfeb18, input_type=GRN_CONTENT_JSON, table=0x0000000101c73f68, table_len=4, columns=0x0000000101c73f98, columns_len=0, values=0x0000000101c73f20, values_len=0, ifexists=0x0000000101c73fd0, ifexists_len=0, each=0x0000000101c74030, each_len=0) + 89 at db.c:12266
   12263                 const char *ifexists, unsigned int ifexists_len,
   12264                 const char *each, unsigned int each_len)
   12265        {
-> 12266          if (!ctx || !ctx->impl) {
   12267            ERR(GRN_INVALID_ARGUMENT, "db not initialized");
   12268            return ctx->rc;
   12269          }

この段階で別のターミナルで以下を実行してみます。まだ Terms になにも作成されていません。

% /tmp/local/bin/groonga /tmp/db/db 'select --table Terms' 
[[0,1428929803.02688,0.000563859939575195],[[[0],[["_id","UInt32"],["_key","ShortText"],["blog_title","UInt32"]]]]]

さきほどの lldb で continue を実行し続けると処理が終了します。

(lldb) c
(lldb) c
(lldb) c
(lldb) c
Process 55735 resuming
[[0,1428930199.34877,3.53673601150513],1]
Process 55735 exited with status = 0 (0x00000000)

結果 Terms にデータが作られています。ここにデータが入る関数を探します。

% /tmp/local/bin/groonga /tmp/db/db 'select --table Terms'
[[0,1428930299.4782,0.000668048858642578],[[[6],[["_id","UInt32"],["_key","ShortText"],["blog_title","UInt32"]],[6,"!",1],[5,"1",1],[2,"is",1],[4,"record",1],[3,"test",1],[1,"this",1]]]]

さて lldb を終了してもう一度同じ事を繰り返します。これで準備が整いました。

(lldb) quit
% rm -rf /tmp/db && mkdir -p /tmp/db && lldb -- /tmp/local/bin/groonga --file /tmp/test.grn -n /tmp/db/db

実践あるのみ

あとは各自もくもく。

例えば試しに /tmp/test.grn の load をコメントアウトしてみる。 そうすると grn_load を実行しない。さっきは4回実行していたので何故か考えてみる。

これは /tmp/test.grn を以下に書き換えると理由がわかる

load --table Site
[{"_key":"http://example.org/","title":"This is test record 1!"}]

という風に書き換えると continue 2回で終わる

(lldb) b grn_load
(lldb) r
(lldb) c
(lldb) c

ちなみに最初の記述だと grn_load が4回実行されていて、3回目で Terms にデータが作成されている事がわかります。どうやら最後の bracket を読み終える前に転置インデックスが作られているようです。

という風に実際にコードを実行しながらソースコードを読んだり、また実際にコードを読んでアタリをつけてブレイクポイントをつけたりしてゴールを目指す作業をしました。

まとめ

私は C 言語をまともに使った事ないですし gdb 使ったこともありませんでした。 日頃から使っている人でも苦戦している方が多かったようなのでもしかしたら難しいのかもしれません。

なんですが開発環境と作業方法さえ分かってしまえばプログラマだったら結構楽しめる勉強会だと思うので次回また開催してほしい次第です。

追記(2015-04-16)

pat.c の 746行(_grn_pat_add) で以下のように転置インデックスが作成され始める。[1,"this",0] が追加されている

[[0,1429158176.47048,0.000427007675170898],[[[1],[["_id","UInt32"],["_key","ShortText"],["blog_title","UInt32"]]]]]
[[0,1429158184.56761,0.000448942184448242],[[[1],[["_id","UInt32"],["_key","ShortText"],["blog_title","UInt32"]],[1,"this",0]]]]

ii.c 3799行目(grn_ii_update_one) で以下のように転置インデックスが update され始める。[1,"this",0] が [1,"this",1] になっている

[[0,1429159703.08784,0.000547885894775391],[[[6],[["_id","UInt32"],["_key","ShortText"],["blog_title","UInt32"]],[6,"!",0],[5,"1",0],[2,"is",0],[4,"record",0],[3,"test",0],[1,"this",0]]]]
[[0,1429159709.06348,0.000452995300292969],[[[6],[["_id","UInt32"],["_key","ShortText"],["blog_title","UInt32"]],[6,"!",0],[5,"1",0],[2,"is",0],[4,"record",0],[3,"test",0],[1,"this",1]]]]

とりあえず転置インデックスが作成され始める場所を特定する事はできました。 それは分かったんですが、内部でどのような処理するのかはあんまりわかっていません(コード読め)