Groongaを検索エンジンとして使う

# はじめに
こんにちは[rilmayer](https://twitter.com/rilmayer_jp)です。
この記事はアドベントカレンダー「[Search&Discovery 全部俺](https://adventar.org/calendars/4691)」12日目の記事となります。（寝るまでが・・・

今日は国産の全文検索エンジンである[Groonga](http://groonga.org/ja/)の紹介をしたいと思います。
[前回の記事](https://qiita.com/rilmayer/items/670e2a24fe16a00d1407)で紹介した全体像のうちの検索エンジンの一例ですね。

#Groongaとは？
転置索引を用いた高速・高精度な全文検索エンジンです。
カラムストアを持つ列指向のデータベースとしての側面も持っているようで、集計等にも使えるらしいです。
日本語のトークナイズやTF-IDFによるスコアリング（ソート）にも対応しており、検索エンジンとして必要な機能が一通り提供されているため実サービスで検索エンジンとして採用している企業もあります。

# 検索エンジンとしての基本的な操作を試してみる
## Groongaインストールと立ち上げ
[こちら](http://groonga.org/ja/docs/install/docker.html#pulling-image)で色々な方法が紹介してありますが、今回はローカル環境（OSX）のDockerで試してみます。
Docker Imageめっちゃ軽くてびっくり！
このDocker ImageはデフォルトでMecabとかも入ってるので、そのまま使えて便利ですね。

と、思ったのですが実際に即した感じで使いたかったので、以下のようなDockerfileを作成してサーバーモードで立ち上げます。

```Dockerfile:Dockerfile
FROM ubuntu:18.04

RUN apt update && \
  apt install -y

RUN apt-get -y install groonga \
    groonga-tokenizer-mecab \
    groonga-server-http

EXPOSE 10041
CMD ["service", "groonga-httpd", "start"]
```

以下のようにbuildしてrunすれば立ち上がります。

```bash
# build
docker build -t groonga .

# run
# 中に入る場合
docker run -i -t -p 10041:10041 groonga bash

# デーモンとして起動する場合
docker run -t -d -p 10041:10041 groonga
```

ということで、 http://localhost:10041/ にアクセスすると以下のようなコンソールにアクセスできます。便利。

<img width="701" alt="スクリーンショット 2019-12-12 9.26.13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108223/818b6df8-00fd-2a69-471e-7aa888fd812f.png">


## テーブルの作成・データの投入・検索
groongaはあくまで「全文検索ができるデータベース」なので、テーブル作成して利用する形になります。
Elasticsearchだとindexと言ったり、Solrだとcoreと言ったりします。

今回は操作スクリプトをPythonで書いてみました。（公式はRuby推しっぽい気がしますが・・・）
groongaでは新たにカラムを作成する際に「インデックス型」という型を指定することにより、そのカラムに対して全文検索を行うことができます。

```python
import requests
import json

# ステータス取得
base_url = 'http://localhost:10041'
url = base_url + '/d/status'
res = requests.get(url)
json.loads(res.content) # => [[0,1576165601.392383,0.0001587867736816406],{"alloc_count":309,"starttime":1576110346,"start_time":1576110346,"uptime":55255,"version":"8.0.0","n_queries":0,"cache_hit_rate":0.0,"command_version":1,"default_command_version":1,"max_command_version":3}]

```

### 全文検索用のテーブルとカラム追加
ここがgroongaのポイントになるかと思いますが、通常のテーブルを作成した後に全文検索用のテーブルとカラムを作成しメインのテーブルに紐付けて使います。
ここでは仮にアイドル名とその説明が含まれているテーブルを作成してみることにします。

<img width="626" alt="スクリーンショット 2019-12-13 1.44.00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/108223/d38aff66-fe6b-eca4-2af3-3c8dc0273c67.png">


```python
# Idolテーブル作成
url = base_url + '/d/table_create'
payload = {
    'name': 'MyFavoriteIdols', # テーブル名
    'flags': 'TABLE_HASH_KEY',
    'key_type': 'ShortText'
}
headers = {'content-type': 'application/json'}
res = requests.get(url, params=payload, headers=headers)

# カラム作成
column_settings = [
    {
        'table': 'MyFavoriteIdols',
        'name': 'idol_name',
        'flags': 'COLUMN_SCALAR',
        'type': 'Text'
    },
    {
        'table': 'MyFavoriteIdols',
        'name': 'description',
        'flags': 'COLUMN_SCALAR',
        'type': 'Text'
    }
]
url = base_url + '/d/column_create'
headers = {'content-type': 'application/json'}
for column_setting in column_settings:
    res = requests.get(url, params=column_setting, headers=headers)

# 語彙表作成
url = base_url + '/d/table_create'
payload = {
    'name': 'Terms', # テーブル名
    'flags': 'TABLE_PAT_KEY', # 主キーをパトリシア木に格納
    'key_type': 'ShortText', # flagを指定した場合に必要
    'default_tokenizer': 'TokenMecab', # MeCabによりトークナイズ
    'normalizer': 'NormalizerAuto' # 組み込みのノーマライザー http://groonga.org/ja/docs/reference/normalizers/normalizer_auto.html
}
headers = {'content-type': 'application/json'}
res = requests.get(url, params=payload, headers=headers)

# カラム追加
column_setting = {
    'table': 'Terms',
    'name': 'idol_description',
    'flags': 'COLUMN_INDEX|WITH_POSITION',
    'type': 'MyFavoriteIdols',
    'source': 'description'
}

url = base_url + '/d/column_create'
headers = {'content-type': 'application/json'}
res = requests.get(url, params=column_setting, headers=headers)
```

### データの追加
```python
# descriptionはwikipediaの該当ページから拝借
#   https://ja.wikipedia.org/wiki/AmiinA
#   https://ja.wikipedia.org/wiki/%E3%83%BB%E3%83%BB%E3%83%BB%E3%83%BB%E3%83%BB%E3%83%BB%E3%83%BB%E3%83%BB%E3%83%BB
#   https://ja.wikipedia.org/wiki/%E3%83%A4%E3%81%AA%E3%81%93%E3%81%A8%E3%81%9D%E3%81%A3%E3%81%A8%E3%83%9F%E3%83%A5%E3%83%BC%E3%83%88
#   https://ja.wikipedia.org/wiki/BEYOOOOONDS
#   https://ja.wikipedia.org/wiki/TipToe.
payload = [
    {
        "_key": "amiina",
        "name": "amiinA",
        "description": '雑誌「ニコ☆プチ」の読者モデルであった[1]amiと、miinaの2人で「  あみいな」を結成。「あみいな」は2人の名前「あみ」と「みいな」を掛け合わせたもの。\n\n2012年8月、当時同じ事務所のMYM Melodyが出演するイベントで初披露。\n\n結成当初は、ももいろクローバーZやAKB48などのカバーだったが、オリジナル曲を作る際にプロデューサーとして楽曲クリエイティブチーム・nanolineの齊藤州一が加わる[2]。\n\n2013年8月、初のシングル「1☆2☆3☆/マインドトラベル」を発売。アーティスト表記をアルファベットの「amiina」とする。\n\n2014年4月に吉田豪が「With you」（bayfm）[3]で、5月に南波一海がインターネット放送「南波一海のアイドル三十六房」[4]で「マインドトラベル」を紹介したことから、その頃から音楽に詳しいアイドルファンから反応が来るようになり、齊藤は「音楽的に自由に攻めてもいいのではないか」と思うようになったと語っている[5]。\n\n2016年2月13日のLIVEをもってmiinaが脱退[6]、活動休止となる。\n\n2016年3月22日、スタッフのTwitterで、表記を「amiinA」に変更し、新メンバーを加え活動を再開することを発表[7]。 5月3日、渋谷WWWで開催された「WonderTraveller!!! act.4」で、新メンバーmiyuが加入し、amiinAとして再始動[8]。\n\n2016年12月28日のライブをもち、miyuの高校受験のため活動を一時休止[9]。\n\n2017年4月15日よりライブ活動を再開[10]。\n\n新生amiinAは、「awake mind into ideal naked Avalon」の略。\n'
    },
    {
        "_key": "dots",
        "name": "・・・・・・・・・",
        "description": '2016年4月に開催された「『女の子の東京をつくろう!!』プロジェクト アイドルオーディション」で選ばれたメンバーによるユニット[1][2]。\n\nコンセプトは「ポスト・ポケモンGO!時代のアイドル」「常に纏える(まとえる)アイドル」[3]「都市の幽霊」[4]。\n\n名前や顔を出さない、シューゲイザーやアンダーグラウンド・パンクなどを踏襲した楽曲、テクノロジーを用いた全く新しいアイドル活動などを特徴とする。\n\nライブのことを「観測」、ファンのことを「観測員」と呼ぶ。\n\nライブ中は写真撮影可能、動画撮影不可[5]。\n'
    },
    {
        "_key": "ysm",
        "name": "ヤなことそっとミュート",
        "description": 'BELLRING少女ハート→There There Theresの運営を手掛けたクリムゾン印刷による新アイドルプロジェクト。ただし、BELLRING少女ハートディレクターの田中紘治およびAqbiRecは運営には関与せず[1]、エンジニアの慎秀範（DCG代表）と林惇太、作曲家・アレンジャーのタニヤマヒロアキ（2019年離脱[注 1]）からなるチーム「DCG ENTERTAINMENT」がプロデュースを担っている[2]。\n\nコンセプトは「ヤなことだらけの日常をそっとミュートしても何も解決しないんだけど、とりあえずロックサウンドに切ないメロディーを乗せて歌ってみる事にする。」[3]。グランジやシューゲイザーといったオルタナティブ・ロックを主体とする。なお、タニヤマはヤナミューではマネージメントとパフォーマンスを主に担当[2]していたため、本職である作曲・編曲はオープニングSEの「ヤなことFriday」以外手がけていない。\n\nライブ中の写真撮影・動画撮影は原則として可能。リフト、ダイブ、サーフといった"人の上に乗る行為"は2017年4月29日以降禁止。\n'
    },
    {
        "_key": "byooooonds",
        "name": "BEYOOOOONDS",
        "description": 'ハロプロ研修生から選ばれたメンバーで結成されたユニット、CHICA#TETSU（チカ＃テツ）、雨ノ森 川海（あめのもり かわうみ）、および『ハロー!プロジェクト“ONLY YOU”オーディション』合格者により構成される[1]。\n「〜を超えて」「〜の向こう側へ」という意味の英単語「Beyond」をグループ名の語源とし、既成の枠組などを超えて、自由に未来へ大きくビヨーンと伸びていってほしいという思いが込められている。形を変幻自在にビヨーンびよーんと変貌させる、スライムのようなグループを目指す[1]。\nBEYOOOOONDSのリーダーは、2018年12月現在、公式には発表されていない。\n'
    },
    {
        "_key": "tiptoe",
        "name": "tipToe.",
        "description": '制作プロダクション合同会社SOVAの社内プロジェクトとして立ち上がり、2016年夏に行ったオーディション[1] で一般から選ばれたメンバーで結成。「みんなで青春しませんか？」をコンセプトに掲げ、青春や学校をモチーフとした楽曲やヴィジュアルが持ち味。メンバーは3年任期制で加入から3年で必ず卒業することになっている。\n\n統括及び音楽プロデューサーは音楽レーベル6jomaProject主宰の本間翔太。映像や衣装などを担当するヴィジュアルプロデューサーは写真家の長谷川圭佑。楽曲や振付などに自身もアーティストとして活動する若手クリエイターを数多く起用することも特徴。\n\n楽曲は、渋谷系ギターポップをベースにエレクトロニカ・チップチューンの要素を盛り込んだ爽やかでエモーショナルなものが多い。\n\nグループ名は「少しだけ背伸びして、今よりも高い所に届くように」という思いを込めて「背伸びする」(=「つま先で立つ」)を意味する英語「stand on tiptoe」から[2]。\n'
    },
]

# データのロード
url = base_url + '/d/load?table=MyFavoriteIdols'
headers = {'content-type': 'application/json'}
res = requests.post(url, data=json.dumps(payload), headers=headers)
```

### データの検索
以下のようなコマンドで検索ができます。

```python
# 検索
query = 'メンバー'
column_setting = {
    'table': 'MyFavoriteIdols',
    'query': f"description:@{query}",
    'output_columns': '_key,idol_name,description',
    'sort_keys': '-_score'
}

url = base_url + '/d/select'
headers = {'content-type': 'application/json'}
res = requests.get(url, params=column_setting, headers=headers)
results = json.loads(res.content)

# 結果表示
print('hit:  ', results[1][0][0][0])
clumn_names = results[1][0][1]
for result in results[1][0][2:]:
    for i, clumn_name in enumerate(clumn_names):
        print(clumn_name[0], ':  ', result[i][0:50])
    print()
```

結果は以下のような感じ。

```
hit:   4
_key :   amiina
idol_name :   amiinA
description :   雑誌「ニコ☆プチ」の読者モデルであった[1]amiと、miinaの2人で「  あみいな」を結成。「あ

_key :   tiptoe
idol_name :   tipToe.
description :   制作プロダクション合同会社SOVAの社内プロジェクトとして立ち上がり、2016年夏に行ったオーディシ

_key :   byooooonds
idol_name :   BEYOOOOONDS
description :   ハロプロ研修生から選ばれたメンバーで結成されたユニット、CHICA#TETSU（チカ＃テツ）、雨ノ森

_key :   dots
idol_name :   ・・・・・・・・・
description :   2016年4月に開催された「『女の子の東京をつくろう!!』プロジェクト アイドルオーディション」で選
```


# おわりに
今回はgroonagaを検索サーバーとして使ってみるためのさわりを紹介してみました。
今後は実際のサーバー上に立てた場合の性能試験、テストデータによる検索結果比較などやってみたいです。