BEAR.Sunday分散キャッシングフレームワーク


この記事は [BEAR.Sunday Advent Calendar 2021](https://qiita.com/advent-calendar/2021/bear-sunday)の記事です。今年取り組んだBEAR.Sundayの新しいキャッシングフレームワークの記事です。まずは誰もが知っている有名なコンピューターサイエンスの格言から。

>コンピュータサイエンスには難しいことが2つだけある。キャッシュの無効化と、ものの名前をつけることだ。

> フィル・カールトン

## 情報APIと計算API

APIを**情報API** (Information API)、**計算API** (Computation API)と分ける見方があります。違う呼び方では**Data API**、 または**Process API**です。 

https://www.youtube.com/watch?v=JL-iwDa90vk

例えばブログやニュースの記事は前者の情報APIが取り扱うのもので、コンテンツは本質的に静的です。後者はGitHubもダッシュボードやGmail、これらのサイトのコンテンツは動的です。

## TTLキャッシュ

計算APIは求められる度に計算が必要で、基本的にキャッシュに向いていません。日付から曜日を求めるページにキャッシュをしようとは思わないでしょう。

情報APIはキャッシュに向いています。TTL(time to live)で定めた一定時間同じコンテンツを返し、その時間が過ぎればサーバーサイドの場合は再計算、CDNの場合は上位のネットワークに問い合わせます。

この単純な仕組みはアクセスの多いコンテンツには有効ですが、ロングテールで変更の可能性があるコンテンツには不向きです。

## キャッシュの無効化

TTLは予測可能で決定論的(deterministic)なものと動的なものがあります。例えば月末まで有効で内容が変わらないクーポン情報APIや、現在の株価APIのTTLは情報の有効期限が予測できます。場中の時は次の瞬間に値が変わりますし、週末なら月曜日の会場まで値は取得できません。TTLは予測可能でその度にその値を設定することができます。

ところがユーザーが任意のタイミングで編集可能なコンテンツはTTLを決定するのが困難です。長すぎると変更が反映されないし、短すぎるとキャッシュのヒット率が下がります。

## イベントドリブンコンテンツ

「イベンドリブンコンテンツ」はキャッシュの無効化を時間経過ではなく、ユーザーのアクションなどのイベントで行います。その場合コンテンツの依存性に注意しなければなりません。例えばブログ記事ページにあるコメントが変わると、ブログ記事ページ全体のキャッシュを更新しなければなりません。ブログ著者が名前を変更すると、著者リソースに依存するすべてのページのキャッシュを更新しなければなりません。


## ネットワークキャッシュ

> 最高のアプリケーションパフォーマンスはネットワークを使わない時に得られる
> 
> ロイ・T・フィールディング

REST論文の著者、フィールディングのいうようにRESTのキャッシュは本質的にはサーバーサイドのキャッシュではなく、クライアントサイドのキャッシュです。キャッシュを行うのはクライアントやその上位のCDNですが、そのコントロールを行うのはサーバーサイドです。(HTTPのCache-Controlヘッダー）

「n秒間はコンテンツの変更がない」という事をサーバーサイドが知ることができれば、それをクライアントに伝えて計算資源だけでなくネットワークリクエストを節約します。

イベントドリブンコンテンツでは、TTLは分からないので（1秒後かも知れないし1年後かも知れません）代わりに[条件付きリクエスト](https://developer.mozilla.org/ja/docs/Web/HTTP/Conditional_requests)で変更がないかサーバーに問い合わせます。「304 変更なし」の場合にクライアント（またはCDN）に保存されたキャッシュが使われます。

この時のエンティティタグにも依存性があり、それを解決しなければなりません。コメントが変わればその記事本文のページのキャッシュだけでなく、ETagも無効にしなければなりません。

## ドーナッツキャッシュ

<img width="200" alt="donut caching" src="https://user-images.githubusercontent.com/529021/137097856-f9428918-5b76-4c0e-8cea-2472c15d82e9.png">

コメントを追加したときに、コメントを含むブログ記事のキャッシュとETagは無効にしますが、ブログ記事そのものは変更がないのでDBから再度取得して再レンダリングするのではなくそれまでのキャッシュを利用すれば効率的です。

そのような**部分キャッシュ**の問題を解決するのがドーナツキャッシュです。コンテンツを静的なドーナッツの部分と動的な穴の部分に分けて取り扱います。

同様のシチュエーションで、コメント部分をAJAXで取得したりESIを使ってCDNで合成する方法もあります。しかしそれらの方法では（変更がないのに）毎回コメントの取得にリクエストが発生しますし、ETagを使ったネットワークキャッシュを行うことができません。

## キャッシュ技術

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3940/a92f7804-793a-afd2-de09-25aec96dc2b6.png" width=20%>

フレームワークで一般的な単純なTTLによるキャッシュ技術はほとんど進化していません。"private"でも"must-revalidate"でもないコンテンツがそのようなHTTPヘッダーを出力しています。Fastlyの[The rise of event-driven content](https://www.fastly.com/blog/rise-event-driven-content-or-how-cache-more-edge)という記事は「CSSや画像ファイルは静的、PHPやPerlが出力するコンテンツは動的」を超えたテクニックを紹介しています。まさにずっと考えていた事で"BEAR.Saturday"の頃から持っていた静的コンテンツとCQRSを組み合わせるアイデアの実現を後押ししてくれました。

## BEAR.Sundayのキャッシュ

これらのすべての問題に対応するのがBEAR.Sundayの新しい分散キャッシュフレームワークです。**アプリケーション制約**の１つという思いで、DI、リソース、RESTに続く４つ目の**フレームワーク**と呼んでいます。

http://bearsunday.github.io/manuals/1.0/ja/cache.html

「情報APIでは基本的にGETリクエストではPHPサーバーが動作しない」事を目標に開発しました。キャッシュデータが作られるのはPOSTやPUTなどリソース状態を変更したときです。取得はCDNから行われCommand（操作）とQuery（問い合わせ）が分離されています


構想はBEAR.Saturdayの時からありました。symfony/cacheのタグベースでのキャッシュ消去やFastlyなどのプログラマブルなCDNの登場、完全にRESTベースで構築したBEAR.Sunday、さまざまな技術を繋げることで今年2021年ついに可能になりました。

BEAR.Sundayの開発開始から10年の月日が流れました。次の10年もよろしくお願いします。
今年も後数日でお終いです。皆さん、良い年末年始を。

Merry Chrismas and Happy new BEAR!