すべてのキャッシュは約束だ:「このデータはおそらくまだ正しい」。
「おそらく」を十分積み上げると、Netflix Tudumの問題になる — 編集者が記事を保存し、数分待ち、プレビューはまだ古いバージョンを表示。
Netflixの編集プラットフォーム。CQRSアーキテクチャ。Kafkaが書き込みと読み込みパスを橋渡し。Cassandraが読み込みストア。ニアキャッシュがその前に。
編集者が保存 → CMSウェブフック → 取り込みパイプライン
→ Kafka → Cassandra → ニアキャッシュタイマー
合計: 6ステップ。それぞれがレイテンシを追加。
各ステップ = 嘘をつく機会が増える。
ニアキャッシュはスケジュールでリフレッシュしていた、オンデマンドではなく。50フラグメントのページ、それぞれ独自のタイマー。一部は更新される。一部はされない。
半分新しく、半分古いコンテンツ。間違いでもない。正しくもない。ただ嘘。
裏で何が起きていたか:
1回の編集のタイムライン:
0:00 編集者が記事タイトルを更新
0:01 CMSがウェブフックを発火
0:05 取り込みパイプラインが処理
0:10 Kafkaがイベントを受信
0:15 Cassandraがディスクに書き込み
0:30 ニアキャッシュタイマーが期限切れ
0:31 キャッシュがリフレッシュ... やっと
30秒の嘘。
これを50ページフラグメントで掛け算、それぞれ異なるリフレッシュ間隔:
フラグメントA: "ヘッダー" → 10秒ごとにリフレッシュ → 新しいタイトルを表示
フラグメントB: "コンテンツ" → 60秒ごとにリフレッシュ → 古い本文を表示
フラグメントC: "関連記事" → 120秒ごとにリフレッシュ → 古いリンクを表示
フラグメントD: "著者プロフ" → 30秒ごとにリフレッシュ → 新しいアバターを表示
フランケンシュタインページ。一部は新しく、一部は2分古い。
よりスマートなキャッシュを追加しなかった。TTLをチューニングしなかった。キャッシュを完全に削除した。
ビフォー: CMS → ウェブフック → パイプライン → Kafka → Cassandra → キャッシュ → クライアント。データが古くなれる場所が5つ。
アフター: CMS → RAW Hollow (インメモリ) → クライアント。単一の真実の源。無効化不要。古さ不可能。
RAW Hollow:すべてのサービスインスタンスに埋め込まれたインメモリオブジェクトストア。3年分のコンテンツがインスタンスあたり約130MBに圧縮。すべてのルックアップがO(1)、インプロセス。
ネットワークホップなし。キャッシュ無効化なし。古いデータなし。ただのメモリ読み取り。
ビフォー vs アフター: ホームページ構築が1.4秒から0.4秒に。キャッシュ古さが0-120秒から0秒に。関与コンポーネントが6+から1に。デバッグ複雑度が高から低に。インスタンスあたりメモリが約50MBから約130MBに増加。
トレードオフ: 80MB多いRAM = ゼロの古さバグ。
自問しろ:
解決策は必ずしも「すべてのキャッシュを削除」ではない。でもキャッシングアーキテクチャがビジネスロジックより多くのコンポーネントを持っているなら、何かがおかしい。
シンプル(たいてい大丈夫): App → Redis → DB。1キャッシュレイヤー、明確な無効化。
危険(慎重に監査): CDN → エッジキャッシュ → アプリキャッシュ → Redis → DBキャッシュ。5レイヤー、古いデータを配信する機会が5回。無効化が分散システム問題になる。
Netflix Tudumソリューション: App → インメモリデータ。ゼロキャッシュレイヤー。ゼロ古さ。
最速のキャッシュはキャッシュなしだ。 データセットがメモリに収まるなら、儀式をスキップしろ。
すべてのキャッシュはトレードオフだ:速度 vs 正確性。どちらを選んでいるか知れ。「速度」を選んでいるのに「バグ」を得ているなら、間違った選択をしている。
最高のキャッシュ戦略は、キャッシュが全く必要なくなる戦略だ。
— blanho
冗長パイプライン、インテリジェントなセグメント選択、カスタムストレージレイヤー — Netflix Live Originアーキテクチャの内側。
ロギング、認証、リトライ、レート制限 — 最初に設計しないが、後で全員が苦しむもの。
どのアーキテクチャも1つの問題を解決し、3つの新しい問題を生む。コミットする前に誰も教えてくれないことがここにある。