キャッシュは難しい。そこで安全な設計ができるように考えるべきことをまとめた。
まず、キャッシュにはどのようなものがあるか。
- 前回のHTTPリクエストの結果をローカルに保存しておき、次回はそこから結果を返す
- HDDに書き込んだデータをメモリにも保存しておき次回はそこから読み込む
- RDBで時間がかかるクエリを高頻度で実行する時、あらかじめ別テーブルに集計結果を入れておく
- 数学のアルゴリズムを書く際に、CPU負荷の高い平方根を先に済ませて結果を連想配列に入れておく
このようなものが挙げられるだろう。
本題に入る前にいろいろなタイプのキャッシュを分類をしたい。キャッシュは二種類に分けられる。一つは元データとキャッシュデータが一対一で対応するものだ。キャッシュデータが元データのどの部分をキャッシュしているか明確に答えらえるものとも言える。例えば1万ファイルの中で数ファイルをメモリにも持っているようなキャッシュだ。これは、キャッシュしたファイルがHDD上のどのファイルかはっきりしている。もう一つは、元データの集計結果に対するキャッシュだ。例えば、1年間の売り上げが全てDBに入っているとしよう。その総売上金額をメモリにキャッシュしているとする。そのキャッシュデータは売り上げデータ全体に対するキャッシュだ。逆に言えば元データのどこか個別の場所のキャッシュではない。この違いによってキャッシュ設計で考えるべきことが変わってくるのできちんと区別しよう。
次にキャッシュの難しさについて考えたい。なぜキャッシュは難しいのか。そもそも真実は一箇所に管理していた方が都合がいい。同じデータを複数の場所に保存しているとデータに更新をかけたい時どこを書き換えればいいかわからないくなる。また、元データでなくキャッシュの計算式が変わる場合もある。税込みの売上データをキャッシュしていたらある日消費税率が変わったりする。その時キャッシュデータをどう更新しなければいけないのかが難しい。このように、キャッシュはデータやシステムに変更があったら正確にキャッシュを更新しなければ不整合が起こることが難しい。
では、どのような心構えで挑めばいいだろうか。まず、不整合とは何かはっきりさせたい。一つは元データが更新されたのにキャッシュデータが更新されていない状態だ。次に、キャッシュの計算式を変えた時は、キャッシュを更新するまで全てのキャッシュが不整合な状態になる。さらに、今キャッシュされているデータが整合しているか自信がない時も不整合と同じである。例えばDBの一箇所を手動でUpdateしたが、このUpdateはキャッシュとの間に不整合を起こすかわからなかったとしよう。心配になってる時点で不整合が起きていると一緒だ。
そこで、何が正になるデータかはっきりさせよう。HDDのデータをメモリにも持つならHDDが正だ。RDBで正規化したテーブルとパフォーマンスのために結合結果を保存してあるテーブルがあれば正規化したテーブルの方が正だ。正のデータはキャッシュのデータに依存してはいけない。存在を知っていてもいけない。キャッシュ管理層は正のデータを取りに行っていいが、反対に正のデータ管理層はキャッシュデータに触らなければ整合性を維持できない状態になってはいけない。また、キャッシュデータは知っているけど正のデータは知らない情報があってもいけない(ただし、キャッシュの作成時刻のようなメタデータはOK)。このようにレイヤーを意識して依存関係が複雑にならないようにするといい。
次に、どんな時にキャッシュのどの部分を更新するかきちんと洗い出そう。決定事項はReadme.mdなどにきちんと書き出して管理しておくことをお勧めする。この部分は特に「ソースコードが正解」という事態は避けた方がいい。
そして、不整合はいつか起きるものだと思っておこう。起こらないようにするより大事なことだ。
では、ここからいいキャッシュのためどんな作戦を立てればいいかを考えよう。
まず、元データとキャッシュが1対1で対応するようなキャッシュではここに気をつけたい
- キャッシュはあってもなくても同じ動きになるようにしたい
- あった方が速いがなくてもいいという状態が最善
- 元データ管理層がキャッシュのクリアを指示できるように
- キャッシュがおかしくなったらとりあえずキャッシュをクリアすればいいようにしておきたい
- キャッシュを簡単に削除できるように
逆に、全体に対するキャッシュの場合は
- キャッシュを簡単い作り直せるように
を気をつけよう。
具体例は次回以降に話したい。