2007/04/28(土)ページ送りのためのデータベース検索
adiaryにGoogleのようなページ送りを実装できないかというお話がメーリングリストでありました。
実は速度面で不利なので実装してなかったんですよね。どれくらい不利なのか調べてなかったので検討してみました。
その前にクイズ
adiaryではご覧の通り「次のページ」と「前のページ」は(存在すれば)常に表示されるようになっていますが、実は全件のデータを取得することなく表示しています。どうやってるか分かりますか?
分かってしまえばかなり単純な仕組みですが、ご覧の通り十分実用的な動作をしています。
実行速度の検証
データ数が少ないときは気にするほどの差はないので、とりあえず810件、1MBほどのデータをインポートしてテストしてみました。
条件 | pseudo(cgi) | pseudo(cache) | Pg(8.1.8) | MySQL(5.0.27) |
---|---|---|---|---|
今の実装(トップ) | 131.0 ms | 110.9 ms | 92.9 ms | 50.2 ms |
件数取得(トップ) | 130.7 ms | 111.7 ms | 98.2 ms | 51.7 ms |
今の実装(検索) | 135.3 ms | 121.6 ms | 171.5 ms | 86.1 ms |
件数取得(検索) | 1642.6 ms | 397.5 ms | 269.4 ms | 88.3 ms |
検索は「あ」一文字の全文検索という、多量にデータが引っかかるような検索を行いました。マシンは相変わらず C3 500MHz(笑)
擬似データベース(pseudo)
件数取得の全文検索以外は大したことないですね。擬似データベースはインデックスデータを常にメモリに展開していますので、全文検索しない限りは個別のデータファイルを参照にいきません。たかだか数件しか取得しないのならば、数件発見した時点で個別データのロードを終了するので、通常のトップ表示と比べせいぜい10ms程度しか違わないわけです。
- 件数取得の検索は、810個の個別のデータファイルをすべてロードすることになり、多量の時間がかかっている。
- cache と付いているのは、cgiがキャッシュされている場合です*1。この場合、一度でも読み出したファイルはメモリにキャッシュされますので810件のファイルを開く必要がないので速く済んでいます。
- それ以外は、結局メモリに展開されている(1ファイルになっている)インデックスデータをループで回すだけなので、時間的にほとんど変わらない。
PostgreSQL (Pg)
総じて擬似データベースより少し良いパフォーマンスという感じですね。件数取得に時間がかかっていますが、PostgreSQLでは、件数取得を指示された場合、そのためだけに1回クエリを投げる実装になっています(Satsuki-systemの実装の話です)。
SELECT count(pkey) FROM $table WHERE ~
件数取得をすると時間が増えてしまうのは、単純に検索範囲が増えた結果なのか……難しいところです。
MySQL 5.0.27
MySQL (MyISAM) がこんな速いとは思いませんでした。単純 Query で約倍も速い。MySQLはありがたいことに、検索時にヒット件数を取得する書式があり、ほとんどオーバーヘッドなく該当件数を取得出来ます。
SELECT SQL_CALC_FOUND_ROWS $cols FROM $table WHERE ~
考察
微妙なところですねぇ~。気にしなくていいと言えば気にしなくていいような、気にすると言えば気にするような。検索のことを考えなければ、どのデータベースでも件数取得しても問題ないのですか、全文検索、しかも多くのユーザーが使っている cgi 動作を考えると、ちょっとなぁ……。
その他、擬似データベースは結構チューニングしてあるのですが、その甲斐あってか思ったより高パフォーマンスでびっくりました。1000件ぐらいなら余裕で使えますね。これ見てるadiary利用者の方々は「MySQL速いんだ、ぜひとも使おう」とか思いませんように。
adiary を cgi として動かす限り、MySQLの速さの恩恵にあずかるどころかMySQLのモジュールロード時間で恩恵どころか遅くなってしまいます(PostgreSQLも同様)。速く動作させたかったら、まず cgi 以外の方法で動作させてからにしましょう。
件数取得せずにページ送りを出す方法
クイズの答え。例えば1ぺージ5件表示だとしたら、検索するときに6件のデータを取得します。検索結果として6件のデータが得られたら次のページが存在すると分かります。前のページがあるかどうかは、検索開始位置が1件目であれば前のページがない、2より大きければ前のページがあると判別します。
単純でしょ?