FDE 面試準備指南(三十九):RKK 實戰——從 10,000 到百萬用戶:AI 系統的橫向擴展架構設計

10,000 個內部員工用,一切都很順。
百萬外部用戶第一天上線,系統在 30 分鐘內崩潰。
「加更多機器」不是答案——
正確的問題是:哪些地方讓你根本無法加機器?


面試情境

面試官:「你幫一家金融公司做了內部員工 AI 助手,10,000 個內部用戶,系統很穩定。現在 CEO 決定把這個產品開放給外部客戶,目標是百萬 MAU(月活躍用戶)。你說需要重新設計架構。從哪裡開始?你會做哪些改動?為什麼?」


一、為什麼 10K → 1M 不只是「加機器」

10K 內部用戶的隱性假設(這些假設在 1M 時全部失效):

  用戶行為:
  ├── 行為模式可預測(9-18 點工作時間,流量曲線平滑)
  ├── 用量相對均勻(員工配額相似,不會有人瘋狂濫用)
  └── 系統問題可以容忍(內部用戶有耐心,可以接受偶爾慢)

  系統設計:
  ├── Session State 在記憶體(少數實例,重啟少)
  ├── 認證:單一 LDAP/SSO(一種身份系統就夠)
  ├── 沒有速率限制(員工不會惡意攻擊自家系統)
  └── SLA:P95 < 10s 內部用戶接受

1M 外部用戶:每一個假設都被打破

  假設失效              真實挑戰                         系統症狀
  ──────────────────────────────────────────────────────────────
  行為可預測            病毒式傳播:1 小時內 100x 流量    Auto-Scale 來不及 → 503
  用量均勻              惡意用戶濫用、失控的客戶端 Bug    一個用戶拖垮整個平台
  Session 在記憶體      Scale-Out 後新實例找不到 Session  對話斷掉,用戶流失
  無速率限制            機器人、爬蟲、Bug 迴圈呼叫        LLM 配額耗盡 → 全平台崩潰
  SLA 寬鬆              外部客戶不等待,直接離開           用戶留存率崩潰
  成本不計較            1M × 50 queries × $0.02 = $1M/月  CFO 叫停

三個結構性差距(加機器解決不了):
  差距 1:Stateful 設計 → Auto-Scale 加了機器但 Session 找不到,白加
  差距 2:無速率限制 → 單一惡意用戶可以耗盡整個系統的 LLM 配額
  差距 3:無非同步架構 → 長任務佔滿連接槽,新請求全部 503

二、三個演進階段的完整架構設計

╔══════════════════════════════════════════════════════════════╗
║  Phase 1:< 10K 用戶(POC / 內部試點)                        ║
║  策略:速度優先,接受架構妥協                                  ║
╚══════════════════════════════════════════════════════════════╝

  Client
    │
    ▼
  Load Balancer(Round-robin)
    │
    ▼
  Agent Service(Stateful,Session 存記憶體)
    ├──→ LLM API
    ├──→ Vector DB
    └──→ PostgreSQL(Primary,讀寫混合)

  ✅ 可接受的妥協:
  ├── Session 在記憶體(重啟少,可接受)
  ├── 無速率限制(內部用戶信任)
  └── 單一 DB(流量低,夠用)

  ❌ 擴展瓶頸:
  ├── 加第二台 Agent → Session 在 A,請求打到 B → 對話斷掉
  └── DB 讀寫混合在高峰期互相干擾

  新增元件:零 | 月成本:$300-800

╔══════════════════════════════════════════════════════════════╗
║  Phase 2:10K - 200K 用戶(MVP / 生產試點)                   ║
║  策略:無狀態化 + 讀寫分離 + 基本快取                          ║
╚══════════════════════════════════════════════════════════════╝

  Client
    │
    ▼
  Load Balancer(Stateless,任意請求 → 任意實例)
    │
    ▼
  Agent Pool(無狀態,Auto-Scale on CPU + Concurrency)
    ├──→ Redis(Session + Tool 結果快取,TTL 管理)
    ├──→ LLM API(+ Exact-match Response Cache)
    ├──→ Vector DB
    └──→ PostgreSQL
          ├── Primary(寫入)
          └── Read Replica × 2(查詢,稽核,讀取)

  ✅ 解決的問題:
  ├── Scale-Out 有效(Session 在 Redis,哪個實例都能讀)
  └── DB 讀寫分離(Read Replica 吸收 80% 讀取壓力)

  ❌ 殘留瓶頸:
  ├── 無語意快取(重複語義查詢仍打 LLM)
  └── 長任務佔連接槽(10s 的任務佔住連接等 LLM)

  新增元件:Redis Cluster、Read Replica × 2
  月成本增量:+$500-1,200

╔══════════════════════════════════════════════════════════════╗
║  Phase 3:200K - 1M+ 用戶(Enterprise Scale)                 ║
║  策略:非同步 + 語意快取 + 多層防護 + 成本控制                  ║
╚══════════════════════════════════════════════════════════════╝

  Client
    │
    ▼
  CDN(靜態資源快取 + 不含個人化的 API 回應快取)
    │
    ▼
  API Gateway(三層 Rate Limiting:Global / Tenant / User)
    │
    ├──→ [同步路徑] 預估 < 8s 的即時請求
    │         │
    │         ▼
    │    ┌─────────────────────────────────────────┐
    │    │  Semantic Cache Layer                    │
    │    │  Query → Embedding → Vector 相似度搜尋   │
    │    │  相似度 > 0.95 → 直接命中,< 100ms 回傳 │
    │    └─────────────────┬───────────────────────┘
    │                      │(未命中)
    │                      ▼
    │    Agent Pool(Auto-scale,基於 Queue Depth + P95)
    │         ├──→ Redis(Multi-layer 快取)
    │         ├──→ LLM API(Token Budget 管理)
    │         └──→ Vector DB(Clustered,Multi-replica)
    │
    └──→ [非同步路徑] 預估 > 8s 的長任務
              │
              ▼
         Task Queue(Pub/Sub)
         └── HTTP 202 立刻回傳 task_id,不佔連接槽
              │
              ▼
         Worker Pool(獨立 Auto-scale,基於 Queue Depth)
              │
              ▼
         Result Store(DB)
              │
              ▼
         通知機制(WebSocket Push 或 Polling Endpoint)

  ✅ 解決所有問題:
  ├── 語意快取命中率 40-60%,LLM 成本降低 40-60%
  ├── 非同步佇列消除長任務的連接槽瓶頸
  └── 三層 Rate Limiting 防惡意用戶

  新增元件:CDN, API Gateway, Semantic Cache, Task Queue, Worker Pool
  月成本增量:+$2,000-5,000(但 LLM 成本降低 40-60%,淨效益正)

三、無狀態服務設計

問題核心:為什麼 Stateful 服務讓 Scale-Out 失效

  Stateful 服務的 Scale-Out 悖論:

  Phase 1 架構(Stateful):

  Round 1:
  User A → LB → Instance A(Session 存在 A)
  User A → LB → Instance A(同一個實例,正常)

  Scale-Out 觸發,新增 Instance B:

  Round 2:
  User A → LB → Instance B(LB 換了實例)
                  → 找不到 Session → 對話重置
  ← 用戶體驗:「剛才說的話全忘了」

  這就是加機器讓體驗更差的原因。

Stateless 服務設計:

  ┌──────────────────────────────────────────────────────────────┐
  │  請求攜帶 session_id(JWT Token 或 Cookie)                   │
  └──────────────────────┬───────────────────────────────────────┘
                         │
                         ▼
  ┌──────────────────────────────────────────────────────────────┐
  │  Load Balancer(Pure Round-robin,任意請求 → 任意實例)        │
  └──────┬────────────────────────────────┬─────────────────────┘
         │                                │
         ▼                                ▼
  Instance A(無任何用戶 State)    Instance B(無任何用戶 State)
         │                                │
         └────────────────┬───────────────┘
                          │  根據 session_id 讀取
                          ▼
                   ┌──────────────┐
                   │  Redis       │
                   │  session_id  │
                   │  → State     │
                   └──────────────┘

  任意實例都能讀取任意用戶的 State
  → Scale-Out 有效
  → Rolling Deploy 不中斷對話
  → 多 Region 部署可共用 Redis Cluster

Session State 分層設計:

  State 類型        儲存位置      TTL       理由
  ──────────────────────────────────────────────────────────────
  Conversation      Redis         24h       高頻讀寫,低延遲
  History
  ──────────────────────────────────────────────────────────────
  User Profile      PostgreSQL    永久       結構化,需要 JOIN 查詢
  ──────────────────────────────────────────────────────────────
  Tool 結果快取      Redis         5-60min   依 Tool 的資料新鮮度
  ──────────────────────────────────────────────────────────────
  Long-running      PostgreSQL    7 天      需要持久化,可以跨 Session
  Task Status
  ──────────────────────────────────────────────────────────────

四、非同步佇列架構

問題:同步架構的連接槽瓶頸

  同步模型的計算:
  10,000 並發 × 8s(平均延遲) = 需要 80,000 個同時持有的 HTTP 連接
  Cloud Run 預設每個實例 concurrency=80 → 需要 1,000 個實例
  LLM 配額 1,000 RPM → 只能支撐 1,000/60 ≈ 17 個並發
  
  瓶頸不在計算資源,在 LLM 配額。加機器沒用。

非同步架構:把「等待」和「計算」分開

  任務分類決策(在 API Gateway 層判斷):

  判斷標準              路徑           理由
  ──────────────────────────────────────────────────────────────
  預估 < 8s             同步           用戶期待即時回應
  預估 8s-60s           非同步 + Push   後台處理 + WebSocket 推送
  預估 > 60s            非同步 + Poll   長任務,用戶可以等
  批次處理               非同步佇列     不需要即時回應

  非同步流程完整設計:

  ┌──────────────────────────────────────────────────────────────┐
  │  用戶提交長任務(「分析這份 100 頁 PDF」)                     │
  └──────────────────────┬───────────────────────────────────────┘
                         │
                         ▼
  ┌──────────────────────────────────────────────────────────────┐
  │  API Gateway                                                  │
  │  1. 生成 task_id(UUID)                                      │
  │  2. 寫入 Task Queue(Pub/Sub)                                │
  │  3. 立刻回傳 HTTP 202 Accepted + task_id                     │
  │  (整個過程 < 50ms,不佔連接槽)                               │
  └──────────────────────┬───────────────────────────────────────┘
                         │
                         ▼
  ┌──────────────────────────────────────────────────────────────┐
  │  Task Queue(訊息持久化,Worker 故障不丟失)                    │
  │  訊息:{task_id, user_id, payload, created_at}               │
  └──────────────────────┬───────────────────────────────────────┘
                         │
                         ▼
  ┌──────────────────────────────────────────────────────────────┐
  │  Worker Pool(獨立 Auto-scale,基於 Queue Depth)              │
  │  ├── Queue Depth > 100 → 擴展 Worker                         │
  │  ├── 執行任務:LLM 呼叫、向量搜尋、結果整合                   │
  │  └── 完成後更新 Task Status = COMPLETED,寫入 Result          │
  └──────────────────────┬───────────────────────────────────────┘
                         │
                         ▼
  ┌──────────────────────────────────────────────────────────────┐
  │  通知機制(兩種模式,依場景選擇)                               │
  │  模式 A:WebSocket Push(適合 Web App)                       │
  │          任務完成 → Server push 通知 → 用戶介面更新            │
  │  模式 B:Polling(適合 Mobile / 簡單場景)                    │
  │          用戶每 5s 呼叫 GET /tasks/{task_id} 查詢狀態          │
  └──────────────────────────────────────────────────────────────┘

Task State Machine:

  PENDING → PROCESSING → COMPLETED
                      → FAILED(支援 Retry,最多 3 次)
                      → TIMEOUT(超過 10 分鐘未完成)

  Task Status 提供給用戶的資訊:
  {
    "task_id": "uuid-xxx",
    "status": "PROCESSING",
    "progress": 0.6,            // 60% 完成
    "estimated_completion": "30s",
    "created_at": "...",
    "updated_at": "..."
  }

五、語意快取策略

為什麼傳統 Key-Value 快取在 AI 系統效果差:

  傳統快取(Exact-match Key-Value):
  Key = hash(query_string)
  「今天天氣如何」→ hash_A → Cache Miss
  「今天的天氣是什麼」→ hash_B → Cache Miss(兩次都打 LLM)

  語意快取(Semantic Cache):
  Key = vector_embedding(query)
  「今天天氣如何」→ vector_A
  「今天的天氣是什麼」→ vector_B
  cosine_similarity(vector_A, vector_B) = 0.97 > 0.95 → Cache Hit ✅

語意快取的完整架構:

  ┌──────────────────────────────────────────────────────────────┐
  │  User Query(自然語言)                                        │
  └──────────────────────┬───────────────────────────────────────┘
                         │
                         ▼
  ┌──────────────────────────────────────────────────────────────┐
  │  Embedding Model(將 Query 向量化,dim=768 或 1536)           │
  │  延遲:10-30ms                                                │
  └──────────────────────┬───────────────────────────────────────┘
                         │
                         ▼
  ┌──────────────────────────────────────────────────────────────┐
  │  向量快取 DB(儲存歷史 Query 的向量 + 對應回應)               │
  │  搜尋:ANN(Approximate Nearest Neighbor)                    │
  │  延遲:5-20ms                                                 │
  │                                                              │
  │  相似度 > 0.95 → Cache Hit → 回傳快取回應(< 60ms 總延遲)    │
  │  相似度 ≤ 0.95 → Cache Miss → 執行完整 LLM 呼叫(2-8s)      │
  │  LLM 呼叫完成後 → 將 (query_vector, response) 存入快取        │
  └──────────────────────────────────────────────────────────────┘

相似度閾值的選擇:

  閾值      Cache Hit 率    風險
  ──────────────────────────────────────────────────────────────
  0.99      低(5%)        低(幾乎完全相同才命中)
  0.95      中(30-50%)    低(語義幾乎相同)        ← 推薦
  0.90      高(60-70%)    中(語義相近但可能不完全同)
  0.85      很高(70%+)    高(可能回傳語義相近但不準確的快取)

三層快取整合效益:

  層次              快取對象                  命中率     節省
  ──────────────────────────────────────────────────────────────
  Semantic Cache    自然語言查詢的語義匹配     30-60%    LLM 呼叫費用
  Redis             Tool 結果(SAP、DB 查詢) 60-70%    外部 API 費用
  CDN               不含個人化的 API 回應      10-20%    伺服器計算費用
  ──────────────────────────────────────────────────────────────
  組合命中率(保守估計):50-65%
  成本影響:LLM 費用 × (1 - 0.55) = 45% 的費用

快取的邊界條件(什麼不應該快取):

  ❌ 不應快取的查詢類型:
  ├── 含有「現在」「今天」「最新」等時效性詞彙
  ├── 個人化查詢(「我的帳戶餘額」)
  └── 工具呼叫結果(高時效性,如庫存、匯率)

  ✅ 應快取的查詢類型:
  ├── 知識類查詢(「什麼是 RAG?」「合約中的違約金條款一般如何計算?」)
  ├── 政策和規則查詢(不常變動的)
  └── 固定格式的報告生成(相同模板,不同時期資料不同)

六、三層速率限制

為什麼要分三層(不只是全域限制):

  只有 Global 限制的問題:
  └── 一個失控的客戶(10,000 RPM)耗盡全域配額
      → 其他所有客戶全部看到 429,但他們沒有做錯任何事

  三層設計的隔離邏輯:

  ┌─────────────────────────────────────────────────────────────┐
  │  Global Rate Limit(500,000 RPM)                           │
  │  保護後端基礎設施和 LLM API 配額                             │
  │  超限:HTTP 503(系統過載,稍後再試)                         │
  └──────────────────────┬──────────────────────────────────────┘
                         │ 通過後進入
                         ▼
  ┌─────────────────────────────────────────────────────────────┐
  │  Tenant Rate Limit(每個企業客戶,依方案配額)                │
  │  Enterprise:50,000 RPM | Standard:10,000 RPM | Free:500 RPM│
  │  超限:HTTP 429 + Retry-After header(告知何時重試)          │
  │  效果:一個客戶失控不影響其他客戶                             │
  └──────────────────────┬──────────────────────────────────────┘
                         │ 通過後進入
                         ▼
  ┌─────────────────────────────────────────────────────────────┐
  │  User Rate Limit(每個終端用戶)                             │
  │  請求次數:100 RPM                                          │
  │  Token 配額:10,000 Tokens/天(與計費掛鉤)                  │
  │  超限:HTTP 429 + 用戶友好訊息(「今日配額已用完,明日重置」) │
  └─────────────────────────────────────────────────────────────┘

Rate Limiting 算法選擇(Token Bucket):

  Token Bucket 的運作:
  ├── 每個 Token 單位時間補充(1 Token/10ms = 100 Token/s)
  ├── 桶的容量 = 允許的突發量(burst = 200 Token)
  ├── 請求消耗 Token(每個請求 1 Token)
  └── 桶空了 → 限流(429)

  允許突發的設計意義:
  └── 用戶在 1 秒內發送 50 個請求是合法的(burst)
      → Token Bucket 吸收突發,不直接拒絕
      長期平均 > 限制才真正限流

  Rate Limit State 的儲存:
  └── Redis(滑動視窗計數器):
      INCR rate_limit:{tenant_id}:{minute_bucket}
      EXPIRE rate_limit:{tenant_id}:{minute_bucket} 120

七、資料庫擴展設計

三個 DB 擴展問題的優先順序:

  問題 1(Phase 2 必解):讀寫混合
  問題 2(Phase 2 必解):連接數耗盡
  問題 3(Phase 3 需解):單一 DB 寫入瓶頸(通常是最後遇到的問題)

讀寫分離架構(Read Replica):

  AI 系統的讀寫比典型值:
  ├── 讀取(查詢歷史對話、查詢用戶設定、讀取 Tool 結果):85%
  └── 寫入(新增對話記錄、更新 Task Status):15%

  設計:

  Agent Service
    │
    ├──→ PostgreSQL Primary(只處理寫入,15% 流量)
    │
    ├──→ Read Replica A(查詢對話歷史,讀取 30%)
    └──→ Read Replica B(查詢用戶設定、稽核,讀取 55%)

  複製延遲(Replication Lag)的影響:
  └── Primary 寫入後,Read Replica 通常 10-100ms 後才同步
      需要考慮:
      ├── 用戶剛更新設定,立刻查詢 → 可能還是舊值
      └── 解法:「寫後讀」(Read-your-writes)的請求強制打 Primary

Connection Pool 設計(PgBouncer):

  問題:
  100 個 Agent 實例 × 50 個 DB 連接 = 5,000 個連接
  PostgreSQL 預設 max_connections = 100 → 直接崩潰

  PgBouncer 解法:

  100 個 Agent 實例
    ↓(每個實例 50 個連接 = 5,000 條連接)
  PgBouncer(Connection Pooler)
    ↓(複用 50 個真實 DB 連接)
  PostgreSQL(只有 50 個連接,在 max_connections 以內)

  PgBouncer 模式選擇:
  ├── Session Mode:一個客戶端連接 = 一個 DB 連接(無複用)
  ├── Transaction Mode:同一 DB 連接在事務間複用 ← AI 系統推薦
  └── Statement Mode:最激進複用(不支援事務)

資料分層儲存架構:

  資料類型              儲存選擇              TTL/Retention
  ──────────────────────────────────────────────────────────────
  Session(熱)          Redis Cluster        24h(自動清理)
  Conversation(溫)     PostgreSQL + Replica  用戶主動刪除前保留
  Vector Index           Vector DB            永久(索引版本管理)
  Task Result(冷)      PostgreSQL           30 天後歸檔
  Audit Log(冷)        Object Storage       7-10 年(合規)
  ──────────────────────────────────────────────────────────────
  設計原則:
  ├── 熱資料(高頻讀寫)→ Redis:延遲 < 5ms,無需 Schema
  ├── 溫資料(需查詢)→ PostgreSQL:支援 SQL 查詢和事務
  └── 冷資料(稽核/歸檔)→ Object Storage:成本 $0.02/GB,不佔 DB 容量

八、Auto-scaling 設計與 Cold Start 消除

AI 系統 Auto-scaling 的特殊挑戰:

  傳統服務:CPU 高 → 擴展(CPU 和工作量正相關)
  AI 服務:等 LLM 回應時,Agent 的 CPU 幾乎是 0%
           但此時連接槽可能已滿,系統實際在瓶頸中

  結論:用 CPU 擴展 AI 服務是錯誤的指標。

Auto-scaling 指標選擇:

  指標              反映的問題              適合場景
  ──────────────────────────────────────────────────────────────
  CPU              計算密集型瓶頸           ❌ 不適合 AI 服務
  ──────────────────────────────────────────────────────────────
  Memory           記憶體洩漏              作為輔助指標
  ──────────────────────────────────────────────────────────────
  Concurrency      每個實例的並發請求數     ✅ AI 系統首選
  (請求並發數)    直接反映連接槽壓力       超過 80% concurrency → 擴展
  ──────────────────────────────────────────────────────────────
  Queue Depth      非同步佇列的積壓量       ✅ Worker Pool 的擴展指標
  ──────────────────────────────────────────────────────────────
  P95 Latency      用戶體驗降級信號         ✅ 搭配 Concurrency 使用

建議配置(Phase 2-3):

  Agent Service(同步路徑):
  ├── min-instances: 2(保持暖機,消除 Cold Start)
  ├── max-instances: 100
  ├── concurrency: 10(每個實例最多 10 個 LLM 並發)
  ├── 擴展觸發:concurrency 使用率 > 70% 持續 60s
  └── 縮減延遲:scale-down-delay = 300s(防止頻繁啟停)

  Worker Pool(非同步路徑):
  ├── min-instances: 0(非高峰期省成本)
  ├── max-instances: 200
  └── 擴展觸發:Queue Depth > 50 持續 30s

Cold Start 問題與解法:

  Cold Start 發生場景:
  Scale-Out 新增實例 → 實例初始化(載入模型、建立連接)→ 8-15s 延遲

  解法比較:

  解法              消除效果    月成本        適合場景
  ──────────────────────────────────────────────────────────────
  min-instances=2   100%        $50-150/月    有 SLA 要求(P95 < 5s)
  ──────────────────────────────────────────────────────────────
  Warmup Ping       90%         $5/月         成本敏感,偶爾慢可接受
  每 10 分鐘一次
  ──────────────────────────────────────────────────────────────
  Predictive Scale  80%         中等           有規律的流量模式
  (提前預置)
  ──────────────────────────────────────────────────────────────

  min-instances 成本分析:
  ├── 2 個實例 × 0.5 vCPU × $0.00002/vCPU-s × 86400s × 30 天 ≈ $50/月
  └── 對比 Cold Start 的業務損失:
      每次 Cold Start 影響 N 個用戶,用戶流失率 × LTV > $50/月
      → min-instances = 2 是合理的投資

九、為什麼選 X 不選 Y:關鍵技術決策

決策 1:Session 儲存 → Redis vs Memcached vs PostgreSQL

  ┌──────────────┬───────────────────────────┬─────────────────────────┐
  │              │  Redis(✅ 選擇)           │  Memcached / PostgreSQL  │
  ├──────────────┼───────────────────────────┼─────────────────────────┤
  │  延遲         │  < 1ms(in-memory)        │  Memcached 相似;        │
  │              │                           │  PostgreSQL 10-50ms     │
  ├──────────────┼───────────────────────────┼─────────────────────────┤
  │  TTL         │  原生支援(EXPIRE 命令)    │  Memcached:有;         │
  │              │                           │  PostgreSQL:需自己管理  │
  ├──────────────┼───────────────────────────┼─────────────────────────┤
  │  資料結構     │  List, Hash, Set 等豐富結構│  Memcached:只有 KV;    │
  │              │                           │  PostgreSQL:完整 SQL    │
  ├──────────────┼───────────────────────────┼─────────────────────────┤
  │  持久化       │  可選(RDB/AOF)           │  Memcached:無;         │
  │              │                           │  PostgreSQL:完整        │
  ├──────────────┼───────────────────────────┼─────────────────────────┤
  │  Cluster     │  Redis Cluster 原生支援    │  Memcached 較複雜        │
  └──────────────┴───────────────────────────┴─────────────────────────┘
  結論:Redis 在低延遲、TTL、豐富資料結構三者兼顧,是 Session 儲存的最佳選擇。

決策 2:快取策略 → Semantic Cache vs Exact-match Cache

  Exact-match Cache:「今天天氣如何」和「今天的天氣是什麼」→ 兩個 Miss
  Semantic Cache:cosine_similarity(v1, v2) = 0.97 → Hit

  適合 Exact-match 的場景:
  └── Query 完全固定(系統生成的查詢,不是用戶輸入)

  適合 Semantic Cache 的場景:
  └── 用戶自然語言輸入(措辭多樣,語義相似)→ AI 系統的主要場景

決策 3:非同步通知 → WebSocket Push vs Polling

  ┌──────────────┬───────────────────────┬─────────────────────────────┐
  │              │  WebSocket Push        │  Polling                    │
  ├──────────────┼───────────────────────┼─────────────────────────────┤
  │  即時性       │  毫秒級(任務完成即推)│  最多 5s 延遲(每 5s 查詢) │
  ├──────────────┼───────────────────────┼─────────────────────────────┤
  │  伺服器負載   │  維持長連接            │  無長連接                   │
  ├──────────────┼───────────────────────┼─────────────────────────────┤
  │  實作複雜度   │  高(需要 WS 伺服器)  │  低(HTTP Endpoint)        │
  ├──────────────┼───────────────────────┼─────────────────────────────┤
  │  適合場景     │  Web App(長期在線)   │  Mobile / 簡單場景          │
  └──────────────┴───────────────────────┴─────────────────────────────┘

決策 4:Rate Limiting 算法 → Token Bucket vs Fixed Window vs Sliding Window

  Fixed Window 的問題:
  └── 23:59:59 發送 100 個請求(本分鐘上限)
      00:00:00 再發送 100 個請求(新分鐘)
      = 1 秒內 200 個請求,繞過了 100 RPM 的限制

  Token Bucket 的優勢:
  ├── 允許合理的突發(burst capacity)
  ├── 長期強制平均速率
  └── 不存在跨窗口邊界的漏洞
  → AI 服務推薦 Token Bucket

決策 5:DB 讀取擴展 → Read Replica vs Sharding

  Sharding 的代價:
  ├── 跨 Shard 查詢複雜(需要 Scatter-Gather)
  ├── 資料不均衡(某些 Shard 過熱)
  └── Schema 變更困難

  什麼時候才需要 Sharding:
  └── 寫入成為瓶頸(Primary DB 的寫入 QPS 達到上限)
      一般 PostgreSQL Primary 可以支撐 10,000+ WPS
      → AI 系統在 1M 用戶以下,通常 Read Replica 足夠
      → Sharding 是更後期的問題

決策 6:Auto-scaling 指標 → Concurrency vs CPU

  AI 服務等待 LLM 時的資源狀態:
  ├── CPU:~5%(等待,不計算)
  ├── Memory:正常
  └── 並發連接數:已滿(每個實例的連接槽被佔用)

  用 CPU 擴展 → CPU 低,不擴展 → 但連接槽滿了 → 新請求排隊 → 延遲爆炸
  用 Concurrency 擴展 → 連接槽 > 70% → 立刻擴展 → 延遲穩定

十、系統效應:擴展設計的量化對比

維度              Phase 1(10K)   Phase 2(100K)   Phase 3(1M)
──────────────────────────────────────────────────────────────────────
LLM 成本/MAU       $4.00           $2.50             $1.50
(快取命中率)      0%快取           30%語意快取        55%組合快取
──────────────────────────────────────────────────────────────────────
P95 延遲           3s              3s                3s
(Cold Start)      偶爾 15s Cold   < 4s(min=2)     < 4s(min=2)
──────────────────────────────────────────────────────────────────────
Scale-Out 效果     ❌ Session 斷掉  ✅ 無狀態,有效    ✅ 全面有效
──────────────────────────────────────────────────────────────────────
惡意用戶影響        拖垮整個系統    影響單一 Tenant    Rate Limit 隔離
──────────────────────────────────────────────────────────────────────
長任務體驗          同步等待/超時   同步等待           非同步 202,不超時
──────────────────────────────────────────────────────────────────────
月架構成本          $500            $1,500            $4,000-6,000
月 LLM 成本(1M MAU)不適用         不適用             $1,500,000 × 45% 省去
                                                      ≈ $825,000 節省
──────────────────────────────────────────────────────────────────────

Phase 3 架構投入的 ROI 計算:
  額外架構成本:~$4,000/月
  LLM 成本節省(55% 快取):~$825,000/月(1M MAU 規模)
  ROI:200x+

  即使在 100K MAU 規模:
  LLM 節省:$82,500 × 30% = $24,750/月
  架構成本:$1,500/月
  ROI:16x

十一、面試答題要點

「這道題的核心不是『加幾台機器』,而是找出哪些架構假設在 10K 時成立、在 1M 時失效——然後系統性地替換它們。

我會用三個演進階段來回答:Phase 1 是 POC,接受有狀態設計;Phase 2 是生產試點,核心改動是無狀態化(Session 移到 Redis)+ 讀寫分離;Phase 3 是規模化,加入語意快取、非同步佇列和三層速率限制。

四個關鍵技術決策:第一,用 Redis 做 Session 儲存而不是 DB,因為 < 1ms 延遲 + 原生 TTL 是 Session 場景的必要條件。第二,語意快取而不是 Exact-match,因為用戶自然語言查詢的字面多樣但語義重複,Exact-match 命中率 < 5%,語意快取可達 40-60%。第三,Token Bucket Rate Limiting 而不是 Fixed Window,因為 Fixed Window 有跨邊界的 2x 漏洞。第四,用 Concurrency 而不是 CPU 作為 Auto-scaling 指標,因為 AI 服務等 LLM 時 CPU 幾乎是 0,但連接槽可能已滿。

成本視角:在 1M MAU 規模,Phase 3 架構每月額外架構成本約 $4,000,但 55% 的快取命中率讓 LLM 成本降低超過 $800,000/月。這個架構投入的 ROI 超過 200x。」


系列導覽:
(三十八)從 POC 到 Production:生產化清單
(四十)AI 系統的 PII 保護:資料脫敏與合規稽核

Yen

Yen

Yen