BloGroonga

2012-11-29

今日は groonga勉強会「全文検索エンジンgroongaを囲む夕べ 3」 の当日ですね。

groonga 2.0.9リリース

groonga 2.0.9 をリリースしました!

今回のリリースの主なトピックは4つあります。

  • snippet_html()関数の実験的なサポート
  • インデックスにより関連付けられたテーブル間のネストしたインデックス検索のサポート
  • インデックスを利用した高速な指定範囲検索のサポート
  • geo_distance()関数で矩形近似により境界をまたぐ二点間の距離計算をサポート

それぞれの環境毎のインストール方法: インストール

snippet_html()関数の実験的なサポート

今回のリリースでは、実験的ではありますが、キーワードとその周辺のテキストを抽出するためのsnippet_html()関数をサポートしました。 あくまで実験的なので今後予告なく変更する可能性があります。

snippet_html()関数は以下のような構文で使用します。

  snippet_html(column名)

どんな風に使えるかを具体例で説明します。以下は説明のためのスキーマ定義とサンプルデータです。

  table_create Documents TABLE_NO_KEY
  column_create Documents content COLUMN_SCALAR Text
  table_create Terms TABLE_PAT_KEY|KEY_NORMALIZE ShortText --default_tokenizer TokenBigram
  column_create Terms documents_content_index COLUMN_INDEX|WITH_POSITION Documents content

  load --table Documents
  [
  ["content"],
  ["Groonga is a fast and accurate full text search engine based on inverted index."],
  ["Groonga is also a column-oriented database management system (DBMS)."],
  ["Mroonga was called groonga storage engine."]
  ]

Documentsテーブルに登録したデータからキーワードgroongaを含むテキストを抽出するには以下のクエリを実行します。

  select Documents --output_columns "snippet_html(content)" --command_version 2 --match_columns content --query "groonga" 

上記クエリでは検索したいキーワードgroongaを --query に指定し、検索対象のカラムには --match_columns content としてDocumentテーブルのcontentカラムを指定します。

このとき、クエリにコマンドバージョン(--command_version 2)を指定する必要があります。これは、 --ouput_columns で関数を呼びだすことができるようになったのは2.0.9からだからです。

結果は以下のようになり、指定したキーワードが <span> タグで囲まれ、キーワード周辺のテキストとして抽出できていることがわかります。

検索結果のキーワードをハイライト表示したいときに便利な機能です。

  [
    [0,1353893385.5454,0.000486850738525391],
    [
      [
        [3],
        [["snippet_html","null"]],
        [["Groonga is a fast and accurate full text search engine based on inverted index."]],
        [["Groonga is also a column-oriented database management system (DBMS)."]],
        [["Mroonga was called groonga storage engine."]]
      ]
    ]
  ]

関数の詳細については snippet_html() を参照してください。

インデックスにより関連付けられたテーブル間のネストしたインデックス検索のサポート

今回のリリースでは複数のテーブルの間でインデックスが張られているなど、テーブル間の関連が存在する場合にインデックス名をネストして指定することで複数テーブルからまとめて検索することができるようになりました。

これだけだとわかりにくいので、例を示します。

ブログ記事を格納してるテーブルと記事に対するコメントを格納するテーブルがあるとします。

記事を格納しているテーブルには記事そのもののカラムとコメントテーブルを参照するカラムがあり、コメントテーブルにはコメントを格納するカラムと、記事テーブルへのインデックスカラムがあるとします。

これまで特定のキーワードをコメントに含む記事を探すには、コメントテーブルの全文検索と、その全文検索結果から該当記事を検索する必要がありました。

これが、参照先のインデックスカラムを指定することでまとめて検索できるようになりました。

どう使うかのサンプルを以下で示します。

サンプルのスキーマ定義は以下の通りです。

  table_create Comments TABLE_HASH_KEY UInt32
  column_create Comments content COLUMN_SCALAR ShortText

  table_create Articles TABLE_NO_KEY
  column_create Articles content COLUMN_SCALAR Text
  column_create Articles comment COLUMN_SCALAR Comments

  table_create Lexicon TABLE_PAT_KEY|KEY_NORMALIZE ShortText --default_tokenizer TokenBigram
  column_create Lexicon articles_content COLUMN_INDEX|WITH_POSITION Articles content
  column_create Lexicon comments_content COLUMN_INDEX|WITH_POSITION Comments content

  column_create Comments article COLUMN_INDEX Articles comment

記事とコメントのサンプルデータは以下のようにして登録します。

  load --table Comments
  [
  {"_key": 1, "content": "I'm using groonga too!"},
  {"_key": 2, "content": "I'm using groonga and mroonga!"},
  {"_key": 3, "content": "I'm using mroonga too!"}
  ]

  load --table Articles
  [
  {"content": "Groonga is fast!", "comment": 1},
  {"content": "Groonga is useful!"},
  {"content": "Mroonga is fast!", "comment": 3}
  ]

コメントテーブルから指定したキーワードを含むレコードを検索し、そのコメントのレコードを参照しているカラムの記事データを取得するクエリは以下のように書くことができます。

  select Articles --match_columns comment.content --query groonga --output_columns "_id, _score, *" 

--match_columns にはArticlesテーブルのcommentカラムとCommentsテーブルのcontentカラムとをピリオドで連結して指定します。

するとCommentsテーブルのcontentカラムをまずはじめに全文検索し、該当するレコードを参照しているArticlesのcommentカラムのレコードを取得できる仕組みになっています。 (そのため、上記スキーマ定義でCommentsテーブルにインデックスカラムarticleがない場合、CommentsテーブルからArticlesテーブルを辿れないので正しい結果が得られません。)

  [
    [0,1353903149.81632,0.000459432601928711],
    [
      [
       [1],
       [["_id","UInt32"],["_score","Int32"],["comment","Comments"],["content","Text"]],
       [1,1,1,"Groonga is fast!"]
      ]
    ]
  ]

従来から可能であった特定のキーワードを含む記事の全文検索だけでなく、特定のキーワード(上記の場合はgroonga)を含むコメントを参照している記事を取得することができるようになりました。

インデックスを利用した高速な指定範囲検索のサポート

今回のリリースでは、範囲を指定した検索をインデックスを使って高速に行えるようになりました。

どれくらい高速になったかという例を簡単なサンプルで示します。

サンプルのスキーマ定義は以下の通りです。

  table_create Shops TABLE_HASH_KEY ShortText
  column_create Shops ranking COLUMN_SCALAR UInt32

  table_create Rankings TABLE_PAT_KEY UInt32
  column_create Rankings shops_ranking COLUMN_INDEX Shops ranking

以下のようにしてお店とそのランキングの値を登録します。高速化した結果をよりわかりやすく示すために、1000万件ほどのデータを登録します。

  load --table Shops
  [
  {"_key": "Shop1", "ranking": 1},
  {"_key": "Shop2", "ranking": 2},
  {"_key": "Shop3", "ranking": 3},
  {"_key": "Shop4", "ranking": 4},
  {"_key": "Shop5", "ranking": 5},
  {"_key": "Shop6", "ranking": 6},
  {"_key": "Shop7", "ranking": 7},
  {"_key": "Shop8", "ranking": 8},
  {"_key": "Shop9", "ranking": 9},
  {"_key": "Shop10", "ranking": 10},
  {"_key": "Shop11", "ranking": 11},
  ...
  ]

お店とランキングの値が登録できたので、実際に検索してみます。ここでは、ランキング上位10店を検索してみます。

範囲の指定にはrankingカラムに対して 'ranking <= 10' として表現します。

実際の検索結果は以下のようになります。(groonga 2.0.8の場合)

  select Shops --filter 'ranking <= 10'
  [
    [0,1355465886.15137,1.39784264564514],
    [
      [
        [10],
        [
          ["_id","UInt32"],["_key","ShortText"],["ranking","UInt32"]
        ],
        [1,"Shop1",1],
        [2,"Shop2",2],
        [3,"Shop3",3],
        [4,"Shop4",4],
        [5,"Shop5",5],
        [6,"Shop6",6],
        [7,"Shop7",7],
        [8,"Shop8",8],
        [9,"Shop9",9],
        [10,"Shop10",10]
      ]
    ]
  ]

同じようにしてgroonga 2.0.9でも検索してみます。検索結果は以下のようになります。

  select Shops --filter 'ranking <= 10'
  [
    [0,1355465837.0779,0.00165677070617676],
    [
      [
        [10],
        [
          ["_id","UInt32"],["_key","ShortText"],["ranking","UInt32"]
        ],
        [1,"Shop1",1],
        [2,"Shop2",2],
        [3,"Shop3",3],
        [4,"Shop4",4],
        [5,"Shop5",5],
        [6,"Shop6",6],
        [7,"Shop7",7],
        [8,"Shop8",8],
        [9,"Shop9",9],
        [10,"Shop10",10]
      ]
    ]
  ]

検索結果は当然同じですが、実行時間が違います。実行時間は以下の部分が該当します。

    [0,1355465886.15137,1.39784264564514],

groonga 2.0.8の場合は1.39784264564514秒でした。

    [0,1355465837.0779,0.00165677070617676],

一方、groonga 2.0.9の場合は0.00165677070617676秒でした。

groongaが出力する結果のフォーマット詳細については 出力形式 を参照してください。

groongaのバージョン groonga 2.0.8 groonga 2.0.9
実行時間(秒) 1.39784264564514 0.00165677070617676

2.0.9にするだけでこのサンプルのケースでは実行時間が数ミリ秒にまで短縮できていることがわかります。

測定は以下の環境で実施しました。

CPU Intel® Core i7-2640M CPU @ 2.80GHz
メモリ 8GB

geo_distance()関数で矩形近似により境界をまたぐ二点間の距離計算をサポート

今回のリリースでは、geo_distance()関数で子午線や日付変更線、赤道といった境界をまたいだ場合での任意の二点間の距離計算をサポートしました。

距離の近似方法にはいくつかやりかたがあります。groongaではgeo_distance関数において以下の近似方法をサポートしています。 それぞれ、速度や正確さにトレードオフが存在します。

  • 矩形近似 二点間の地形を平面とみなして計算します。

    最も高速に距離を計算できますが、二点間の距離が離れると正確に計算できません。

  • 球面近似 二点間の地形を球面とみなして計算します。 矩形近似より遅くなりますが、より正確に距離を計算できます。
  • 楕円近似 二点間の地形を楕円体とみなして計算します。 球面近似より遅くなりますが、より正確に距離を計算できます。

今回の機能追加は近似方法として矩形近似を選択した場合に有効です。

実際に、子午線をまたいで二点間の距離を計算するサンプルを紹介します。

以下のサンプルではパリ(フランス)からマドリード(スペイン)の距離を矩形近似で計算しています。

"175904000x8464000"と表記されているのがパリ(フランス)のミリ秒表記で、"145508000x-13291000"がマドリード(スペイン)のミリ秒表記です。

  select Geo --output_columns distance --scorer 'distance = geo_distance("175904000x8464000", "145508000x-13291000", "rectangle")'
  [
    [
      0,
      1337566253.89858,
      0.000355720520019531
    ],
    [
      [
        [
          1
        ],
        [
          [
            "distance",
            "Int32" 
          ]
        ],
        [
          1051293
        ]
      ]
    ]
  ]

ある点の経緯度をどのようにミリ秒表記で表わすかについては、 組込型 に詳細を記載してあります。

関数の詳しい使いかたについては geo_distance のドキュメントを参照してください。

さいごに

2.0.8からの詳細な変更点は 2.0.9リリース 2012/11/29 を確認してください。

それでは、groongaでガンガン検索してください!