2005年03月09日

同じクエリが良く発行されていて、DBサーバにまで負荷をかけずにフロントエンドでキャッシュする仕組みが無いかと思っていて、
どこかで実装されている物だと思い探していたのですが、無いようなのので実装してみました。

DBI経由で取得するクエリの結果を透過的にキャッシュする事が可能なモジュールです。
透過的と言っても、キャッシュしないSQL分を正規表現で指定して弾くというだけで
アプリケーションによっては、不都合が大量に出てくるので明示的にキャッシュのON/OFFを指定するメソッドも追加しています。
現在は、MFPMにて試験的に導入されています。

ソース
説明

開発の思想としては、キャッシュにヒットする事でパフォーマンスが向上する事は考えておらず、
キャッシュにヒットさせることでDBサーバのクエリ数を減らし、結果的に全体のパフォーマンスを向上させる事を考えています。
なので、DBサーバとフロントエンドが同居している場合はどうなるかは分かりません。
ただし、indexを使わないwhereクエリなどではパフォーマンスが上がりそうです。
キャッシュのさせ方を間違えると効果が期待出来ないので、かなり諸刃の剣なモジュールだとは思います。

開発はDBD::mysql環境のみで行っているので、DBD::Pgとかでどうなるかは分かっていません。
MySQL環境で開発した影響で、SQL_CALC_FOUND_ROWS /FOUND_ROWSもうまく取り扱えるように実装されています。

使い方は


use DBIx::QueryCache;

my $attr = { _DBIx_QueryCache_dir => $cache_dir };
my $dbh = DBIx::QueryCache->connect($data_source, $username, $auth, $attr);

としてDBIと同じようにconnectをしておいて


#キャッシュさせるクエリ
$sth = $dbh->prepare_enable($statement[, $expries]);

#キャッシュさせたくないクエリ
$sth = $dbh->prepare_disable($statement);


って感じです。
connectのオプションのつけ方しだいで、既存のprepareのみを使用してキャッシュ処理を行う事も可能です。

当初は、既存のソースコードの変更を最小限にしつつ、キャッシュ機能が使えるようにしたかったのですが
アプリケーションによっては、明示的にキャッシュ制御が出来ないと不都合が起きてしまうので
今のような中途半端な仕様になってしました。。。

デフォルトでは、Cache::FileCacheを用いてキャッシュ処理を行っていますが、get/set/sizeメソッドが実装されている物なら、どれでも使えます。
ただし、Cache::FileはCache::FileCache使用時よりも3倍遅いので推奨しません。


肝心のパフォーマンスですが。
単純なselect文では標準のDBIの方が早いです。
like '%key%'のようなインデックスを使わないクエリの場合は、こちらの方が早いです。
早いと言ってもキャッシュに引っかからなければパフォーマンスはでません。

参考程度に、軽くパフォーマンス測定をした時の結果を載せておきます。
PerlスクリプトのあるマシンとDBサーバは物理的に異なるマシンで、100MのLANで接続された環境となっています。
コードA


use DBIx::QueryCache;

my $dbh = DBIx::QueryCache->connect($data_source, $username, $auth,
{
_DBIx_QueryCache_dir => './DBIxQueryCacheTEST',
});
for (my $i = 0;$i < 100;$i++) {
for (my $ii = 1;$ii < 100;$ii++) {
my $sth = $dbh->prepare_enable("select * from FAVORITE_PM limit $ii");
$sth->execute;
while (my $row = $sth->fetchrow_arrayref) {
}
}
}

コードB


use DBI;

my $dbh = DBI->connect($data_source, $username, $auth);
for (my $i = 0;$i < 100;$i++) {
for (my $ii = 1;$ii < 100;$ii++) {
my $sth = $dbh->prepare("select sql_calc_found_rows * from FAVORITE_PM limit $ii");
$sth->execute;
while (my $row = $sth->fetchrow_arrayref) {
}
}
}

コードC


use DBIx::QueryCache;

my $dbh = DBIx::QueryCache->connect($data_source, $username, $auth,
{
_DBIx_QueryCache_dir => './DBIxQueryCacheTEST',
});
for (my $i = 0;$i < 100;$i++) {
for (my $ii = 1;$ii < 100;$ii++) {
my $sth = $dbh->prepare_enable("select * from MODULE_MASTER where DESCRIPTION like '%dbi%' limit $ii");
$sth->execute;
while (my $row = $sth->fetchrow_arrayref) {
}
}
}

コードD


use DBI;

my $dbh = DBI->connect($data_source, $username, $auth);
for (my $i = 0;$i < 100;$i++) {
for (my $ii = 1;$ii < 100;$ii++) {
my $sth = $dbh->prepare("select * from MODULE_MASTER where DESCRIPTION like '%dbi%' limit $ii");
$sth->execute;
while (my $row = $sth->fetchrow_arrayref) {
}
}
}

各コードを3回実行した時の中間タイム

A B C D
real 0m21.455s 0m17.840s 0m8.705s 0m12.179s
user 0m20.461s 0m6.654s 0m7.964s 0m2.035s
sys 0m0.880s 0m3.478s 0m0.737s 0m1.098s

現状はApache::DBIやClass::DBIなどの事を考慮していません。

あとは、ある程度動作実績をつけて、ドキュメントを英文にしてCPANに登録する感じかな。

Posted by Yappo at 2005年03月09日 04:25 | TrackBack | Perl
Comments
Post a comment









Remember personal info?






コメントを投稿する前に↓の場所にnospamと入力してください。