全文検索はじめの一歩

今のプロジェクトで全文検索をいつかは使いそうのなので 最近Groongaで学ぶ全文検索という勉強会に参加させていただいています。

勉強会で学んだ内容の復習を兼ねて実際に手を動かしてみようと思います。
今回は実際にGroongaを使ってスキーマ設計をしてみます。
※このブログを書いている時にちょうどGroongaで学ぶ全文検索 2016-02-12があったので今回のブログの設計チェックがお題になりました。

全文検索とは

複数文書にまたがって、文書の全文を対象とした検索

入力(クエリー、キーワードなど)→全文検索→出力(マッチした文書)

上記の様に複数の文書の中からクエリやキーワードなどの入力にマッチした文書を出力として返す

全文検索の仕組み

複数の文書の中の文字列を毎回頭から検索していると時間がかかるため
辞書の索引の様に、事前にどの文書にマッチする文字列が存在しているかという情報を保持するインデックス作る。
全文検索の際には作成したインデックスからマッチする文書を検索する。

Groongaで全文検索してみる

※Groongaについては下記を参照してください。

http://groonga.org/ja/docs/characteristic.html

環境

Vagrantにテスト用環境を準備して検証しました。
CentOS 7.1

確認用データ取得

今回は確認用に国立国会図書館でインターネット公開されている図書を検索してみます。
国立国会図書館デジタルコレクション書誌情報 インターネット公開.図書 http://www.ndl.go.jp/jp/aboutus/standards/opendataset.html#opendataset01 dataset_201601_t_internet.tsv 約34.9万件

下記のようなデータ

URL     タイトル        巻次    シリーズ        版表示  著者    出版者  出版年  ISBN    冊数(ページ数・大きさ)        公開範囲
http://dl.ndl.go.jp/info:ndljp/pid/1453136      一、二年生の基礎英作文                岡田実麿 著     青々書院        昭和14          251, 40p ; 19cm インターネット公開(保護期間満了)
http://dl.ndl.go.jp/info:ndljp/pid/1273322      一、二年生の急所を掴む漢文入門                        森本和司 著     駸々堂書店      昭和8           366p ; 20cm     "インターネット公開(裁定) 著作権法第67条第1項により文化庁長官裁定を受けて公開 裁定年月日: 2012/03/01"
http://dl.ndl.go.jp/info:ndljp/pid/1273298      一、二年生の急所を掴む植物学  

Groongaインストール

$ sudo rpm -ivh http://packages.groonga.org/centos/groonga-release-1.1.0-1.noarch.rpm
$ sudo yum makecache
$ sudo yum install -y groonga

$ which groonga
/usr/bin/groonga

スキーマ設計手順

  1. まずは何が返ってきて欲しいかを決める:書籍

  2. 何で検索したいかを決める:タイトル、著者、出版社などを想定

  3. 返ってきて欲しいもののtableを作る:Books

  4. 検索したいものをcolumnにする:title,author,publisher

データ準備

必要なデータに絞ってJSONに変換(簡易な変換プログラムを作成しました)
実際のデータは下記のような形です。
load.txt

[
  {
    "_key": "http://dl.ndl.go.jp/info:ndljp/pid/902895",
    "title": "英語雑爼 : 対照双訳",
    "author": "高橋五郎 訳註",
    "publisher": "建文館"
  },
  {
    "_key": "http://dl.ndl.go.jp/info:ndljp/pid/869681",
    "title": "英語指教図",
    "author": "堤吉兵衛 編",
    "publisher": "堤吉兵衛"
  },
・
・
・
  {
    "_key": "http://dl.ndl.go.jp/info:ndljp/pid/3948475",
    "title": "PPP(汚染者負担の原則)の学際的研究",
    "author": "",
    "publisher": "(財)統計研究会"
  }
]

データベース、テーブル作成

create.txt

table_create --name Books --flags TABLE_HASH_KEY --key_type ShortText
column_create --table Books --name title --flags COLUMN_SCALAR --type ShortText
column_create --table Books --name author --flags COLUMN_SCALAR --type ShortText
column_create --table Books --name publisher --flags COLUMN_SCALAR --type ShortText

オプションの補足

--flags TABLE_HASH_KEY:

  • キーをサポート、高速
  • 前方一致検索など高度な検索はできないが、
    前方一致はなどはINDEXテーブルでやるため今回はTABLE_HASH_KEYを選択
  • 後から更新や削除をする可能性があればこちらを選択する
  • URLは4Kを超える事があるので要注意、サイズが4Kを越えないと仮定してこのまま進める
  • サイズが小さくてユニークなものをなものをキーにする(全てのデータにISBNがあればISBNが良い)
  • 一括でデータを入れ替えるなど更新、削除しないのであればTABLE_NO_KEYでも良い

http://groonga.org/ja/docs/reference/tables.html#table-hash-key

--key_type ShortText:

  • 4,095バイト以下の文字列、今回はURLをキーにするのでこちらを選択

--type ShortText:

  • 4,095バイト以下の文字列、タイトル、著者、出版社共に4,095バイトで足りそうなのでこちらを選択
  • 著者は複数人いる場合もあるのでベクターのがより良いが、今回はスカラーにする
    その場合にはload用のJSONデータも配列にする必要がある

http://groonga.org/ja/docs/reference/types.html#shorttext

作成

$ groonga -n ./Books.db < create.txt

確認

$ groonga Books.db dump
table_create Books TABLE_HASH_KEY ShortText
column_create Books author COLUMN_SCALAR ShortText
column_create Books publisher COLUMN_SCALAR ShortText
column_create Books title COLUMN_SCALAR ShortText

$ groonga Books.db
> select --table Books
[[0,1455198347.19075,0.000711917877197266],[[[0],[["_id","UInt32"],["_key","ShortText"],["author","Text"],["publisher","Text"],["title","Text"]]]]]

データロード

$ groonga Books.db < load.txt
[[0,1455202019.36071,1.84122562408447],348520]

ひとまず大好きな「日本酒」でタイトルを検索してみると0.6秒で24件ヒット
(検索結果はわかりやすいように整形しています。)

$ time groonga Books.db select --table Books --query title:@日本酒
[
  [
    0,
    1455202671.73876,
    0.595662832260132
  ],
  [
    [
      [
        24
      ],
      [
        [
          "_id",
          "UInt32"
        ],
        [
          "_key",
          "ShortText"
        ],
        [
          "author",
          "ShortText"
        ],
        [
          "publisher",
          "ShortText"
        ],
        [
          "title",
          "ShortText"
        ]
      ],
      [
        113657,
        "http://dl.ndl.go.jp/info:ndljp/pid/957582",
        "大阪財務研究会 編",
        "大阪財務研究会",
        "最新日本酒醸造法"
      ],
      [
        154271,
        "http://dl.ndl.go.jp/info:ndljp/pid/964665",
        "仁木悦太郎 著",
        "新日本酒研究所",
        "新日本酒の研究"
      ],
・
・
・
      [
        269942,
        "http://dl.ndl.go.jp/info:ndljp/pid/848165",
        "徳野嘉七 著",
        "帰一社",
        "日本酒改良実業問答"
      ]
    ]
  ]
]
real    0m0.620s
user    0m0.602s
sys 0m0.017s
  • --query title:@日本酒 :インデックス作成前の場合部分一致、インデックスを作ると全文検索を使うという意味

インデックス作成

転置インデックスとして用いるテーブルとインデックス用カラム作成 create_indexes.txt

table_create --name Indexes --flags TABLE_PAT_KEY --key_type ShortText --default_tokenizer TokenBigram --normalizer NormalizerAuto
column_create --table Indexes --name book_title --flags COLUMN_INDEX|WITH_POSITION --type Books --source title

実行

$ groonga Books.db < create_indexes.txt

テーブルのオプションを補足
--flags TABLE_PAT_KEY:

  • 小さく、高度な検索機能もサポートしている、
  • 前方一致検索が使えるものにしたいがTABLE_DAT_KEYは大量のレコードを保存する用途には向いていないとの事でこちらを選択
  • 前方一致検索が使えるとバイグラムでも前方一致でインデックスを検索できるので「酒」1文字でもヒットさせられる
  • MySQL5.7もNgram、日本語サポートしたが1文字では検索できない

http://groonga.org/ja/docs/reference/tables.html#table-pat-key

--default_tokenizer TokenBigram:

--normalizer NormalizerAuto:通常は NormalizerAuto ノーマライザーを使うべきとの事で今回はこちらを選択
ノーマライズなどについてもまた別途まとめようと思います。

http://groonga.org/ja/docs/reference/normalizers.html#normalizerauto

カラムのオプション補足 --flags COLUMN_INDEX|WITH_POSITION:
位置もチェックしたいため指定

例)下記を「日本酒」で検索した際に下はひっかかって欲しくない
日本酒万歳
酒井さんの日本万歳

--type Books:テーブルを指定
--source title:タイトルの転置indexを作る

  • 著者でも検索したい場合にはsourceにtitle,authorのように指定する
    ただしWITH_SECTIONの指定も必要こちらを設定することによって
    タイトルで検索したいのに著者でもひっかかるということが無くなる。

再度日本酒で検索してみるとヒット件数は変わらず検索時間が約0.03秒と約1/20になりました!
最新日本酒醸造法なんていう面白そうな書籍を高速で発見できるようになりました(^_^)
http://dl.ndl.go.jp/info:ndljp/pid/957582

$ time groonga Books.db select --table Books --query title:@日本酒
[
  [
    0,
    1455203109.6853,
    0.00239753723144531
  ],
  [
    [
      [
        24
      ],
      [
        [
          "_id",
          "UInt32"
        ],
        [
          "_key",
          "ShortText"
        ],
        [
          "author",
          "ShortText"
        ],
        [
          "publisher",
          "ShortText"
        ],
        [
          "title",
          "ShortText"
        ]
      ],
      [
        113657,
        "http://dl.ndl.go.jp/info:ndljp/pid/957582",
        "大阪財務研究会 編",
        "大阪財務研究会",
        "最新日本酒醸造法"
      ],
      [
        154271,
        "http://dl.ndl.go.jp/info:ndljp/pid/964665",
        "仁木悦太郎 著",
        "新日本酒研究所",
        "新日本酒の研究"
      ],
・
・
・
      [
        269942,
        "http://dl.ndl.go.jp/info:ndljp/pid/848165",
        "徳野嘉七 著",
        "帰一社",
        "日本酒改良実業問答"
      ]
    ]
  ]
]

real    0m0.030s
user    0m0.025s
sys 0m0.004s

参考資料)
チュートリアル http://groonga.org/ja/docs/tutorial/introduction.html

テーブルについて http://groonga.org/ja/docs/reference/tables.html

table_createコマンド http://groonga.org/ja/docs/reference/commands/table_create.html

Groonga データ型 http://groonga.org/ja/docs/reference/types.html

カラムについて http://groonga.org/ja/docs/reference/column.html