全文検索エンジンGroongaで複数プロセスからアクセスするときの注意点

> [お知らせ] 2016年9月29日(肉の日)に[MySQLとPostgreSQLと日本語全文検索3](https://groonga.doorkeeper.jp/events/50541)というイベントが開催されます。今回の事例紹介はMySQL InnoDBの全文検索機能です。Groongaをストレージエンジンとして採用しているMroongaの紹介もありますよ。

# はじめに

高速な全文検索を実現するためのソフトウェアとして、[Groonga](http://groonga.org/ja/)があります。この記事を書いている時点の最新バージョンは6.0.8です。

Groongaにアクセスするプロセスが1つのときには問題になりませんが、複数のプロセスからGroongaにアクセス(例えば複数のgroongaコマンドで同じデータベースを開く)するようになったときには、ちょっと注意しないといけないことがあります。

今回はその「ちょっと注意しないといけない」ポイントについて説明します。

# スキーマを変更する場合

Groongaのデータベースは[複数のプロセスで共有可能](http://groonga.org/ja/docs/characteristic.html#sharable-storage-and-read-lock-free)なようになっています。そのため複数のプロセスからデータを更新することに何ら問題はありません。
しかし、スキーマの変更となると事情が変わってきます。
例えば、同じGroongaのデータベースを複数のクライアントからアクセスしスキーマの変更を行うケースを考えます。

あらかじめ、以下のスキーマでデータベースを構築してあるとしましょう。

```
table_remove User

table_create User TABLE_PAT_KEY UInt32
column_create User first_name COLUMN_SCALAR ShortText
column_create User last_name COLUMN_SCALAR ShortText

load --table User
[
{"_key":1, "first_name":"Taro", "last_name":"Maruwa"},
{"_key":2, "first_name":"Hanako", "last_name":"Yamada"}
]
```

groongaコマンドを使って、データベースにアクセスすることにします。
プロセスAでは、スキーマの変更はしません。一方プロセスBではスキーマを変更するものとします。

## プロセスA

プロセスAではスキーマを変更しません。定義されているテーブルとカラムのリストを確認してみましょう。
`table_list`と`column_list`で定義を確認します。

```
% groonga testdb/db
> table_list 
[[0,1474006053.211516,0.0006153583526611328],[[["id","UInt32"],["name","ShortText"],["path","ShortText"],["flags","ShortText"],["domain","ShortText"],["range","ShortText"],["default_tokenizer","ShortText"],["normalizer","ShortText"]],[256,"User","testdb/db.0000100","TABLE_PAT_KEY|PERSISTENT","UInt32",null,null,null]]]
> column_list User
[[0,1474006056.986597,0.0002224445343017578],[[["id","UInt32"],["name","ShortText"],["path","ShortText"],["type","ShortText"],["flags","ShortText"],["domain","ShortText"],["range","ShortText"],["source","ShortText"]],[256,"_key","","","COLUMN_SCALAR","User","UInt32",[]],[258,"first_name","testdb/db.0000102","var","COLUMN_SCALAR|PERSISTENT","User","ShortText",[]],[257,"last_name","testdb/db.0000101","var","COLUMN_SCALAR|PERSISTENT","User","ShortText",[]]]]
```

## プロセスB

プロセスBではスキーマの定義を変更します。
具体的には、`last_name`のカラムの型を`ShortText`から`LongText`に変更します。そのために、一旦`User`テーブルから`last_name`カラムを削除して、再度カラムを作りなおします。

```
% groonga testdb/db
> column_remove User last_name
[[0,1474006068.331246,0.002974987030029297],true]
> column_create User last_name COLUMN_SCALAR LongText
[[0,1474007443.677316,0.004316091537475586],true]
> column_list User
[[0,1474007451.995359,8.463859558105469e-05],[[["id","UInt32"],["name","ShortText"],["path","ShortText"],["type","ShortText"],["flags","ShortText"],["domain","ShortText"],["range","ShortText"],["source","ShortText"]],[256,"_key","","","COLUMN_SCALAR","User","UInt32",[]],[258,"first_name","testdb/db.0000102","var","COLUMN_SCALAR|PERSISTENT","User","ShortText",[]],[257,"last_name","testdb/db.0000101","var","COLUMN_SCALAR|PERSISTENT","User","LongText",[]]]]
```

`column_list`で現在のカラムを参照すると、`last_name`カラムの型が`LongText`に変更されていることがわかります。

```
[257,"last_name","testdb/db.0000101","var","COLUMN_SCALAR|PERSISTENT","User","LongText",[]]
```

## 再びプロセスA

さて、プロセスAではプロセスBのスキーマ定義変更はどのように見えているでしょうか。
プロセスAに戻って確認してみましょう。

プロセスBがテーブルを削除した時点では、カラムが消えているだけなので特に問題がありません。

```
> column_list User
[[0,1474006125.018618,9.870529174804688e-05],[[["id","UInt32"],["name","ShortText"],["path","ShortText"],["type","ShortText"],["flags","ShortText"],["domain","ShortText"],["range","ShortText"],["source","ShortText"]],[256,"_key","","","COLUMN_SCALAR","User","UInt32",[]],[258,"first_name","testdb/db.0000102","var","COLUMN_SCALAR|PERSISTENT","User","ShortText",[]]]]
```

しかし、カラムが再作成されたあとはどうでしょうか。`column_list`で定義を確認してみます。

```
> column_list User
[[0,1474007459.899354,9.703636169433594e-05],[[["id","UInt32"],["name","ShortText"],["path","ShortText"],["type","ShortText"],["flags","ShortText"],["domain","ShortText"],["range","ShortText"],["source","ShortText"]],[256,"_key","","","COLUMN_SCALAR","User","UInt32",[]],[258,"first_name","testdb/db.0000102","var","COLUMN_SCALAR|PERSISTENT","User","ShortText",[]],[257,"last_name","testdb/db.0000101","var","COLUMN_SCALAR|PERSISTENT","User","ShortText",[]]]]
```

注目すべきは、`last_name`の型です。プロセスBで`LongText`型で作成したはずの`last_name`カラムが、何故か`ShortText`型になってしまっています。これでは、プロセスBでカラムを再作成する前の状態ですね。

```
[257,"last_name","testdb/db.0000101","var","COLUMN_SCALAR|PERSISTENT","User","ShortText",[]]
```

古いlast_nameの定義を参照したままになってしまっていることがわかりました。

# 古い定義が参照されたままになる問題への対策

古い定義を参照したままになっているのが問題なので、再度データベースを開き直します。
具体的にはプロセスAを実行しているgroongaコマンドを一旦終了します。

```
> quit
[[0,1474010060.188753,3.24249267578125e-05],true]
% groonga testdb/db
> column_list User
[[0,1474010067.628873,0.0002775192260742188],[[["id","UInt32"],["name","ShortText"],["path","ShortText"],["type","ShortText"],["flags","ShortText"],["domain","ShortText"],["range","ShortText"],["source","ShortText"]],[256,"_key","","","COLUMN_SCALAR","User","UInt32",[]],[258,"first_name","testdb/db.0000102","var","COLUMN_SCALAR|PERSISTENT","User","ShortText",[]],[257,"last_name","testdb/db.0000101","var","COLUMN_SCALAR|PERSISTENT","User","LongText",[]]]]
```

再度データベースを開き直したので、期待通りに`last_name`カラムは`LongText`型になっています。

```
[257,"last_name","testdb/db.0000101","var","COLUMN_SCALAR|PERSISTENT","User","LongText",[]]
```

groongaコマンドを終了したくない場合には、[database_unmapコマンド](http://groonga.org/ja/docs/reference/commands/database_unmap.html)を実行する方法もあります。
このコマンドを実行すると、それまでキャッシュされた情報を一旦破棄するからです。


# まとめ

groongaコマンドを利用して複数のプロセスからGroongaにアクセスするときに、スキーマの変更を伴う場合に注意するべきポイントを説明しました。
変更したスキーマ定義が反映されていないときには、複数のプロセスでアクセスしたりしていないか確認してみてください。
もし、上記の状況になったら、groongaコマンドを一旦終了するか、database_unmapコマンドを実行するなどしてデータベースを開き直すことで解決できます。