データベースからランダムにレコードを1つだけ取り出す方法(MediaWiki編)
「ランダムで何かの要素を表示したい」という要件を見かける。難易度は低い。すぐ実装できる。が、それなりにスケールする実装は簡単ではない。
カウンター、ランキングなど、「コンピュータだとすぐできそうじゃん」と思えるようなことを、スケールする実装に仕上げるのは難しい。
— グニャラくん (@gunyarakun) April 23, 2013
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の値を取ると書いてある。また、インデックスが付与されている。
このフィールドは、新しいレコードが追加されるたびに乱数が代入される。乱数はおそらくレコード間で重複しないようにチェックしているはず。
- 0から1の乱数を発生させる
- pagerandomフィールドが乱数以上の数値を持つようなレコードをORDER BY pagerandom LIMIT 1で取得する
- 2.でレコードが取得できればそのレコードを返す。そうでなければ、0以上の数値を持つようなレコードをORDER BY page_random LIMIT 1で取得する
とても単純な処理ですね。最悪2クエリ発生してしまいますが、インデックスはちゃんと効きそうです。
ニコニコ大百科作る際もこの設計を参考にした記憶があります。1件じゃなくてn件ランダムに取得したい場合にはあまり向かないでしょう。
このエントリは、えせはらさんの「サービスのデータベース周りなどをチェックしたらレスポンス速度が格段に上がった話」のエントリや、mrknさんの以下のツイートに寄せたものです。
ランダム訪問で「グニャラくん」と「とはえ」の2つを行き来する2周期軌道に入ったまま抜けられない
— 二周目 (@mrkn) July 2, 2013