groonga - An open-source fulltext search engine and column store.

4.6. タグ検索・参照関係の逆引き

本チュートリアルで、groongaはカラム値として他のテーブルへの参照の配列を持つことができることを紹介いたしました。実は、テーブルへの参照の配列データを用いることによって、いわゆるタグ検索を行うことが可能となります。

タグ検索はgroongaの転置インデックスというデータ構造を用いて高速に行われます。

4.6.1. タグ検索

動画共有サイトの検索エンジンを作ることを想定します。1つの動画には、その動画の特徴を表す、複数の語句が付与されています。「ある語句が付与されている動画の一覧を取得する」検索を行いたいとします。

実際に、動画情報のテーブルを作成し、検索をしてみましょう。

動画の情報を保存する、Videoテーブルを作成します。Videoテーブルでは、動画のタイトルをtitleカラムに、動画のタグ情報をtagsカラムにTagテーブル型で複数格納しています。 タグの情報を保存する、Tagテーブルを作成します。Tagテーブルでは、タグ文字列を主キーに格納し、Videoテーブルのtagsカラムに対するインデックスをindex_tagsカラムに格納しています。

Execution example:

> table_create --name Video --flags TABLE_HASH_KEY --key_type UInt32
[[0,1335519627.03224,0.000266790390014648],true]
> table_create --name Tag --flags TABLE_HASH_KEY --key_type ShortText
[[0,1335519627.23315,0.000222206115722656],true]
> column_create --table Video --name title --flags COLUMN_SCALAR --type ShortText
[[0,1335519627.43389,0.000550031661987305],true]
> column_create --table Video --name tags --flags COLUMN_VECTOR --type Tag
[[0,1335519627.63497,0.000490188598632812],true]
> column_create --table Tag --name index_tags --flags COLUMN_INDEX --type Video --source tags
[[0,1335519627.83602,0.0032198429107666],true]
> load --table Video
> [
> {"_key":1,"title":"Soccer 2010","tags":["Sports","Soccer"]},
> {"_key":2,"title":"Zenigata Kinjirou","tags":["Variety","Money"]},
> {"_key":3,"title":"groonga Demo","tags":["IT","Server","groonga"]},
> {"_key":4,"title":"Moero!! Ultra Baseball","tags":["Sports","Baseball"]},
> {"_key":5,"title":"Hex Gone!","tags":["Variety","Quiz"]},
> {"_key":6,"title":"Pikonyan 1","tags":["Animation","Pikonyan"]},
> {"_key":7,"title":"Draw 8 Month","tags":["Animation","Raccoon"]},
> {"_key":8,"title":"K.O.","tags":["Animation","Music"]}
> ]
[[0,1335519628.03974,2.00352549552917],8]

インデックスカラムを作成すると、全文検索が高速に行えるようになります。インデックスカラムは、対象のカラムに保存されたデータに更新があったとき、自動的に更新されます。

「ある語句が付与されている動画の一覧を取得する」検索を行いましょう。

Execution example:

> select --table Video --query tags:@Variety --output_columns _key,title
[
  [
    0,
    1335519630.24437,
    0.000667810440063477
  ],
  [
    [
      [
        2
      ],
      [
        [
          "_key",
          "UInt32"
        ],
        [
          "title",
          "ShortText"
        ]
      ],
      [
        2,
        "Zenigata Kinjirou"
      ],
      [
        5,
        "Hex Gone!"
      ]
    ]
  ]
]
> select --table Video --query tags:@Sports --output_columns _key,title
[
  [
    0,
    1335519630.44675,
    0.000470399856567383
  ],
  [
    [
      [
        2
      ],
      [
        [
          "_key",
          "UInt32"
        ],
        [
          "title",
          "ShortText"
        ]
      ],
      [
        1,
        "Soccer 2010"
      ],
      [
        4,
        "Moero!! Ultra Baseball"
      ]
    ]
  ]
]
> select --table Video --query tags:@Animation --output_columns _key,title
[
  [
    0,
    1335519630.64889,
    0.000455856323242188
  ],
  [
    [
      [
        3
      ],
      [
        [
          "_key",
          "UInt32"
        ],
        [
          "title",
          "ShortText"
        ]
      ],
      [
        6,
        "Pikonyan 1"
      ],
      [
        7,
        "Draw 8 Month"
      ],
      [
        8,
        "K.O."
      ]
    ]
  ]
]

このように、「Variety」、「Sports」、「Animation」のようなタグで検索を行うことができました。

4.6.2. 参照関係の逆引き

groongaはテーブル間の参照関係の逆引きを高速に行うためのインデックスを付与することができます。タグ検索は、その1例にすぎません。

例えば、ソーシャルネットワーキングサイトにおける友人関係を逆引き検索することができます。

以下の例では、ユーザー情報を格納するUserテーブルを作成し、ユーザー名を格納するusernameカラム、ユーザーの友人一覧を配列で格納するfriendsカラムとそのインデックスのindex_friendsカラムを追加しています。

Execution example:

> table_create --name User --flags TABLE_HASH_KEY --key_type ShortText
[[0,1335519630.85121,0.00020289421081543],true]
> column_create --table User --name username --flags COLUMN_SCALAR --type ShortText
[[0,1335519631.05176,0.00130391120910645],true]
> column_create --table User --name friends --flags COLUMN_VECTOR --type User
[[0,1335519631.25402,0.000970840454101562],true]
> column_create --table User --name index_friends --flags COLUMN_INDEX --type User --source friends
[[0,1335519631.45581,0.00640487670898438],true]
> load --table User
> [
> {"_key":"ken","username":"健作","friends":["taro","jiro","tomo","moritapo"]}
> {"_key":"moritapo","username":"森田","friends":["ken","tomo"]}
> {"_key":"taro","username":"ぐるんが太郎","friends":["jiro","tomo"]}
> {"_key":"jiro","username":"ぐるんが次郎","friends":["taro","tomo"]}
> {"_key":"tomo","username":"トモちゃん","friends":["ken","hana"]}
> {"_key":"hana","username":"花子","friends":["ken","taro","jiro","moritapo","tomo"]}
> ]
[[0,1335519631.6631,1.60283184051514],6]

指定したユーザーを友人リストに入れているユーザーの一覧を表示してみましょう。

Execution example:

> select --table User --query friends:@tomo --output_columns _key,username
[
  [
    0,
    1335519633.46704,
    0.000623941421508789
  ],
  [
    [
      [
        5
      ],
      [
        [
          "_key",
          "ShortText"
        ],
        [
          "username",
          "ShortText"
        ]
      ],
      [
        "ken",
        "健作"
      ],
      [
        "taro",
        "ぐるんが太郎"
      ],
      [
        "jiro",
        "ぐるんが次郎"
      ],
      [
        "moritapo",
        "森田"
      ],
      [
        "hana",
        "花子"
      ]
    ]
  ]
]
> select --table User --query friends:@jiro --output_columns _key,username
[
  [
    0,
    1335519633.67028,
    0.000344514846801758
  ],
  [
    [
      [
        3
      ],
      [
        [
          "_key",
          "ShortText"
        ],
        [
          "username",
          "ShortText"
        ]
      ],
      [
        "ken",
        "健作"
      ],
      [
        "taro",
        "ぐるんが太郎"
      ],
      [
        "hana",
        "花子"
      ]
    ]
  ]
]

さらに、ドリルダウンを使って、友人リストに入っている数の一覧を表示してみましょう。

Execution example:

> select --table User --limit 0 --drilldown friends
[
  [
    0,
    1335519633.87264,
    0.000327825546264648
  ],
  [
    [
      [
        6
      ],
      [
        [
          "_id",
          "UInt32"
        ],
        [
          "_key",
          "ShortText"
        ],
        [
          "friends",
          "User"
        ],
        [
          "index_friends",
          "User"
        ],
        [
          "username",
          "ShortText"
        ]
      ]
    ],
    [
      [
        6
      ],
      [
        [
          "_key",
          "ShortText"
        ],
        [
          "_nsubrecs",
          "Int32"
        ]
      ],
      [
        "taro",
        3
      ],
      [
        "jiro",
        3
      ],
      [
        "tomo",
        5
      ],
      [
        "moritapo",
        2
      ],
      [
        "ken",
        3
      ],
      [
        "hana",
        1
      ]
    ]
  ]
]

このように、テーブルの参照関係を逆にたどる検索ができました。

4.6.3. インデックス付きジオサーチ

位置情報のカラムに対して、インデックスを付与することが出来ます。大量の位置情報レコードを検索する場合に、検索速度が速くなります。

Execution example:

> table_create --name GeoIndex --flags TABLE_PAT_KEY --key_type WGS84GeoPoint
[[0,1335519634.07459,0.00024867057800293],true]
> column_create --table GeoIndex --name index_point --type Site --flags COLUMN_INDEX --source location
[[0,1335519634.2754,0.00743818283081055],true]
> load --table Site
> [
>  {"_key":"http://example.org/","location":"128452975x503157902"},
>  {"_key":"http://example.net/","location":"128487316x502920929"}
> ]
[[0,1335519634.48372,0.801371812820435],2]
> select --table Site --filter 'geo_in_circle(location, "128515259x503187188", 5000)' --output_columns _key,location
[
  [
    0,
    1335519635.48562,
    0.000363826751708984
  ],
  [
    [
      [
        1
      ],
      [
        [
          "_key",
          "ShortText"
        ],
        [
          "location",
          "WGS84GeoPoint"
        ]
      ],
      [
        "http://example.org/",
        "128452975x503157902"
      ]
    ]
  ]
]

同様に、位置情報レコードを用いてソートする場合に、ソート速度が速くなります。

Execution example:

> select --table Site --filter 'geo_in_circle(location, "128515259x503187188", 50000)' --output_columns _key,location,_score --sortby '-geo_distance(location, "128515259x503187188")' --scorer '_score = geo_distance(location, "128515259x503187188")'
[
  [
    0,
    1335519635.68736,
    0.000502824783325195
  ],
  [
    [
      [
        2
      ],
      [
        [
          "_key",
          "ShortText"
        ],
        [
          "location",
          "WGS84GeoPoint"
        ],
        [
          "_score",
          "Int32"
        ]
      ],
      [
        "http://example.org/",
        "128452975x503157902",
        2054
      ],
      [
        "http://example.net/",
        "128487316x502920929",
        6720
      ]
    ]
  ]
]