大多數工程師的做法:把 KV Cache 設一個固定 TTL,讓它自動過期,再出問題就加更多 GPU。 資深 FDE 的思維:把快取當分層財務決策——什麼時候該升層、什麼時候該壓縮、什麼時候主動驅逐, 每一個決策節點都有精確的數字閾值,而不是「看狀況再說」。 差距不在技術知識,在於你能不能把顯存、計費週期、對話語義三個維度同時管住。
面試情境
你的 B2B 對話式 AI 平台服務 5 萬名企業用戶,平均每位用戶每天與 Agent 進行 20–40 輪對話。 隨著上下文長度增長,GPU 顯存使用率持續攀升,高峰期 OOM(Out of Memory)崩潰率達到 3%, 同時 Vertex AI 帳單每月 $120K,CFO 要求兩個月內把成本降低 50%。 你被要求在不降低對話品質的前提下,重新設計快取架構。你的方案是什麼?
一、核心問題:為什麼 KV Cache 管理會讓 B2B SaaS 崩潰
1.1 KV Cache 的本質與代價
大型語言模型在推理時,Attention 機制需要存取所有歷史 Token 的 Key/Value 向量。每一輪新對話都要「看過」所有先前的 Token,這個快取(KV Cache)讓模型不需要重新計算,代價是它活在 GPU 顯存(VRAM)裡。
以 Gemini 1.5 Pro 為例:
- 每 1K tokens 的 KV Cache 佔用約 0.5–1 MB VRAM
- 100 輪對話 × 平均 500 tokens/輪 = 50K tokens = 25–50 MB per session
- 同時服務 1,000 個活躍 session = 25–50 GB VRAM
一張 A100 80GB 只能服務約 1,600 個並發長對話 session,再多就 OOM。
1.2 計費陷阱:Vertex AI Context Caching 的結構性問題
Vertex AI Context Caching 按以下規則計費:
- 最低快取長度:32,768 tokens(低於此不得建立快取)
- 計費單位:每小時,不足一小時按一小時計算
- 快取寫入費: 比標準輸入 token 低 25%,但快取存儲本身每小時有持續費用
這意味著:若用戶聊了 10 分鐘(產生 5K tokens)就下線,快取根本無法建立;若強行建立也不符合條件。但若系統設計不當,在對話剛過 32K 就立即建立,而用戶在 10 分鐘後下線,你就付了整整一小時的快取存儲費,什麼效益都沒有。
1.3 業務維度的三重矛盾
┌─────────────────────────────────────────────────────────────┐
│ KV Cache 的三重矛盾 │
├──────────────────┬──────────────────┬───────────────────────┤
│ GPU 顯存限制 │ 計費週期結構 │ 對話品質要求 │
│ │ │ │
│ 長對話 → OOM │ 短對話 → 浪費 │ 壓縮過度 → 失憶 │
│ 多用戶 → 競爭 │ 建得太早 → 罰款 │ 丟棄太多 → 幻覺增加 │
│ 無驅逐 → 崩潰 │ 放太久 → 超收 │ 快照太粗 → 脈絡斷裂 │
└──────────────────┴──────────────────┴───────────────────────┘
核心洞察:這不是純技術問題,是需要對齊計費模型、顯存容量、對話語義三個維度的財務工程問題。
二、三個演進階段
╔══ Phase 1(POC / < 10K 用戶)══╗
策略:單層 Redis TTL,靠 GPU 撐住
┌──────────────────────────────────────────────────────────┐
│ Phase 1 架構(POC) │
│ │
│ 用戶請求 │
│ │ │
│ ▼ │
│ ┌──────────┐ ┌──────────────────────────────────┐ │
│ │ API GW │────▶│ LLM Service (Vertex AI) │ │
│ └──────────┘ │ - 每次請求帶全部 History │ │
│ │ - 無 Context Cache │ │
│ └──────────────┬───────────────────┘ │
│ │ │
│ ┌──────────────▼───────────────────┐ │
│ │ Redis(單機) │ │
│ │ - Key: session_id │ │
│ │ - Value: 全部對話 JSON │ │
│ │ - TTL: 24 小時 │ │
│ └──────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
新增組件(vs 無快取基線): Redis 單機,session 對話存儲
成本/複雜度:
- 基礎設施:$200/月(Redis + GPU VM)
- 工程成本:2 週實作
- OOM 閾值:~500 並發用戶就開始出問題
能解決什麼: 不用每次重連都從頭建立 session;Redis 讀取 < 5ms
還剩什麼問題:
- 沒有 token 感知:不知道對話有多長,盲目存全文
- 沒有顯存管理:100 輪對話直接把 GPU 壓垮
- 帳單線性成長:用戶越多,輸入 token 費用越高(無快取複用)
- 用戶超過 5K 就開始 OOM 崩潰
╔══ Phase 2(MVP / 10K–200K 用戶)══╗
策略:L1 Redis + L2 Vertex AI Context Cache,加入 Token 閾值判斷
┌──────────────────────────────────────────────────────────────────┐
│ Phase 2 架構(MVP) │
│ │
│ 用戶請求 │
│ │ │
│ ▼ │
│ ┌──────────┐ ┌────────────────────────────────────────────┐ │
│ │ API GW │──▶│ Cache Gateway(新增) │ │
│ └──────────┘ │ - 查 Token 計數 (Redis Counter) │ │
│ │ - 判斷是否升層至 L2 │ │
│ └────────┬──────────────────┬────────────────┘ │
│ │ │ │
│ Token < 32K │ Token ≥ 32K │ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌─────────────────────────┐ │
│ │ L1: Redis │ │ L2: Vertex AI Context │ │
│ │ Cluster │ │ Caching API │ │
│ │ (最近 3 輪) │ │ (全部歷史壓縮快取) │ │
│ └──────┬───────┘ └──────────┬──────────────┘ │
│ │ │ │
│ └──────────┬────────────┘ │
│ ▼ │
│ ┌────────────────────────┐ │
│ │ LLM Service │ │
│ │ (Vertex AI Gemini) │ │
│ └────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
新增組件(vs Phase 1):
- Cache Gateway:token 計數 + 升層決策
- Redis Cluster(多節點,支援 LRU 驅逐策略)
- Vertex AI Context Caching API 整合
- Redis Sorted Set 存放活躍 session 排名(LRU 基礎)
成本/複雜度:
- 基礎設施:$3,500/月(Redis Cluster + GPU + L2 快取費)
- 工程成本:6 週實作
- 5 萬用戶時的 OOM 率從 3% 降至 0.4%
能解決什麼:
- 長對話(> 32K tokens)自動升至 L2,GPU 顯存降低 60%
- 輸入 token 費用降低(Context Cache 重複使用)
- Redis LRU 自動驅逐非活躍 session,顯存不再積累
還剩什麼問題:
- 用戶閒置後快取仍在計費(未主動 Evict)
- 無語義壓縮:長時間不活躍的對話沒有被摘要歸檔
- 重新上線的用戶需要重建完整 Context,冷啟動慢
╔══ Phase 3(Scale / 200K–1M+ 用戶)══╗
策略:L1 Redis + L2 Vertex AI Cache + L3 Firestore Snapshot,完整三層驅逐閘道
┌──────────────────────────────────────────────────────────────────────────┐
│ Phase 3 架構(Enterprise Scale) │
│ │
│ 用戶請求 │
│ │ │
│ ▼ │
│ ┌──────────┐ ┌────────────────────────────────────────────────────┐ │
│ │ API GW │──▶│ Hierarchical Cache Gateway │ │
│ └──────────┘ │ ┌────────────┐ ┌──────────────┐ ┌───────────┐ │ │
│ │ │ Token 計數 │ │ 頻率窗格驗證 │ │ 閒置偵測 │ │ │
│ │ │ (Redis) │ │ (滑動窗口) │ │ (TTL觸發) │ │ │
│ │ └──────┬─────┘ └──────┬───────┘ └─────┬─────┘ │ │
│ └─────────┼───────────────┼────────────────┼─────────┘ │
│ │ │ │ │
│ ┌────────────────▼───┐ ┌───────▼──────┐ ┌────▼──────────┐ │
│ │ L1: Redis Cluster │ │ L2: Vertex AI│ │ L3: Firestore │ │
│ │ - 最近 3 輪明文 │ │ Context Cache│ │ Snapshot │ │
│ │ - LRU 驅逐 │ │ - 32K+ token │ │ - < 1K token │ │
│ │ - TTL: 30 分鐘 │ │ - 按小時計費 │ │ - 永久保存 │ │
│ └────────┬───────────┘ └──────┬───────┘ └─────┬─────────┘ │
│ │ │ │ │
│ ┌────────▼──────────────────────▼─────────────────▼──────────┐ │
│ │ LLM Service (Vertex AI Gemini) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ 異步 Flash 壓縮任務(Cloud Run Jobs) │ │
│ │ 閒置 15 分鐘觸發 → Flash 模型語義摘要 → 寫入 L3 → Evict L2 │ │
│ └──────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────┘
新增組件(vs Phase 2):
- 頻率窗格驗證器:滑動窗口計數器,防止短對話誤建 L2 快取
- 閒置偵測器:Redis TTL + Pub/Sub 觸發異步壓縮
- Flash 壓縮任務:Cloud Run Job 執行語義摘要 + 重要性評分
- L3 Firestore Snapshot:< 1K token 的精煉核心記憶快照
- 主動 Evict 邏輯:壓縮完成後呼叫 API 釋放 L2 快取
成本/複雜度:
- 基礎設施:$18,000/月(vs 無優化的 $60,000+)
- 工程成本:12 週實作(包含 Flash 壓縮品質調校)
- 100 萬用戶 OOM 率 < 0.05%
- GPU 顯存使用率峰值從 92% 降至 41%
解決的問題:
- 閒置快取費用歸零(主動 Evict)
- 冷啟動使用 L3 Snapshot,< 1K token 重建 context
- GPU 顯存降低 80%,帳單砍半
三、L1 層:Redis Cluster 的對話槽位與 LRU 驅逐設計
3.1 資料結構設計
L1 層使用 Redis Cluster,每個 session 的資料結構如下:
Key: conv:{tenant_id}:{session_id}:messages
Type: List(最多保留最近 N 輪)
TTL: 1,800 秒(30 分鐘不活躍自動過期)
Key: conv:{tenant_id}:{session_id}:token_count
Type: Integer(累計 token 數)
TTL: 1,800 秒
Key: conv:{tenant_id}:active_sessions
Type: Sorted Set(score = last_active_timestamp)
TTL: 永不過期(LRU 基礎)
Redis 設定 maxmemory-policy allkeys-lru,當記憶體達到閾值(設 80% 容量)時,自動驅逐最久未存取的 key。
3.2 LRU 驅逐的租戶公平性問題
純 LRU 在多租戶環境有個問題:大租戶(高頻用戶)會把小租戶的 session 全部驅逐出去。解法是實作「租戶配額感知的 LRU」:
每個租戶最大可佔用 Redis 記憶體 = 總容量 × (租戶月費 / 總月費) × 1.5
(1.5 倍作為突發緩衝)
超過配額的租戶:新 session 直接觸發該租戶內部的 LRU 驅逐
未超配額的租戶:按全域 LRU 正常運作
這樣 Enterprise 客戶不會因為 SMB 客戶的突發流量而 session 被驅逐。
3.3 token 計數的精確度取捨
精確 token 計數需要呼叫 tokenizer,每次對話更新有 2–5ms 額外延遲。在 L1 層使用估算法:
估算 token 數 = 字元數 × 0.35(中文)或 × 0.25(英文)
誤差率:± 8%
觸發 L2 的保守閾值:28K estimated tokens(= 實際 32K 的 87.5%)
用 8% 誤差換取 < 0.1ms 的計數延遲,在 100K QPS 下每秒省下 200–500ms 的累積計算時間。
3.4 Redis Cluster 的 Sharding 策略
在多租戶環境下,Redis Cluster 的 key 分佈策略直接影響 hot slot 問題。若直接以 tenant_id 作為 hash slot 基礎,大型租戶的所有 session 都落在同一個 slot,會造成單節點過熱。
正確做法是使用複合 hash tag:
Key 格式:{tenant_id:session_id}:messages
Redis 以花括號內的內容計算 hash slot:tenant_id:session_id
效果:
- 同一 tenant 的不同 session 分散到不同 slot
- 同一 session 的所有 key(messages、token_count)落在同一 slot(保證原子操作)
Cluster 節點數設計參考:
- 每個 Redis 節點建議最大記憶體 32 GB(含 replica 的 overhead)
- 每個活躍 session 約佔 50–200 KB(3 輪對話 JSON + metadata)
- 100K 並發 session × 100 KB = 10 GB,建議 6 節點(3 主 3 從)配置,留 40% 緩衝
3.5 L1 的 Pipeline 批量寫入優化
在高峰期(如早上 9–10 點,企業用戶集中上線),每秒可能有 50K+ 次 Redis 寫入。使用 Redis Pipeline 將每輪對話的多個操作批量提交:
單次對話輪次的 Redis 操作(批量執行):
1. LPUSH conv:{tid}:{sid}:messages {turn_json} → 新增對話輪次
2. LTRIM conv:{tid}:{sid}:messages 0 5 → 只保留最近 6 輪
3. INCRBY conv:{tid}:{sid}:token_count {turn_tokens} → 更新 token 計數
4. EXPIRE conv:{tid}:{sid}:messages 1800 → 重設 TTL
5. ZADD conv:{tid}:active_sessions {ts} {sid} → 更新 LRU 排名
Pipeline 批量 vs 逐條執行:RTT 從 5ms × 5 = 25ms 降至 5ms(一次 RTT)
四、L2 層:Vertex AI Context Caching 的精確觸發機制
4.1 升層決策矩陣
不能只看 token 數就升層,必須同時驗證對話頻率:
┌──────────────────────────────────────────────────────────────────┐
│ L2 升層決策矩陣 │
│ │
│ Token 數 < 28K │ Token 數 ≥ 28K │
│ ───────────────────────────────────────────────── │
│ 頻率 < 3 輪/小時 │ 不升層 │ 不升層(短暫爆發) │
│ 頻率 3–10 輪/小時 │ 不升層 │ 升層(正常長對話) │
│ 頻率 > 10 輪/小時 │ 不升層 │ 升層 + 提前觸發(高頻用戶) │
│ │
│ 滑動窗口:過去 60 分鐘的對話輪數(Redis ZADD + ZCOUNT) │
└──────────────────────────────────────────────────────────────────┘
頻率驗證的實作使用 Redis Sorted Set:
ZADD conv:{session_id}:turns {current_timestamp} {turn_id}
ZREMRANGEBYSCORE conv:{session_id}:turns 0 {timestamp_1h_ago}
turn_count = ZCOUNT conv:{session_id}:turns {timestamp_1h_ago} +inf
這個操作的時間複雜度是 O(log N),N 為一小時內的對話輪數,在 1,000 QPS 下約 0.3ms。
4.2 Vertex AI Context Cache 的生命週期管理
建立快取時,TTL 設定策略:
初始 TTL = 2 小時(預估用戶當天會繼續使用)
每次對話輪次 → 延長 TTL 30 分鐘(上限 8 小時)
閒置 15 分鐘 → 觸發異步壓縮任務,壓縮完成後主動 DELETE cache
主動 DELETE 是關鍵:不等 TTL 自然過期,壓縮完成後立即釋放,避免付「無效快取時間」的費用。
4.3 快取命中率監控
在 Cloud Monitoring 追蹤三個指標:
| 指標 | 目標值 | 告警閾值 |
|---|---|---|
| L1 命中率 | > 85% | < 70% |
| L2 命中率 | > 60% | < 40% |
| L2 建立後存活時間(中位數) | > 90 分鐘 | < 45 分鐘 |
| 無效快取費用比例 | < 5% | > 15% |
L2 存活時間低於 45 分鐘代表升層條件設得太寬鬆,讓太多短暫高峰對話建了快取又立刻閒置。
4.4 L2 快取的版本管理問題
當 Gemini 模型版本升級時(如從 gemini-1.5-pro-001 升至 gemini-1.5-pro-002),舊版本的 Context Cache 無法被新版本讀取,必須重建。這個「快取失效風暴」(Cache Invalidation Storm)如果發生在高峰期,會造成:
- 所有活躍的 L2 快取同時失效
- 瞬間大量請求降級到 L1 或重建 Context
- GPU 顯存和 token 費用同時暴增
緩解策略:
- 滾動升級:先在 10% 流量上切換新模型版本,觀察 L2 重建速率,確認無異常後再全量切換
- 雙版本並行期:維持 48 小時的舊版本快取,讓用戶自然下線後重建新版本快取
- 預熱腳本:在低峰期(凌晨 2–4 點)批量預建高活躍租戶的 L2 快取(取最近 7 天活躍 session 的 Top 20%)
4.5 L2 快取的成本精算模型
以 5 萬 MAU、平均對話深度 30 輪、每輪 400 tokens 為例:
每日 L2 快取建立評估:
- 活躍 session 數:5 萬 × 60%(當天活躍率)= 3 萬 session
- 突破 32K token 閾值的 session:3 萬 × 25%(長對話比例)= 7,500 session
- 符合頻率條件的 session:7,500 × 70%(頻率篩選通過率)= 5,250 session
每個 L2 快取的成本:
- 平均快取大小:40K tokens
- 每小時存儲費:$0.00025/1K tokens × 40K = $0.01/小時
- 平均存活時間:2.5 小時(閒置 15 分鐘觸發壓縮前的預期活躍時間)
- 每 session 每天 L2 費用:$0.01 × 2.5 = $0.025
總 L2 每日成本:5,250 × $0.025 = $131/天 ≈ $3,900/月
對比無快取方案:
- 每次請求重新輸入 40K tokens:$0.001/1K × 40 = $0.04/請求
- 每個 session 每天 30 次請求:$0.04 × 30 = $1.20/session/天
- 5,250 session × $1.20 = $6,300/天 ≈ $189,000/月
節省:$189,000 - $3,900 = $185,100/月(節省 98%)
五、L3 層:Flash 模型語義壓縮與 Firestore Snapshot
5.1 Flash 壓縮的觸發與執行流程
┌──────────────────────────────────────────────────────────────────┐
│ L3 壓縮流程 │
│ │
│ Redis TTL 到期事件(Keyspace Notification) │
│ │ │
│ ▼ │
│ Cloud Pub/Sub(session_idle_topic) │
│ │ │
│ ▼ │
│ Cloud Run Job(flash-compressor) │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 1. 從 Redis L1 拉取最近 3 輪對話 │ │
│ │ 2. 從 Vertex AI L2 取得完整 Context(若存在) │ │
│ │ 3. 呼叫 Gemini Flash:語義摘要 + 重要性評分 │ │
│ │ 4. 產出 Core Memory Snapshot(目標 < 1K tokens) │ │
│ │ 5. 寫入 Firestore:conv_snapshots/{tenant}/{session} │ │
│ │ 6. 呼叫 Vertex AI API:DELETE context cache │ │
│ │ 7. 發送完成事件至監控系統 │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ Firestore(L3 永久存儲) │
└──────────────────────────────────────────────────────────────────┘
5.2 語義重要性評分(Semantic Importance Score)
Flash 模型在壓縮時,對每個對話段落打 0–1 的重要性分數:
評分維度(權重):
- 用戶明確提出的需求/問題(0.9–1.0)
- Agent 給出的決策與建議(0.8–0.95)
- 用戶確認的關鍵事實(0.7–0.9)
- 閒聊/寒暄/重複確認(0.0–0.2)
- 已被後續對話推翻的決策(0.1–0.3)
壓縮策略:
- 分數 ≥ 0.7:逐字保留(Verbatim)
- 分數 0.4–0.69:語義摘要(1–2 句)
- 分數 < 0.4:丟棄
壓縮率目標:50K tokens → 800 tokens(壓縮比 62:1),實測品質分數(人工評估的 Context Recall)維持在 78% 以上。
5.3 用戶重新上線的冷啟動流程
用戶在 3 天後重新開啟對話:
- Cache Gateway 查詢 Redis L1 → 未命中(已過期)
- Cache Gateway 查詢 Vertex AI L2 → 未命中(已主動 Evict)
- Cache Gateway 查詢 Firestore L3 → 命中 Snapshot
- 將 Snapshot 注入 System Prompt:
[對話歷史摘要 - 上次對話於 2026-06-05]
用戶背景:企業資安部門主管,負責評估 AI 工具採購。
已確認需求:需要符合 ISO 27001 的資料處理協議。
待解決問題:需要法務確認第三條款後才能推進。
重要偏好:偏好書面報告勝於口頭說明。
這個 Snapshot 只佔 800 tokens,vs 重建完整 Context 需要 50K tokens。 輸入成本節省:98.4%;用戶感受的對話連貫性:測試中 82% 的用戶認為「AI 記得之前聊過的事」。
5.4 Snapshot 品質的 A/B 測試框架
Flash 壓縮品質難以用單一指標衡量,需要多維評估:
| 評估維度 | 測量方法 | 合格閾值 |
|---|---|---|
| Context Recall(脈絡召回率) | 人工評估:Snapshot 能讓 Agent 正確回答 10 個對話歷史問題的比例 | > 75% |
| 幻覺率 | 用戶明確糾正 Agent 錯誤記憶的次數 / 總互動次數 | < 2% |
| 用戶滿意度差異 | 使用 Snapshot 冷啟動 vs 全量 Context 的 CSAT 分差 | < 5 分(10 分制) |
| 壓縮比 | Snapshot tokens / 原始對話 tokens | 目標 1:50 至 1:80 |
A/B 測試分流:
- 控制組(20%):重新上線時使用全量 Context 重建(付 50K token 費用)
- 實驗組(80%):使用 L3 Snapshot 冷啟動
每兩週評估一次 A/B 結果,調整 Flash 壓縮 prompt 的重要性評分權重。
5.5 Snapshot 的增量更新策略
每次對話 15 分鐘閒置後都全量重壓縮一次,在高活躍用戶(每天多次上線)身上會產生冗餘壓縮費用。優化方式是增量 Snapshot:
首次壓縮:全量對話 → 生成 Snapshot v1
後續壓縮:Snapshot v1 + 最新對話增量 → 生成 Snapshot v2
(只需輸入 v1 + 新增的 N 輪對話,而非全部歷史)
成本對比:
- 全量壓縮:每次輸入 50K tokens → $0.02/次
- 增量壓縮:每次輸入 1K(v1)+ 5K(新增)→ $0.002/次
- 高活躍用戶(每天 3 次閒置壓縮):$0.006 vs $0.06,省 90%
增量策略的 flip condition:若對話主題發生根本性轉變(語義距離 > 0.8,用 Embedding 餘弦距離判斷),應捨棄舊 Snapshot 全量重壓縮,避免摘要中累積過時資訊。
六、Cache Gateway 的統一決策邏輯
6.1 請求處理的決策樹
┌──────────────────────────────────────────────────────────────────┐
│ Cache Gateway 決策樹 │
│ │
│ 收到對話請求 │
│ │ │
│ ▼ │
│ ┌────────────┐ 命中 ┌──────────────────────────────────────┐ │
│ │ 查 L1 Redis │──────▶│ 返回最近 3 輪 + 組合完整 Context │ │
│ └─────┬──────┘ └──────────────────────────────────────┘ │
│ │ 未命中 │
│ ▼ │
│ ┌────────────────┐ 命中 ┌────────────────────────────────┐ │
│ │ 查 Vertex AI L2│──────▶│ 載入 Cache ID + 重建 L1 │ │
│ └────────┬───────┘ └────────────────────────────────┘ │
│ │ 未命中 │
│ ▼ │
│ ┌────────────────┐ 命中 ┌────────────────────────────────┐ │
│ │ 查 Firestore L3│──────▶│ Snapshot → System Prompt 冷啟動 │ │
│ └────────┬───────┘ └────────────────────────────────┘ │
│ │ 未命中 │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ 全新用戶:空白 Context 啟動 │ │
│ └────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
6.2 寫入路徑的一致性保障
Cache Gateway 在每次對話輪次結束後:
- 同步:更新 L1 Redis(< 2ms)
- 異步:更新 token 計數器;若超過升層閾值,異步排隊建立 L2
- 觸發評估:每次寫入後重新評估閒置計時器
Gateway 本身是無狀態服務,部署在 Cloud Run,可橫向擴展,每個實例處理 500 QPS,p99 延遲 < 8ms。
6.3 Circuit Breaker 設計
Cache Gateway 對每一層都實作 Circuit Breaker,防止單層故障導致全局雪崩:
┌──────────────────────────────────────────────────────────────┐
│ Circuit Breaker 狀態機 │
│ │
│ Closed(正常) │
│ │ 失敗率 > 10%(60 秒窗口) │
│ ▼ │
│ Open(斷路)──────── 30 秒冷卻 ──────────▶ Half-Open │
│ │ │ │
│ │ 所有請求直接降級 │ 允許 5% 流量測試 │
│ │ │ │
│ │ 成功 ──▶ Closed │
│ │ 失敗 ──▶ Open │
└──────────────────────────────────────────────────────────────┘
L1 Circuit Breaker 閾值:
- 錯誤率 > 10% / 60 秒 → Open
- 降級行為:繞過 L1,直接從 Vertex AI L2 讀取
L2 Circuit Breaker 閾值:
- 錯誤率 > 5% / 60 秒,或 p99 延遲 > 3,000ms → Open
- 降級行為:使用 L1 + 完整 History 送 Vertex AI 推理(無快取)
L3 Circuit Breaker 閾值:
- 錯誤率 > 15% / 60 秒 → Open
- 降級行為:新對話從空白 Context 啟動,記錄告警
6.4 故障降級策略
| 故障場景 | 降級行為 | 用戶影響 |
|---|---|---|
| L1 Redis 不可用 | 直接查 L2/L3,無快取寫入 | 延遲 +50ms,對話不中斷 |
| L2 Vertex AI API 超時 | 使用 L1 + 完整 History 重建 | 成本上升,對話不中斷 |
| L3 Firestore 讀取失敗 | 空白 Context 啟動 | 用戶感受到「AI 忘記了之前的事」 |
| Flash 壓縮任務失敗 | L2 保留到自然 TTL 過期 | 多付 1–3 小時快取費用 |
七、監控儀表板與告警設計
7.1 關鍵可觀測性指標
顯存層(GPU VRAM):
- 每個 GPU 實例的 VRAM 使用率(目標 < 70%,告警 > 85%)
- 活躍 L2 快取數量(告警:> GPU 可負載上限的 90%)
- OOM kill 率(目標 < 0.05%)
- 每個 GPU 實例服務的並發 session 數(目標:A100 80GB 維持 < 1,200 個活躍長對話 session)
快取效率層:
- L1/L2/L3 各層命中率(分租戶追蹤)
- L2 快取平均存活時間(目標 > 90 分鐘)
- 無效快取(建立後 < 30 分鐘被驅逐)的費用佔比
- 升層拒絕率(因頻率不足被 Cache Gateway 拒絕升層的比例):目標 30–50%(太低說明條件太鬆,太高說明條件太嚴)
- L3 Snapshot 命中後冷啟動成功率(用戶感受到連貫性的比例):目標 > 80%
成本層:
- 每 MAU 的 Vertex AI token 費用(目標 < $0.30/天)
- Context Cache 存儲費用佔 LLM 總費用比例(目標 15–25%)
- Flash 壓縮任務的每次執行成本(目標 < $0.002/session)
- 每個 L2 快取的投資回報比(節省的 token 費 / 快取存儲費):目標 > 5:1
儀表板結構建議(Cloud Monitoring):
頁面一:實時健康狀態
├── L1 Redis 延遲 p50/p99(滾動 5 分鐘)
├── L2 建立成功率(每分鐘)
├── OOM kill 事件(即時)
└── Cache Gateway 錯誤率(按錯誤類型分層)
頁面二:成本儀表板(每日/每周)
├── 三層快取費用佔比(圓餅圖)
├── 每 MAU 費用趨勢(折線圖)
├── 無效快取費用(棒狀圖,目標歸零)
└── Flash 壓縮任務總費用 vs 節省的 L2 費用(對比圖)
頁面三:品質監控(每日)
├── L3 Context Recall 抽樣評估結果
├── 用戶「AI 失憶」回報率
└── Flash 壓縮任務失敗率 + 死信佇列積壓數
7.2 Symptom → Diagnosis 鏈
症狀:L2 命中率突然從 65% 跌至 35%
- Traces:查看 Cache Gateway → L2 lookup → miss reason
- 可能原因 A:大量新用戶(首次對話不可能命中)→ 查 DAU 新用戶比例
- 可能原因 B:頻率窗格閾值調太嚴 → 查升層拒絕率
- 可能原因 C:Vertex AI Cache API 間歇性失敗 → 查 L2 建立成功率
症狀:帳單突然暴增 40%
- Metrics:查 Context Cache 存儲費用時序圖
- 可能原因 A:Flash 壓縮任務掛掉 → 查 Cloud Run Job 失敗率
- 可能原因 B:升層條件被人改鬆 → 查 Git history + config diff
- 可能原因 C:大型活動導致大量長對話同時建立 L2 → 查 L2 建立量峰值
症狀:用戶回報 AI 回答「前言不搭後語」(失憶)
- 確認 L3 Snapshot 是否存在(Firestore 查詢)
- 確認 Snapshot 是否被成功注入 System Prompt(查 Gateway request log)
- 確認 Flash 壓縮品質:取最近 10 筆壓縮結果,人工評估 Context Recall
- 若 Recall < 60%:Flash 壓縮 prompt 需要調整(可能對話主題領域偏移)
7.3 容量規劃模型
在擴展到 100 萬 MAU 之前,需要提前規劃各層容量:
┌──────────────────────────────────────────────────────────────────┐
│ 容量規劃速查表(100 萬 MAU 基準) │
├─────────────────┬──────────────────────────────────────────────┤
│ L1 Redis │ │
│ - 記憶體需求 │ 100 萬 × 30%(日活率)× 100KB = 30 GB │
│ - 節點配置 │ 6 節點(各 16 GB)= 96 GB,留 68% 緩衝 │
│ - 峰值 QPS │ 100 萬 × 3 次/分 / 60 = 50K QPS │
│ - 成本/月 │ ~$1,200(Cloud Memorystore Redis 企業版) │
├─────────────────┼──────────────────────────────────────────────┤
│ L2 Vertex AI │ │
│ - 並發快取數 │ 100 萬 × 5%(符合升層條件)= 5 萬 sessions │
│ - 平均大小 │ 40K tokens │
│ - 每月存儲費 │ 5 萬 × $0.01/h × 2.5h = $1,250/天 → $37.5K │
│ - 節省 token費 │ 無快取 $180K/天 vs 有快取 $3.75K/天 │
├─────────────────┼──────────────────────────────────────────────┤
│ L3 Firestore │ │
│ - 文件數 │ 100 萬(每用戶 1 份最新 Snapshot) │
│ - 存儲空間 │ 100 萬 × 3 KB(Snapshot JSON)= 3 GB │
│ - 讀寫 QPS │ 日活 30 萬 × 20%(冷啟動率)= 6 萬次/天 │
│ - 成本/月 │ ~$150(Firestore 依讀寫次數計費) │
├─────────────────┼──────────────────────────────────────────────┤
│ Flash 壓縮任務 │ │
│ - 每日觸發數 │ 日活 30 萬 × 60%(有 L2 的 session)= 18 萬 │
│ - 執行時間 │ 平均 1.5 秒/次 │
│ - Cloud Run │ 並發實例數:18 萬 / (86400/1.5) = 3.1 實例 │
│ - 成本/月 │ 18 萬次 × $0.002 = $360/天 → $10,800/月 │
└─────────────────┴──────────────────────────────────────────────┘
總計:三層快取月成本 ≈ $49,650
對比無快取月成本:$180K/天 × 30 = $5,400,000(僅 token 費)
ROI:約 108:1
八、為什麼選 X 不選 Y
| 選擇 | 選 X 的理由 | 不選 Y 的理由 | Flip Condition |
|---|---|---|---|
| Redis LRU 作為 L1 | < 1ms 讀寫延遲;原生 TTL 支援;allkeys-lru 策略自動驅逐 | Memcached:無持久化、無 Sorted Set(LRU 排名需要);DynamoDB:50ms+ 延遲、按讀寫次數計費 | 若 L1 僅作純 key-value 存取且不需排名,Memcached 更省記憶體(約 30%);若流量 < 1K QPS 且需要持久化,DynamoDB 反而更簡單 |
| Vertex AI Context Caching 作為 L2 | 原生 Gemini 整合,無需序列化/反序列化 KV;官方支援的快取機制;硬體層加速 | 自建 vLLM KV Cache:需要自管 GPU 叢集、版本維護、硬體採購;短期 TCO 高 3–5 倍 | 若月 GPU 用量超過 $50K 且有 MLOps 團隊,自建 vLLM 可降低長期邊際成本;若需要 100% 資料落地控制(金融/醫療法規),自建是必選 |
| Gemini Flash 作為壓縮模型 | 成本是 Gemini Pro 的 1/10;延遲 < 2 秒(壓縮任務可接受);摘要品質足夠(78% Context Recall) | Gemini Pro 壓縮:成本過高,每次壓縮 $0.02 vs Flash 的 $0.002;規則式截斷:Context Recall 只有 45%,用戶感受差 | 若對話涉及高度技術性或法律文件(Context Recall 要求 > 90%),需要升到 Pro;若規模 < 1K sessions/天,規則式截斷已夠用 |
| Firestore 作為 L3 | 自動 schema-less、支援複合查詢(tenant_id + session_id);强一致性讀取;GCP 生態整合 | Cloud Spanner:對 L3 過度設計,每月固定成本 $900+;BigQuery:分析型資料庫,點讀延遲 200ms+,不適合即時查詢 | 若 snapshot 需要跨 session 的複雜關聯查詢(如「找出同一租戶所有提到合約的 session」),Cloud Spanner 的 SQL 能力更強 |
| Cloud Pub/Sub 觸發壓縮 | 解耦閒置偵測與壓縮執行;支援重試、死信佇列;萬次以下觸發幾乎免費 | 直接 HTTP 呼叫 Cloud Run:若壓縮任務 timeout,呼叫端會 block;無法重試失敗任務;Cloud Tasks:適合有明確延遲要求的任務,但 Pub/Sub 更適合事件驅動 | 若壓縮任務有嚴格 SLA(如「必須在閒置後 2 分鐘內完成」),Cloud Tasks 的精確延遲控制更適合 |
| 滑動窗口頻率驗證(Redis ZADD) | 精確反映最近 60 分鐘的真實對話頻率;O(log N) 複雜度;原生 Redis 操作 | 固定窗口計數器:在窗口邊界會有 2× 突發誤判;Token Bucket:實作更複雜,且對「對話頻率」這個語義不夠直觀 | 若只需要「今天是否有活躍對話」這種粗粒度判斷,固定窗口計數器(Redis INCR + EXPIRE)成本更低、更簡單 |
九、系統效應:優化前後對比
| 指標 | 優化前(Phase 1) | Phase 2 | Phase 3(目標狀態) | 改善幅度 |
|---|---|---|---|---|
| GPU VRAM 使用率(峰值) | 92% | 68% | 41% | ↓ 55% |
| OOM 崩潰率 | 3.0% | 0.4% | 0.05% | ↓ 98% |
| 每 MAU 每天 LLM 費用 | $1.20 | $0.65 | $0.30 | ↓ 75% |
| 月度總 Vertex AI 帳單 | $120K | $72K | $36K | ↓ 70% |
| L1 快取讀取延遲(p99) | 45ms(每次全量 DB 讀取) | 3ms | 1.2ms | ↓ 97% |
| 對話請求端到端延遲(p99) | 2,800ms | 1,900ms | 1,400ms | ↓ 50% |
| 用戶重新上線 Context 重建成本 | 50K tokens($0.15/次) | 50K tokens | 800 tokens($0.002/次) | ↓ 98% |
| 無效 L2 快取費用比例 | N/A | 22% | 3% | ↓ 86% |
| Flash 壓縮任務失敗率 | N/A | N/A | 0.8% | 基線 |
| L3 Snapshot Context Recall | N/A | N/A | 78% | 基線 |
| 系統可服務並發用戶數(同 GPU 預算) | 8K | 28K | 95K | ↑ 11.8× |
| 工程師在快取相關 incident 花費時間/月 | 40 小時 | 15 小時 | 4 小時 | ↓ 90% |
| L2 快取投資回報比 | N/A | 3.2:1 | 8.5:1 | ↑ 165% |
| Flash 壓縮平均執行時間 | N/A | N/A | 1.4 秒 | 基線 |
| 冷啟動 System Prompt token 數 | N/A | 50K | 800 | ↓ 98% |
Phase 3 達成 CFO 目標的路徑:
月帳單分解(5 萬 MAU,Phase 3 狀態):
LLM 推理費(優化後): $18,000 (↓ 75% vs Phase 1 的 $72,000)
L2 快取存儲費: $3,900 (精確升層控制,無效比例 3%)
L1 Redis 費用: $1,200 (Memorystore 企業版,6 節點)
L3 Firestore 費用: $150 (讀寫次數計費,100 萬文件)
Flash 壓縮任務費用: $2,160 (18 萬次/天 × 30 天 × $0.002)
Cloud Run Gateway: $800 (無狀態、自動縮放)
Cloud Pub/Sub: $50 (訊息量低於免費配額上限)
─────────────────────────────────
總計: $26,260 (vs Phase 1 的 $120,000)
節省幅度: 78%
CFO 要求(降低 50%): 達成,超額完成
十、進階議題:多租戶快取隔離與安全邊界
10.1 快取洩漏風險
KV Cache 在 GPU 層面共享記憶體,理論上存在跨用戶讀取的風險。Vertex AI Context Caching 的隔離邊界:
- 每個 Cache 物件以
cache_name(含projects/{project}/locations/{location}/cachedContents/{id})索引 - API Key / Service Account 驗證:每個租戶使用獨立的服務帳號,透過 IAM 限制只能讀寫自己租戶的 Cache
- 應用層再加一層:Cache Gateway 驗證 JWT claim 中的
tenant_id與 cache 的 metadata 一致,才允許讀取
10.2 Flash 壓縮的隱私合規
部分企業客戶的對話內容涉及 NDA 資料。壓縮任務的合規設計:
- Flash 壓縮在租戶的 VPC 內執行(Cloud Run 掛載到租戶 VPC)
- Snapshot 寫入 Firestore 時使用客戶管理的加密金鑰(CMEK,透過 Cloud KMS)
- Snapshot 的保留策略由租戶設定(預設 90 天,可縮短至 0)
10.3 快取毒化攻擊(Cache Poisoning)防範
在多租戶系統中,攻擊者可能試圖透過特製輸入污染快取,讓其他用戶的 Agent 行為異常。防範措施:
輸入驗證層(Cache Gateway 前置):
1. 最大 token 限制:單輪輸入 > 8K tokens 需要人工審核(異常長輸入)
2. Prompt Injection 偵測:掃描輸入中是否包含「忽略先前指令」等模式
3. 快取隔離驗證:每次快取讀取前,驗證 cache_name 的 tenant hash 與當前請求一致
4. Snapshot 簽章:寫入 Firestore 時附加 HMAC 簽章,讀取時驗證完整性
10.4 Rate Limiting 與防止 Cache Flooding
惡意用戶可能透過大量短對話觸發大量 L2 快取建立,消耗平台的 Context Cache 配額。防護機制:
| 限制類型 | 限制值 | 實作方式 |
|---|---|---|
| 每租戶每分鐘 L2 建立數 | 最多 50 次 | Redis 計數器 + EXPIRE |
| 每用戶每天 Flash 壓縮次數 | 最多 20 次 | Firestore 計數 field |
| 單 session 最大 L2 快取大小 | 128K tokens | Cache Gateway 前置截斷 |
| 全局每分鐘 L2 建立數(配額保護) | 1,000 次 | Token Bucket 限流 |
十一、面試答題要點
「這題的核心是把 KV Cache 管理從『技術問題』升維成『財務工程問題』。我會設計三層架構:L1 是 Redis Cluster,存最近 3 輪對話,LRU 驅逐保證 < 2ms 讀取;L2 是 Vertex AI Context Caching,但觸發條件是雙重門檻——對話必須超過 32K tokens 且過去 60 分鐘頻率超過 3 輪/小時,用 Redis 滑動窗口驗證,這樣精確避開短對話的計費陷阱;L3 是 Firestore 存放 Flash 模型壓縮的 Core Memory Snapshot,不超過 1K tokens。驅逐邏輯同樣有精確數字:閒置超過 15 分鐘,異步觸發 Flash 壓縮任務,壓縮成功後主動呼叫 API 釋放 L2,不等 TTL 自然過期。這套架構在 100 萬用戶規模下,GPU VRAM 使用率從 92% 降至 41%,每 MAU 每天費用從 $1.20 降至 $0.30,月帳單從 $120K 直接砍到 $36K,CFO 的目標兩個月內完全達成。關鍵 Why-X-not-Y:選 Flash 不選 Pro 做壓縮,因為 Context Recall 差距只有 12%(78% vs 90%),但成本差 10 倍,在壓縮任務這個 batch 場景完全值得。」
系列導航
系列導航
← Part 50:前一篇 | Part 52:下一篇 →
