まだ重たいCMSをお使いですか?
毎秒1000リクエスト を捌く超高速CMS「adiary

2007/04/28(土)ページ送りのためのデータベース検索

adiaryにGoogleのようなページ送りを実装できないかというお話がメーリングリストでありました。

google_pages.jpg

実は速度面で不利なので実装してなかったんですよね。どれくらい不利なのか調べてなかったので検討してみました。

その前にクイズ

adiaryではご覧の通り「次のページ」と「前のページ」は(存在すれば)常に表示されるようになっていますが、実は全件のデータを取得することなく表示しています。どうやってるか分かりますか?

分かってしまえばかなり単純な仕組みですが、ご覧の通り十分実用的な動作をしています。

実行速度の検証

データ数が少ないときは気にするほどの差はないので、とりあえず810件、1MBほどのデータをインポートしてテストしてみました。

条件pseudo(cgi)pseudo(cache)Pg(8.1.8)MySQL(5.0.27)
今の実装(トップ)131.0 ms110.9 ms92.9 ms50.2 ms
件数取得(トップ)130.7 ms111.7 ms98.2 ms51.7 ms
今の実装(検索)135.3 ms121.6 ms171.5 ms86.1 ms
件数取得(検索)1642.6 ms397.5 ms269.4 ms88.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 ~

*1 : SpeedyCGI, FastCGI, mod_perl等

考察

微妙なところですねぇ~。気にしなくていいと言えば気にしなくていいような、気にすると言えば気にするような。検索のことを考えなければ、どのデータベースでも件数取得しても問題ないのですか、全文検索、しかも多くのユーザーが使っている cgi 動作を考えると、ちょっとなぁ……。

その他、擬似データベースは結構チューニングしてあるのですが、その甲斐あってか思ったより高パフォーマンスでびっくりました。1000件ぐらいなら余裕で使えますね。これ見てるadiary利用者の方々は「MySQL速いんだ、ぜひとも使おう」とか思いませんように。

adiary を cgi として動かす限り、MySQLの速さの恩恵にあずかるどころかMySQLのモジュールロード時間で恩恵どころか遅くなってしまいます(PostgreSQLも同様)。速く動作させたかったら、まず cgi 以外の方法で動作させてからにしましょう。

件数取得せずにページ送りを出す方法

クイズの答え。例えば1ぺージ5件表示だとしたら、検索するときに6件のデータを取得します。検索結果として6件のデータが得られたら次のページが存在すると分かります。前のページがあるかどうかは、検索開始位置が1件目であれば前のページがない、2より大きければ前のページがあると判別します。

単純でしょ?