隔週連載groonga

第4回rroongaを使ったソースコード検索エンジンMilkode

はじめまして、おんがえしと申します。

今回は私が作っているソースコード検索エンジンのMilkodeにて、rroonga(とその内部で動くgroonga)がどのように使われているのかを紹介します。

Milkodeの検索画面
Milkodeの検索画面

ソースコードを検索するということ

プログラマの仕事はプログラムを書くことですが、同じくらい「検索する」ことは多いのではないでしょうか。

  • ある関数の名前を変えたいので使っている箇所をすべて見つけたい
  • ライブラリの仕様が変わった。使っているコードはどこだったか?
  • コードリーディング中。今読んでいる関数の実体はどこにあるのか?

ベーマガを買ってゲームのプログラムを写経していた頃から時代は進み、WebにアクセスすればゲームやOS、Webアプリなどのソースコードを無料[1]で手に入れることができるようになりました。必要なものをソースコードから検索し、知識として取り込んでいく技術は今後ますます重要になってくるはずです。

既存の検索ツールの問題点

私もソースコードを検索するためのツールとして色々なものを使ってきました。その内に「こうなったらいいのにな」とか「こういう機能がほしい」といった要求が自然と湧いてくるようになりました。

Wikipediaの「全文検索」の項から言葉を借りて、順番にファイルの中身を調べて検索するタイプをgrep型あらかじめ索引(インデックス)を作っておき索引データを元に検索するものをインデックス型と呼ぶことにします。

それぞれのタイプの不満を挙げてみます。

grep型の不満
  • ファイル数が増えると遅い
  • 検索のたびに*.rbなどと対象のファイルを指定するのが面倒。基本はプロジェクト単位で検索したい
  • .git.svn*~などは自動で検索対象から外してほしい[2]
  • .gitignoreの除外対象に含まれているものも検索対象から外してほしい[2]
  • ブラウザからも検索したい
インデックス型の不満
  • 複数のキーワードにマッチするを見つけることができない
    • ファイル単位で見つけるものが多い
  • 非公開のコードも検索対象に含めたい
    • Webアプリのみ提供しているタイプのものはオープンソースしか検索対象にできない
  • grep型と同じくらい、簡単にインストールできるようにしてほしい

これらの問題を上手く解決することができればそれなりに需要があるのではないかと考え、Milkodeの開発が始まりました。前身となるgrenを作り始めたのが2010年の7月なのでもう2年以上携わっていることになります[3]⁠。

さてどうやって作ろう?

ソースコード検索エンジンを作ろうと思い立ったものの、grep型はともかく、インデックス型のものを作るにはいったい何をどうすればいいのかまったく分かりませんでした。どうも全文検索エンジンと呼ばれる類いのソフトウェアを使えばよいことが分かってきたため、次の要素を条件に全文検索エンジンの選定(Googleでキーワードを変えながらカチカチすすること)を始めることにしました。

  • 主要なOSで使える
  • (比較的)簡単にインストールできる
  • 日本語の情報が豊富

groongaとの出会い

いくつかの検索エンジンを調べていくなかで、カラムストア機能付き全文検索エンジンのgroongaに辿り着きました。

他にも候補はあったのですが、次の理由でgroongaに惹かれたような気がします。

  • Sennaという有名な検索エンジンの後継(らしい)
  • 他の検索エンジンと比べると後発のため、老舗のものにありがちな問題が解消されていそう
  • 日本人の開発者を中心に作られているので日本語の情報が豊富
  • MLのログを読むと質問やバグ報告に対するレスポンスも速い
  • 若いプロジェクトなのでパッチも積極的に受け入れてくれそう

正直検索エンジンに対する知識が皆無だったため、最後はMLのログやサイトの雰囲気から感じられるフィーリングで決めたと思います。

その後、須藤さんや森さんをはじめとするgroonga開発者の皆さんとお会いする機会があったり、須藤さんとTwitterでソフトウェア開発したり、rroongaにWindows関連のパッチを取り込んでもらえたりしたので、最初の直感は間違いではなかったのかなと思います。

もう少し真剣にリファレンスマニュアルなどを読み始め、最終的に「groongaを使おう!」となった決定打は次の3点だったと記憶しています。

  1. 単体で全文検索+ストレージとして動作する
  2. Rubyだけで書ける
  3. ライブラリとして動作する

順に説明していきます。

1. 単体で全文検索+ストレージとして動作する

groongaの場合は、次の両方をgroongaだけで行うことができます。

  1. ファイルの内容をデータベースに格納
  2. 格納されたデータを全文検索

別途MySQLなどのストレージエンジンのインストールを要求するタイプもあったのですが、Milkodeのようなデスクトップアプリとしても動作するものは必要なソフトウェアの数は極力少ない方がよい[4]のでとても助かりました。

2. Rubyだけで書ける

groonga本体はC言語で書かれていますが、ラングバプロジェクトのライブラリを使えば基本的な操作はすべてRubyだけで書くことができます。次はrroongaで作るブックマークアプリケーションの例です。

require 'rubygems'
require 'groonga'

# データベースの作成
Groonga::Context.default_options = {:encoding => :utf8}
Groonga::Database.create(:path => "bookmark.db")

# テーブルの定義
Groonga::Schema.define do |schema|
  Groonga::Schema.create_table("Items", :type => :hash) do |table|
    table.text("title")
  end

  Groonga::Schema.create_table("Terms",
                               :type => :patricia_trie,
                               :key_normalize => true,
                               :default_tokenizer => "TokenBigram") do |table|
    table.index("Items.title")
  end
end

# レコードの追加
items = Groonga["Items"]
items.add("http://gihyo.jp"                  , title:"技術評論社")
items.add("http://ja.wikipedia.org/wiki/Ruby", title:"Ruby")
items.add("http://www.ruby-lang.org/ja"      , title:"オブジェクトスクリプト言語Ruby")

# 全文検索
result = items.select {|record| record.title =~ "Ruby"}

# 結果を集計
p result.map{|record| record.title} #=> ["Ruby", "オブジェクトスクリプト言語Ruby"]

Milkodeではrroongaを使っており、データベースの作成、テーブルやレコードの管理、全文検索まですべての処理をRubyだけで記述しています。

3. ライブラリとして動作する

rroongaはRubyGemsライブラリとして提供されているため、インストールはgemコマンド一発です。

$ gem install rroonga

bundlerであればGemfileに次の記述を一行追加するだけです。とっても簡単ですね!

gem 'rroonga'

すべての機能はRubyのクラスやメソッドとして提供されているため、外部プログラムを呼び出す必要もありません。外部プログラムが絡むとOSによって挙動が変わってしまうことがあるため、この辺もありがたかったです。

これらのメリットと、軽くテストした感じの使い勝手も良さそうだったため、rroongaを使って開発することに決めました。

Milkodeでの使い方

Milkodeでは、主に2つのテーブルを使っています。

  1. ソースコードのテーブル(documents)
  2. パッケージのテーブル(packages)※5

特に重要なのはdocumentsテーブルです。検索対象となるすべてのファイルが1つのテーブルに格納されています。

  • 1ファイル == 1レコード として登録している
  • パッケージごとにテーブルを分けたりはしない
    • 全文検索のクエリとしてpackageカラムを指定した方が高速なため
  • 拡張子はソースコードにとって大切な情報なので専用カラムを作成
    • .cと.cppを区別することができる

documentsテーブルのスキーマ

それではdocumentsテーブルのスキーマ情報をみてましょう。

カラム名説明実例全文検索
pathstringファイルの絶対パス/path/to/proj/src/aaa.txt
packagestringパッケージ名proj
restpathstringパッケージ名以降のパスsrc/aaa.txt
contenttextファイルの内容aaa\nbbb\nccc
timestamptimeファイルのタイムスタンプ2012-11-21 23:26:00 +0900-
suffixtext拡張子txt

定義するためのRubyコード

実際にスキーマを定義しているのは/milkode/lib/milkode/database/document_table.rbの11行目です。

module Milkode
  class DocumentTable
    def self.define_schema
      Groonga::Schema.define do |schema|
        schema.create_table("documents", :type => :hash) do |table|
          table.string("path")
          table.string("package")
          table.string("restpath")
          table.text("content")
          table.time("timestamp")
          table.text("suffix")
        end

        schema.create_table("terms",
                            :type => :patricia_trie,
                            :key_normalize => true,
                            :default_tokenizer => "TokenBigramSplitSymbolAlphaDigit") do |table|
          table.index("documents.path")
          table.index("documents.package")
          table.index("documents.restpath")
          table.index("documents.content")
          table.index("documents.suffix")
        end
      end
    end

さらに詳しい使い方を知りたい人は、milkode/database/document_table.rb辺りから読んでみてください。

とりあえず完成 「行指向のソースコード検索エンジンMilkode」

こうしてrroongaのおかげでMilkodeができました。ブラウザで検索すると次のような画面になります。

Milkodeのホーム画面
Milkodeのホーム画面

他の検索ツールと比較した時の特徴として、次のような点が挙げられます。

  • インデックス型のため、数万オーダーのファイルが登録されていても1秒以内に検索できる[6]
  • しかし、grep型のように複数のキーワードを含むを見つけることができる
  • ブラウザからもコマンドラインからも検索することができる
  • 個人用のデスクトップアプリケーションとしても使えるし、デプロイして共有のWebアプリにすることもできる

Milkodeのインストールはgemから行います。

$ gem install milkode

それにより、次のコマンドが使えるようになります。

コマンド概要
milkgitライクなコマンドを使って検索したいディレクトリを登録
milk webWebアプリを立ち上げてブラウザから検索
gmilkgrepのようにコマンドラインから検索
mcd登録したディレクトリ間を素早く移動

さらに詳しい使い方はhttp://milkode.ongaeshi.meをご覧ください。

派生物として、自分のGitHubレポジトリのソースコードをオフラインで高速に検索できるMyGitHubというのもあります。

まとめ

rroongaを使って良かった点はやはり検索の高速性です。勉強会などで人に見せる機会があるとほとんどの人が検索結果が返ってくるまでのスピードに驚いてくれます。grep型のものと比べると驚く程速いです。また、Rubyらしい書き方で全文検索エンジンにアクセスできるのもとてもよいところです。

困った点として、Windowsなどの一部環境で安定して動いていなかった(Milkodeの症状で言うと20,000レコード程度登録するとSEGV)というのがあったのですが、groongaチームの皆さんがバグ報告から原因を見つけてくれ昨年11月にリリースされたrroonga2.0.9で無事直りました。手元のWindowsではすでに安定して動作しています[7]⁠。

全文検索というとWebアプリのものというイメージがありますが、rroongaはgemさえインストールすればRubyから簡単に使うことができるため、ちょっとしたツールにも使うことができます。ファイルやデータを一つずつ検索すると遅いからもっと早く検索したいなあ、といった要求があったときは是非rroonga(とその内部のgroonga)を使ってみてください!

次回予告

次回はgroongaをライブラリとして使っているプロダクトの紹介です。今回でてきたrroongaについて詳しく紹介します。rroongaが大事にしていることはいったいなんでしょう。お楽しみに!

おすすめ記事

記事・ニュース一覧