データベースからランダムにレコードを1つだけ取り出す方法(MediaWiki編)

「ランダムで何かの要素を表示したい」という要件を見かける。難易度は低い。すぐ実装できる。が、それなりにスケールする実装は簡単ではない。

MediaWikiというソフトウェアがある。Wikipediaで使われているWikiソフトウェアだ。Wikipediaにも「おまかせ表示」という機能がある。記事をランダムに1つ表示してくれる機能だ。さすがにWikipediaで使われている機能なので、スケールする実装になっているだろう。

MediaWikiのレポジトリのmaintenance/tables.sqlにテーブル定義がある。ここで着目するのは、pageテーブルのpage_randomというカラムである。

CREATE TABLE /*_*/page (
  /* 中略 */
  -- Random value between 0 and 1, used for Special:Randompage
  page_random real unsigned NOT NULL,
  /* 中略 */
) /*$wgDBTableOptions*/;

CREATE INDEX /*i*/page_random ON /*_*/page (page_random);

当該フィールドに格納される値は、0から1の値を取ると書いてある。また、インデックスが付与されている。

このフィールドは、新しいレコードが追加されるたびに乱数が代入される。乱数はおそらくレコード間で重複しないようにチェックしているはず。

ランダムにレコードを選び取る処理は、以下のように行われる。

  1. 0から1の乱数を発生させる
  2. pagerandomフィールドが乱数以上の数値を持つようなレコードをORDER BY pagerandom LIMIT 1で取得する
  3. 2.でレコードが取得できればそのレコードを返す。そうでなければ、0以上の数値を持つようなレコードをORDER BY page_random LIMIT 1で取得する

とても単純な処理ですね。最悪2クエリ発生してしまいますが、インデックスはちゃんと効きそうです。

ニコニコ大百科作る際もこの設計を参考にした記憶があります。1件じゃなくてn件ランダムに取得したい場合にはあまり向かないでしょう。

このエントリは、えせはらさんの「サービスのデータベース周りなどをチェックしたらレスポンス速度が格段に上がった話」のエントリや、mrknさんの以下のツイートに寄せたものです。