アペフチ

Sendagaya.rb #139

今日もSendagaya.rb #139@株式会社ランチェスターに参加して来た。『メタプログラミングRuby 第2版』を少し読んだ後、参加者の持ち込んだ相談事の話をしたりした。

メタプログラミングRuby method_missing

今日は「3.3 method_missing」から。

Hash#defaultなんてあったんだ、とか、引数なんて取れたんだ、と話していたが、本筋は問題なく読み進んでいた。

相談事

その後は参加者の一人が持ち込んだ相談について話した。ただ、業務と直結していてここに書いていいか分からないので割愛。面白い話だったし、実際やってみてどうなったか、後日ぜひ聞きたい。

Sendagaya.rb KPT

次回は第140回というまことに切りのいい数値なので、fukajunさんの発案で、KPTをやることになった。多分、飲みながら。

日記の検索部分をHTTP/2対応した

この日記の検索機能(フッターの検索フォームからできる)ではgroonga-httpdを使っている。日記本文はGitHub Pagesにホストしてもらっている。GitHub PagesはHTTPでもHTTPSでもどちらでもアクセスできて、ツイッターなどでURIを貼る時には僕はHTTPSの方を使っている。だがgroonga-httpdはこれまでHTTPSに対応していなかったので、XHRで接続することができなかった。仕方なく前段にNginxを立てて、そこでTLSの終端をしていた。

日課のapt-get update && apt-get upgradeをしていたらGroongaの各種パッケージが降って来たので更新内容を確認しに行った(6.0.0リリース - 2016-02-29)。そこにTLSサポートのことが書かれていたので、早速この日記の検索サーバーでもアップデートして、TLSを有効にした(と書くと白々しいか、TLSサポートは僕が要望した物だった:https://osdn.jp/projects/groonga/lists/archive/dev/2016-February/003951.html)。

また、groonga-httpdは前々からHTTP/2が使えるようになっていたので、ついでにそちらも有効にした。

検索は単純にキーアップイベントを拾い、入力の一文字目からその都度groonga-httpdに検索リクエストを投げるようにしているので、HTTP/2でコネクションを張りっぱなしにして検索できるのは効果が大きいのではないかと思う(測ってない)。

ただ、検索用に日記のデータを入力するのは手元のスクリプトでgroonga-client gemを使って実行していたのだが、このgemがHTTPS対応していないのでそこを対応しないといけない。それまでは、今日のこの日記以降の記事は検索対象にならない。HTTP接続用の別のポートを開けてもいいが、まあいいだろう。

Groongaでできる検索方法あれこれ

Groongaで学ぶ全文検索 2016-02-26に行って来た。

今日のお題は「select」。selectというのは、Groongaの、全文検索エンジンとして最も重要な機能である検索を行うためのコマンド。

前回、実際のテーブルやデータを見ながら話せたのがよかったという声が多かったので、今回も具体的なGroongaの使い方を、というところからの選択。

まず僕が使っている以下の機能を簡単に紹介したあとで、それ以外のを@ktouさんが紹介するという流れだった。

僕が使っている機能:

  • 全文検索
  • 完全一致検索 … EPUB Searcherの削除機能でレコードを特定する時に使っている
  • snippet_html出力 … この日記で、検索結果中の検索語をハイライトしている
  • グルーピング(ドリルダウン) … EPUB Searcherで著者一覧ページで、著者の下に本一覧を表示するのに使っている
  • カラムの重み付け … この日記の検索で、本文よりタイトルとタグでのヒットを重視している。

以下、@ktouさんが紹介してくれた機能:

クエリー言語として解析

例えばGoogleで「A B C」で検索すると「Aが含まれるかつBが含まれるかつCが含まれるページを探す」という意味になる。「A or B or C」と検索すると「Aが含まれるまたはBが含まれるまたはCが含まれるページを探す」という意味になる。ツイッターで「groonga -lang:ja」みたいな検索を僕はよくしていて、ここでの「lang:ja」(日本語のツイート)「-」(を除外する)というのもある(Groongaでもこの記法そのまま使えるとのこと。lang:jalangテーブルに対するjaでの検索になり、「-」を付けると除外になる)。こうした、クエリーの中に検索語以外の命令を含められる時、この命令を表現する物がクエリー言語と呼ばれる。Groongaにもこのクエリー言語を理解する能力がある。

集計機能

グルーピングした際、グループそれぞれについて、何件のレコードがヒットしたかを集計することもできる。件数を数えるほか、あるカラムの値の合計、平均、最大値、最小値を求めることもできる。

忘れていたけど、この、件数を取得して表示するのはEPUB Searcherで使っていた。

クエリー展開

「焼き肉」で検索した時、勝手に「焼き肉 OR 焼肉 OR やきにく」というクエリーに変更して検索してくれる機能。「焼き肉と焼肉とやきにくを同一視する」ということ自体はユーザー(アプリケーション開発者)が予め登録しておく必要がある(どうやって?)。何を同一視したいかというのは、アプリケーションによって変わるからだ。

重みの底上げ

いい名前が付いていないと言っていた。

検索時にカラムごとに重みを設定して(タイトルは重くする、本文はそれほどでもない、など)最終的にレコードごとに総合スコアを出す。これが通常の重み付け。

その総合スコアの計算の後に、更に重みの数値を足すことができる。例えば、キャンペーンフラグカラムがONだと重み+10する、など。

通常の重み付けはカラムに対しての指定だが、この底上げはレコード一つ一つに対しての指定となる点が特徴となる。

使い方例も紹介されたが公開していいかどうか分からないとのことだったのでここでは他の例で説明する。Google検索では、過去に検索して閲覧したことがあるページが、次からの検索で上位になりやすいようになっている。これは、「閲覧した回数」といったカラムを持っておいて、総合スコアを計算し終わった後にそのカラムの値を追加して検索上位に持ってくる、というふうに、Groongaを使う場合は実装することができる。

ソート

ソートできる。

途中から結果を出せる

オフセットとかページネーションとかいうやつ。

参照先でも検索できる

SQLだとJOINするようなのがGroongaはJOINせずに検索できる。

記事テーブルとコメントテーブルがあって、記事に対してコメントが結び付いているとする。この時、SQLだと記事の主キーとコメントの主キーをJOINで結び付ける。

  1. 記事とコメントがくっついたテーブルを作る(と見做すことができる)
  2. コメントテーブルの該当カラムに対して検索を実施し、
  3. 「くっついたテーブル」の記事テーブルの使いたいカラムを取得する

となる。

Groongaはコメントテーブルに記事への参照を記事テーブルにコメントへの参照を(発表していて逆だねって指摘してもらった)直接保持させることができる。だから、JOINのような追加の命令無く

  1. コメントに対して検索し、
  2. そのコメントが付いている記事を取得する

ということができる。

記事に対して、後から関連商品みたいなテーブルを加えたくなることがあるかも知れない。この時、RDBMSであれば(正規化されていれば)単にそのためのテーブルを作成し、検索時にJOINで結び付けるようにすることができる。記事テーブルには変更が必要ない。

Groongaの場合は記事テーブルに関連商品テーブルへの参照を持たせることになる(か逆方向の参照を持たせる。方向はアプリケーションによる)。既存の記事テーブルに対する変更が発生する。しかし、Groongaはカラム指向データベースなので、こうしたカラムの追加操作は低コストでできるので問題にならない(RDBMSでALTER TABLEとなると、件数によってはおおごとだ)。

「Solrはこうしたテーブル跨ぎの検索はうまくできないんでは?」と@ktouさんが言っていたので識者の指摘を待ちたい。