LoginSignup
2
3

More than 5 years have passed since last update.

rroongaとかベクターカラムとか

Last updated at Posted at 2013-02-21

実装が面倒くさいのはどう考えてもデータ構造が悪い

いきなり追記

Groonga公式さんから有難くもコメントを頂き下に書かれた問題は全て解決してしまいました.有難うございます!

どうでもいい話

お手軽に全文検索のできるDBが手に入るGroonga, そのRubyバインディングであるrroongaはネーミングを別にすれば大変素敵です.パフォーマンスや安定性より書くのがラクでとりあえず動けばいい身としては大変ありがたい.

全文検索がさくさくできるのも素敵ですがカラムにテーブル参照をベクターカラムに放り込めるのはとてもいいですね.複雑怪奇なID関係をRDBに押しこむのにウンザリする日々ともこれでおさらばです!

ちゃちゃっとRubyで書いてちゃちゃっと検索!rroonga最高!

どうでもよくない話

どっこい「テーブル参照」×「ベクターカラム」ってとこで微妙につまづいた.ちゃちゃっと書けるから魅力的なのであって,ベストプラクティス調べてるヒマはないのでとりあえず動けばいいや的にテケトーに書いた.誰かもっとクールでイージーに書くやり方を教えてださい.よろしくお願いしましたよ.

状況

まーよくありがちなんですが「実験」のメタデータと「サンプル」のメタデータがあったとしましょう,1つの実験には数個〜数千個のサンプルが使われます.子宝に恵まれているわけです.こんなもんRDBに入れた日には黄金体験です.

Groonga::Database.create(db_path)
Groonga::Schema.define do |schema|
  schema.create_table("Samples", :type => :hash) do |table|
    table.short_text("sample_title")
  end

  schema.create_table("Experiments", :type => :hash) do |table|
    table.reference("sample", "Samples", :type => :vector)
    table.short_text("experiment_title")
  end
end

こんな感じでデータベース作るわけです.この辺はチュートリアルをいじれば書けるので楽勝です.まあほんとはtitle以外に山ほどぶちこむメタデータがあるんですが割愛.

そんでデータを登録します.SamplesもExperimentsも主キーはIDです.登録したら,"任意のサンプルIDを持つ実験のレコード取ってくる"みたいなことしたいわけです.やってみましょう.

samples = Groonga["Samples"]
samples.add("SID001", :sample_title => "human")
samples.add("SID002", :sample_title => "mouse")
samples.add("SID003", :sample_title => "rice")
samples.add("SID004", :sample_title => "wheat")

experiments = Groonga["Experiments"]
experiments.add("EID001",
                :sample => ["SID001", "SID002"],
                :experiment_title => "mammalian")
experiments.add("EID002",
                :sample => ["SID003", "SID004"],
                :experiment_title => "crop")

ap experiments.select{|record| record.sample =~ "SID001" }.map{|record| record["_key"] }
=> []

うん、まあrecord.sampleで返ってくるの配列だろうし=~ってのは違うよね、==でもないし、#include?とかかな、と色々やるもダメ.そもそもselectでブロックに渡すものがGroonga::ExpressionBuildable::ColumnValueExpressionBuilderってなんだこれ?

ここでしばらくハマったけどGroongaのドキュメントを読みに行くとタグ検索でそれっぽいことしてたのでそれに倣ってカラムの定義をやりなおす.っていうかこのチュートリアルのURLなんか変だな…

Groonga::Database.create(db_path)
Groonga::Schema.define do |schema|
  schema.create_table("Samples", :type => :hash) do |table|
    table.short_text("sample_title")
  end

  schema.create_table("Experiments", :type => :hash) do |table|
    table.reference("sample", "Samples", :type => :vector)
++  table.index("Experiments.sample")
    table.short_text("experiment_title")
  end
end

テーブル参照のベクターカラムのindexを、同じテーブルに張ると検索できるようになる.

ap experiments.select{|record| record.sample =~ "SID001" }.map{|record| record["_key"] }
=>
[
    [0] "EID001"
]

いやっほう!

でもこれテーブルのtypeがhashだから完全一致だけかも.

参照レコードの主キーに対して全文検索的にやる時はtype: paticia_trieとかの別テーブルにindex張らんとダメかもしらんし,張ってもダメかも知らん.やる必要がないので調べてない.

とりあえず主キー,この場合はサンプルのIDの完全マッチで実験を探せるようになったのでヨシ.本当はサンプルのタイトルに全文検索かけて実験のレコード引っ張りだすとかできるようになるといいんだけど,現状こんな感じで書いている.

Groonga::Schema.degine do |schema|
  schema.create_table("Text_index",
                      type: :patricia_trie,
                      key_normalize: true,
                      default_tokenizer: "TokenBigram" )
  schema.change_table("Text_index") do |table|
    table.index("Samples.sample_title")
  end
end

別テーブルにSamplesテーブルのsample_titleカラム用の全文検索インデックス張っといて,

query = "human"
sample_hit = samples.select{|record| record.sample_title =~ query }
sample_ids = sample_hit.map{|record| record["_key"] }
experiment_hit = sample_ids.map{|id| experiment.select{|record| record.sample =~ id }
experiment_ids = experiment_hit.map{|record| record["_key"]} }
ap experiment_ids.flatten

汚っ!

なんかもっとうまくやる方法ないかねえ.Experimentsテーブルにうじゃうじゃカラム増やすのも鬱陶しいし,検索用にテーブル別に作るとかなんとかやればいいの?でもメタデータのカラムひとつひとつにそれやるのも面倒だしな.まあいいやこれで.

状況2

んで、まあ泥臭いながらに目的は達せられたのでゴニョゴニョしてたんですが,なんかおかしい.

なにがおかしいってなんかレコードの総数がおかしい.500件追加したつもりが#sizeで返ってくるのが5桁とかになってる.なんだこれは.

ap sample.size
=> 4
ap sample.records
=> 
[
    [0] "SID001",
    [1] "SID002",
    [2] "SID003",
    [3] "SID004"
]
ap experiments.size
=> 6
ap experiments.records.map{|r| r["_key"] }
[
    [0] "EID001",
    [1] "SID001",
    [2] "SID002",
    [3] "EID002",
    [4] "SID003",
    [5] "SID004"
]

これまでの例でやってみると,Experimentsテーブルに6件登録されたことになっとる.#recordsでExperimentsのレコードだけ返ってくるのを期待しとったのだが,index張ると中身までレコードとして吐かれる.ベクターカラムにテーブル参照やるだけだと平気っぽい.

元のコード見に行くの面倒くさいのでこれが正常な動作なのか知らんし、他にいいメソッドが定義されているのかもしれんけど、面倒くさいので主キーの値でフィルタしてる.そしてコードが汚くなる.動いているので構わないが少し悲しい.

結論

rroongaに限らずよくわからないまま使ってると色々詰まる.面倒くさいとかゴチャゴチャ言わずにソースコード読むのがよさそうだけど,やっぱり面倒くさいものは面倒くさいのでQiitaに投げて誰かが教えてくれればよいなあ.

2
3
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3