EmDashサイトでCloudflareキャッシュを有効にして高速化する

EmDash CMS を Cloudflare Workers、D1、R2 にデプロイしているとページの表示が遅いtと感じる場合があります。

これはキャッシュなしでリクエストごとに D1 からコンテンツを取得しレスポンスを組み立てているため Waiting for server response が長くなることが原因です。

実際に計測すると、トップページと記事一覧は TTFB が約1秒、記事詳細では約2秒を超えることがありました。特に記事詳細はコメント、関連記事、タグ、サイドバーウィジェットなどのクエリが重なり、著しくパフォーマンスが悪化していました。

Cloudflare Cache APIを使う

Astro にはルートキャッシュの仕組みがあります。

EmDash のクエリ結果から返る cacheHintAstro.cache.set(cacheHint) に渡すことで、コンテンツIDやコレクション名をキャッシュタグとしてレスポンスに紐づけられます。

ただし、Cloudflare Workers で実際にレスポンスを保存するには、キャッシュプロバイダを設定する必要があります。

今回追加したのは @emdash-cms/cloudflarecloudflareCache() です。これは Cloudflare Workers の Cache API を使い、レンダリング済み HTML をエッジ側に保存します。

D1 や KV に HTML を保存するわけではなく、各 Cloudflare データセンターのエッジキャッシュにレスポンスが置かれます。

設定内容

astro.config.mjscloudflareCache を読み込みexperimental.cache.provider に指定しました。

あわせて routeRules でトップページ、記事一覧、記事詳細、固定ページ、カテゴリ、タグ、検索ページに maxAgestale-while-revalidate を設定しています。

例として、記事一覧は maxAge: 300swr: 86400 にしました。5分間は fresh なキャッシュを返し、その後24時間は stale な HTML を即返しながら裏で再生成できます。

記事詳細は maxAge: 600swr: 86400 です。

計測結果

導入前後で同じ URL を複数回計測しました。

トップページは TTFB 平均 1.151 秒から 0.126 秒、記事一覧は 1.061 秒から 0.130 秒、記事詳細の /posts/emdash-cms は 2.282 秒から 0.128 秒まで短縮しました。

改善率はそれぞれ約89%、約88%、約94%です。

レスポンスヘッダでは x-astro-cache: HITcf-cache-status: HIT を確認できます。初回アクセスやデプロイ直後は MISS になりますが、一度キャッシュされると Waiting for server response は大きく短縮されます。

更新時のpurge

記事を更新したときに古い HTML が残り続けると困るため、Cloudflare の purge API も使えるようにしました。

Worker secret として CF_ZONE_IDCF_CACHE_PURGE_TOKEN を登録します。API token には対象 Zone のキャッシュ削除権限を与えます。

EmDash の保存、公開、非公開、削除などの処理では cache.invalidate({ tags: [...] }) が呼ばれるため、タグ単位でキャッシュを削除できます。

この設定により、通常の閲覧はエッジキャッシュで高速化し、記事更新時には該当記事や一覧系のキャッシュを purge して鮮度を保てます。

タグやカテゴリを更新したときのキャッシュ

記事更新時は EmDash が cache.invalidate({ tags: [collection, id] }) を呼ぶため、記事や一覧のキャッシュを purge できます。

ただし、タグやカテゴリの変更は記事更新とは別です。タグ名を変えても記事本文は変わらないため、最初は記事詳細・一覧・タグアーカイブに古いラベルが残る可能性がありました。

そこで、公開ページ側に taxonomy 用の cache tag を追加しました。

Astro.cache.set({
  tags: [
    `taxonomy:${term.name}`,
    `taxonomy:${term.name}:${term.translationGroup ?? term.id}`,
    `taxonomy:${term.name}:slug:${term.slug}`,
  ],
});

さらに、EmDash の term 作成・更新・削除 API 側で同じタグを cache.invalidate() するようにしました。slug 変更時は旧 slug のタグも purge 対象に含めています。

これで、タグ名・カテゴリ名・slug の変更、記事へのタグ付け変更でも、関連する HTML キャッシュを削除できます。

キャッシュを正しく効かせるには、「記事に依存するページ」だけでなく「taxonomy に依存するページ」も明示しておく必要があります。

注意点

キャッシュは GET リクエスト、未ログインユーザー、成功レスポンスrouteRules または Astro.cache.set()maxAge が設定されたページで有効になります。

ログイン中の astro-session Cookie があるアクセスは、編集ツールバーや下書き表示が混ざらないようにキャッシュをスキップします。

また、コメントのように投稿直後の反映が重要な領域は、記事 HTML 全体のキャッシュとは相性に注意が必要です。記事本文はキャッシュしつつ、コメント一覧だけ API から動的に取得する構成にすると、速度と鮮度のバランスを取りやすくなります。

EmDash のような動的 CMS でも、公開ページの多くは短時間で内容が変わりません。Cloudflare のエッジキャッシュをうまく使うと、D1 へのアクセスを減らしながら体感速度を大きく改善できます。

Comments

Loading comments...