isyumi_netブログ

isyumi_netがプログラミングのこととかを書くブログ

全体を集計した結果のキャッシュ

 

blog.isyumi.net

 前回、元データとキャッシュが1:1で対応するときのコツを書いた。今回は全体を集計した結果のキャッシュの話をする。難易度が1段上がる。

まずどのようなキャッシュが該当するか考えよう。月ごとの売上を合計して画面に表示するプログラムがあったとしよう。このようなSQLになる。

select month , sum(price) as month_sum from sales group by month;

売上件数が多ければ結構な時間がかかってしまう。そこで、別テーブルに集計しておき、次回はそこからデータをとってくることにした。

create table cache_month_sales as select month , sum(price) as month_sum from sales group by month;

この場合、salesテーブルのどの行がキャッシュテーブルのどの行に対応するか明確に決まっていない。このキャッシュは元データ全体に対するキャッシュだ。

このキャッシュの難しさは以下の二点だ

  1. 元データのどこが変更されたらキャッシュのどこを更新しないといけないか一概にいえなくなる
  2. 別の処理がキャッシュに依存している

1は、先述の例は簡単すぎたが、もっと複雑な集計をすることもある。例えば各月のランクもキャッシュしていたなら、salesテーブルを1行変えただけで順位の入れ代わりを計算することになる。

2は、元データからすぐに集計結果を出せるわけではないので、キャッシュが存在しなければ元データからデータを取り出そうという実装ができない。

では、このようなキャッシュを作るうえで気をつけたいことを挙げる。まず、元データに更新がかかったらキャッシュを全部作り直すか部分的に更新するか決めよう。先の例であれば、部分的に更新するSQLはこうだ。

insert into sales values( ? , ? );
update cache_month_sales set month_sum + ? where month = ?;

全部一気に更新するならこうだ。

drop table if exists cache_month_sales;

create table cache_month_sales as select month , sum(price) as month_sum from sales group by month;

これらは主に読み込みと書き込みの頻度を元に決める。書き込みが頻繁なら部分アップデートをしなければいけないがたまにしか書き込まれないなら全体を洗いがえてもいい。部分を更新するほうを選ぶときは、それなりに覚悟を持って。次に、どちらを選ぶにせよ全部一気に更新する方法を用意しよう。最後に、前回も話したが全部一気に更新する間違えにくいUIを用意しておこう。あなたが一番あせってるときに間違えないUIだ。

補足

PostgreSQLを使っている場合、Materialized Viewというそのまんまの機能があるのでそっちを使おう。

正規化したTableとキャッシュ用のテーブルの区別がつくように、キャッシュ用のテーブルにはすべてcache_やmareriarized_などのPrefixをつけよう。

全部一気に更新するときにdrop tableしてから create tableしたが、truncateからのinsert selectのほうがいいかも。

create tableしたら alter add indexをしたほうがいい場合があるので忘れないように。