mybook

エピローグ — キャッシュ設計チェックリスト

マナミから未来のエンジニアへ

1年前、深夜のアラートから始まったキャッシュの旅。マナミは最後に、自分が学んだことをチェックリストとしてまとめた。

キャッシュ設計チェックリスト

基本設計

  • キャッシュなしでも動作するか?(キャッシュは最適化であり依存先ではない)
  • 読み書きパターンを分析したか?(Cache-Aside / Write-Through / Write-Behind)
  • TTL を設定したか?(無期限キャッシュは事故の元)
  • キャッシュキーは一意で、更新時に変化するか?

HTTP キャッシュ

  • 静的アセットに immutable + 長い max-age + ハッシュ付きファイル名
  • HTML に no-cache + ETag
  • API に適切な s-maxage + stale-while-revalidate
  • 個人データに private, no-store
  • Vary ヘッダーを正しく設定

Redis

  • maxmemorymaxmemory-policy を設定
  • 本番で KEYS * を禁止(SCAN を使う)
  • 接続プールを設定
  • 監視メトリクスを収集(ヒット率、メモリ、レイテンシ)

セキュリティ

  • 認証付きレスポンスは CDN にキャッシュしない
  • Redis はプライベートネットワーク + パスワード
  • Cache Poisoning 対策(キャッシュキーの明示)
  • Cache Deception 対策(パス正規化)

障害対策

  • キャッシュサーバーダウン時のフォールバック
  • Cache Stampede 対策(ロック or SWR)
  • サーキットブレーカーの設定
  • アラート閾値の設定

Rails キャッシュクイックリファレンス

# config/environments/production.rb
config.cache_store = :redis_cache_store, {
  url: ENV["REDIS_URL"],
  expires_in: 1.hour,
  namespace: "freshmarket",
  error_handler: -> (method:, returning:, exception:) {
    Rails.logger.error("Redis error: #{exception}")
    Sentry.capture_exception(exception)
  }
}
 
# Cache-Aside パターン
Rails.cache.fetch("product:#{id}", expires_in: 1.hour) do
  Product.find(id)
end
 
# フラグメントキャッシュ(View)
<% cache @product do %>
  <%= render "product_card", product: @product %>
<% end %>
 
# ロシアンドールキャッシュ
<% cache ["products", @products.maximum(:updated_at)] do %>
  <% @products.each do |product| %>
    <% cache product do %>
      <%= render "product_card", product: product %>
    <% end %>
  <% end %>
<% end %>
 
# HTTP キャッシュ(Controller)
class ProductsController < ApplicationController
  def show
    @product = Product.find(params[:id])
    
    if stale?(etag: @product, last_modified: @product.updated_at)
      expires_in 1.hour, public: true,
        stale_while_revalidate: 5.minutes
      render :show
    end
  end
end
 
# キャッシュ無効化(Model コールバック)
class Product < ApplicationRecord
  after_commit :invalidate_cache
 
  private
 
  def invalidate_cache
    Rails.cache.delete("product:#{id}")
    Rails.cache.delete_matched("products:*")
  end
end

Cache-Control 早見表

シナリオ設定
静的アセット(ハッシュ付き)public, max-age=31536000, immutable
HTML ページno-cache + ETag
公開 APIpublic, s-maxage=60, stale-while-revalidate=300
ユーザー固有 APIprivate, no-cache
認証・決済no-store

キャッシュ戦略の全体像

Loading chart...

最後に

INFO

キャッシュの本質: 同じ計算を二度しない。それだけのことだが、正しく実装するには、HTTP、CDN、アプリケーション、Redis、DB、セキュリティ、分散システムの知識が必要になる。一つずつ、層を重ねていけばいい。

マナミの FreshMarket は今日も快適に動いている。深夜2時のアラートはもう鳴らない。