茶室的投影螢幕亮著,滿是密密麻麻的折線圖。
洛基走進來時,大師正背對著門口,雙手抱胸盯著螢幕。有些線條平穩,有些劇烈波動,還有幾條已經變成警告的紅色。
「兩小時前系統上線了,」大師頭也不回,「現在健康嗎?」
洛基走近螢幕,看到一些熟悉的指標名稱:ConsumedReadCapacityUnits
、WriteThrottleEvents
...
「呃...有些紅色的線,」洛基不太確定,「應該有問題?」
大師轉過身:「如果我問你:『系統是否健康』,你會怎麼回答?」
洛基想了想:「看錯誤日誌?如果沒有錯誤,應該就正常。」
「當你看到錯誤日誌時,」大師在白板上畫了一條時間軸,「使用者已經受影響了。」
時間軸:
10:00 系統正常
10:15 容量使用率 85%(沒注意到)
10:20 開始限流(使用者受影響)
10:25 大量錯誤(開始看日誌)← 你在這裡才發現問題
10:30 手動擴容(問題已持續 10 分鐘)
洛基看著時間軸,理解了問題的嚴重性。
大師繼續:「監控的目的,不是『發現問題』,而是『在問題發生前發現徵兆』。」
他在白板上寫下一個詞:「可觀測性(Observability)」
洛基坐下來,翻開筆記本。
大師問:「前面二十七天,我們學了完整的 DynamoDB 技術——設計、查詢、索引、Streams。為什麼現在要學監控?」
洛基思考:「因為...上線後要知道系統狀況?」
「更具體一點,」大師引導,「如果沒有監控,會發生什麼?」
洛基想到剛才的時間軸:「問題發生了,但我不知道。等使用者回報時,已經影響很多人了。」
「還有呢?」
「即使知道有問題,」洛基補充,「也不知道『為什麼』有問題。是容量不夠?還是程式碼錯誤?」
大師點頭:「這就是監控的兩個核心價值。」
他在白板上列出:
監控的核心價值:
1. 提前預警
- 在問題影響使用者『前』發現徵兆
- 容量使用率 80% → 告警 → 擴容 → 避免限流
2. 快速診斷
- 問題發生時,迅速定位根本原因
- 不是「猜測」問題,而是「看數據」找答案
洛基看著這兩點,突然理解為什麼大師一開始問「系統是否健康」——沒有監控,根本無法回答這個問題。
「DynamoDB 有哪些指標需要監控?」洛基問。
大師切換投影螢幕,顯示 CloudWatch 的指標列表,密密麻麻數十個指標。
「太多了,」大師說,「我們聚焦最關鍵的五類。」
他在白板上分類:
類別 1:容量消耗
指標名稱 | 說明 | 為何重要 | 告警閾值 |
---|---|---|---|
ConsumedReadCapacityUnits | 實際消耗的 RCU | 知道讀取流量有多大 | > 配置容量的 80% |
ConsumedWriteCapacityUnits | 實際消耗的 WCU | 知道寫入流量有多大 | > 配置容量的 80% |
洛基皺眉:「為什麼 80% 就要告警?不是還有 20% 的餘裕嗎?」
「因為兩個原因,」大師解釋,「第一,流量可能突然增加。第二,Auto Scaling 需要時間反應——從偵測到擴容完成,可能需要 3-5 分鐘。」
「所以要提前告警,」洛基理解了,「留時間緩衝。」
類別 2:限流事件
指標名稱 | 說明 | 影響/為何重要 | 告警閾值/可能原因 |
---|---|---|---|
ReadThrottleEvents | 讀取被限流的次數 | 使用者請求被拒絕 | > 0(任何限流都應該告警) |
WriteThrottleEvents | 寫入被限流的次數 | 資料寫入失敗 | 容量不足、熱分區、Burst capacity 耗盡 |
「限流是最嚴重的問題,」大師強調,「使用者直接受影響。所以閾值設為 0——只要發生一次,就要立即告警。」
類別 3:錯誤率
洛基想起第 16 天的錯誤處理:「UserErrors
和 SystemErrors
?」
「對,」大師說,「但含義不同。」
指標名稱 | 說明 | 常見原因/影響 | 處理方式 |
---|---|---|---|
UserErrors | 客戶端錯誤(4xx) | ValidationException(參數錯誤)、ConditionalCheckFailedException(條件不符)、ResourceNotFoundException(表不存在) | 通常是程式碼邏輯問題,需要檢查應用層 |
SystemErrors | DynamoDB 內部錯誤(5xx) | AWS 服務問題 | 自動重試,如果持續發生需要聯繫 AWS |
「UserErrors
高,通常是我們的程式碼有問題,」洛基總結,「SystemErrors
高,是 AWS 那邊有問題。」
類別 4:延遲
大師展示延遲指標:
指標名稱 | 說明 | 正常範圍(p99) | 告警條件 |
---|---|---|---|
SuccessfulRequestLatency | 成功請求的延遲(毫秒) | GetItem: < 10ms / Query: < 20ms / Scan: 依資料量而定較高 | p99 延遲持續超過正常範圍 |
「為什麼要看 p99 而不是平均值?」洛基問。
「因為平均值會掩蓋問題,」大師在白板上舉例:
假設 100 個請求:
- 99 個請求:5ms
- 1 個請求:500ms
平均值 = (99×5 + 500) / 100 = 9.95ms ← 看起來正常
p99 = 500ms ← 發現有 1% 的請求異常慢
洛基理解了:「平均值被大多數正常請求拉平了,但 p99 能抓出異常值。」
「這些指標,」洛基看著白板上的列表,「我要一直盯著看嗎?」
大師笑了:「當然不是。這就是為什麼要設定告警。」
他展示告警設定:
告警名稱 | 監控指標 | 閾值 | 評估週期 | 為什麼這樣設定 |
---|---|---|---|---|
讀取容量告警 | ConsumedReadCapacityUnits | > 80% | 連續 2 個 5 分鐘 | 留緩衝給 Auto Scaling |
限流告警 | ReadThrottleEvents | > 0 | 1 個 5 分鐘 | 任何限流都要立即處理 |
系統錯誤告警 | SystemErrors | > 5 | 1 個 5 分鐘 | 偶發可忍受,持續則嚴重 |
延遲告警 | SuccessfulRequestLatency (p99) | > 50ms | 連續 3 個 5 分鐘 | 避免誤報,確認趨勢 |
洛基注意到「連續 2 個」、「連續 3 個」的設定:「為什麼不是一次超過就告警?」
「避免誤報,」大師解釋,「流量可能短暫突增,導致某一分鐘超過閾值,但隨即恢復正常。連續多次超過,才代表真正的問題。」
「但限流是『1 個 5 分鐘』,」洛基指出。
「因為限流影響使用者,」大師說,「寧可誤報,也不要錯過。」
大師切換螢幕,顯示一個新的圖表。
「假設,」他說,「CloudWatch 告警顯示 ReadThrottleEvents
發生了,但你檢查容量使用率,只有 60%。」
洛基皺眉:「60% 就限流?不應該啊...」
他突然想起:「等等,熱分區?」
「正確,」大師說,「總容量使用率低,但某個特定的 Partition Key 流量過大,導致該分區限流。」
「那要怎麼找出是哪個 PK?」洛基問。
大師指向螢幕:「Contributor Insights。」
螢幕上顯示:
Top Contributors (2210-04-20 10:00-11:00)
Partition Key | 請求次數 | 消耗容量 | 限流次數
----------------------------------|---------|---------|----------
EVENT#MARS-SCIENCE-CONF-001 | 8,500 | 850 RCU | 120
EVENT#REGULAR-MEETING-123 | 500 | 50 RCU | 0
EVENT#WORKSHOP-AI-045 | 400 | 40 RCU | 0
...
洛基看著第一行:「火星科學大會...佔了 85% 的請求,而且被限流 120 次!」
「這就是熱分區,」大師說,「單一 PK 流量過大。其他活動的請求都正常,但這個活動的請求被限流了。」
「所以總使用率 60%,但熱分區已經超載,」洛基理解了,「Contributor Insights 能精確找出是哪個 PK 造成問題。」
大師點頭:「找到熱 PK 後,才能決定解決方案——增加容量、分散 PK、加快取...這個我們之後再說。」
「現在,」大師清空白板,「模擬一個實際案例。」
突然,Hippo 的聲音響起:「警告:系統偵測到 WriteThrottleEvents 異常。當前告警數:1。」
投影螢幕自動切換,顯示告警詳情。
洛基愣了一下,然後意識到:「這是即時告警?」
「對,」大師說,「Hippo 整合了 CloudWatch 告警。當指標超過閾值,會主動通知。」
螢幕上顯示:
場景:星際活動報名系統
症狀:
- CloudWatch 告警:WriteThrottleEvents > 0
- 使用者回報:報名時出現「請稍後再試」錯誤
你要怎麼診斷?
洛基拿出筆記本,開始系統化思考。
「首先,」他說,「檢查 ConsumedWriteCapacityUnits
vs ProvisionedWriteCapacityUnits
。」
大師在白板上寫下數據:
ConsumedWriteCapacityUnits: 800 WCU
ProvisionedWriteCapacityUnits: 1000 WCU
使用率:80%
「80%...正好在告警閾值,」洛基分析,「但還沒超過配置容量。如果是容量不足,應該超過 100%。」
「繼續,」大師鼓勵。
洛基想到剛才學的:「會不會是熱分區?查看 Contributor Insights。」
「Hippo,」大師說,「顯示 Contributor Insights 數據。」
「正在查詢...」Hippo 回應,螢幕切換:
Top Contributors (寫入)
Partition Key | 寫入次數 | 限流次數
------------------------------|---------|----------
REGISTRATION#EVENT#HOT-CONF | 3,200 | 85
REGISTRATION#EVENT#NORMAL-001 | 100 | 0
REGISTRATION#EVENT#NORMAL-002 | 90 | 0
「分析完成,」Hippo 補充,「HOT-CONF
佔總寫入流量 91.4%,建議檢查 PK 設計。」
洛基眼睛一亮:「HOT-CONF
這個活動的報名請求,佔了大部分寫入,而且被限流 85 次!」
「原因是什麼?」大師問。
洛基回想 PK 設計:「等等...REGISTRATION#EVENT#HOT-CONF
這個 PK,所有報名同一個活動的請求,都寫入同一個 Partition?」
「對,」大師說,「這是設計問題。熱門活動的報名請求,全部集中到同一個分區。」
洛基立刻想到解決方案:「應該用 USER#{userId}
當 PK,而不是 REGISTRATION#EVENT#{eventId}
。這樣寫入會分散到各個使用者的分區。」
「完全正確,」大師滿意地點頭,「這個案例展示了診斷的完整流程。」
他在白板上總結:
診斷流程:
1. 發現症狀(告警或使用者回報)
↓
2. 檢查總體容量使用率
↓
3. 如果使用率不高但仍限流 → 檢查 Contributor Insights
↓
4. 找出熱 PK
↓
5. 分析為何該 PK 會熱(設計問題?突發流量?)
↓
6. 決定解決方案(重新設計 / 增加容量 / 加快取)
「再來一個案例,」大師換了場景。
場景:活動查詢功能
症狀:
- CloudWatch 告警:SuccessfulRequestLatency (p99) > 200ms
- 使用者回報:列表載入很慢
診斷?
洛基開始系統化思考。
「首先,」他說,「確認是哪種操作慢了——GetItem、Query、還是 Scan?」
大師給出數據:
操作分解:
- GetItem 平均延遲:5ms
- Query 平均延遲:15ms
- Scan 平均延遲:350ms ← 異常
Scan 的使用量:佔總請求的 15%
洛基馬上發現問題:「有人在用 Scan!而且 p99 達 350ms。」
「為什麼會有 Scan?」大師問。
洛基想了想:「可能是...某個查詢沒有使用正確的索引?」
大師展示應用程式碼:
// 問題程式碼
async function searchEventsByTopic(topic) {
// ❌ 錯誤:沒有為 topic 建立 GSI,所以用 Scan
const result = await docClient.scan({
TableName: "Events",
FilterExpression: "topic = :topic",
ExpressionAttributeValues: { ":topic": topic },
});
return result.Items;
}
洛基看出問題:「應該為 topic
建立 GSI,用 Query 而不是 Scan。」
「對,」大師說,「這是常見錯誤——沒有為查詢需求建立對應的索引,只好用 Scan,導致效能低落。Hippo 請準備一份高延犀診斷清單。」
白板立即顯示:
洛基看著白板上密密麻麻的診斷流程和檢查清單。
「這些指標和告警,」他問,「實際運作時,我要怎麼快速掌握系統狀況?」
大師切換螢幕,顯示一個整合儀表板:
DynamoDB 健康儀表板
┌─────────────────────────────────────┐
│ 容量使用率 │
│ ████████░░ 82% (告警) │
│ │
│ 限流事件(過去 1 小時) │
│ ReadThrottle: 12 次 ⚠️ │
│ WriteThrottle: 0 次 ✓ │
│ │
│ 錯誤率(過去 1 小時) │
│ UserErrors: 45 次 │
│ SystemErrors: 0 次 ✓ │
│ │
│ 延遲 (p99) │
│ GetItem: 8ms ✓ │
│ Query: 22ms ⚠️ │
│ Scan: 420ms ⚠️ │
│ │
│ Top 熱門 PK (Contributor Insights) │
│ 1. EVENT#MARS-CONF ████████ 65% │
│ 2. EVENT#WORKSHOP ██ 15% │
│ 3. EVENT#MEETUP ██ 12% │
└─────────────────────────────────────┘
洛基看著儀表板:「一眼就能看出問題——容量 82% 接近告警、有讀取限流、Query 和 Scan 延遲偏高、火星大會佔了 65% 流量。」
「這就是可觀測性的價值,」大師說,「不是等問題發生,而是主動掌握系統健康狀況。」
洛基闔上筆記本。
從進門時看到滿是紅線的儀表板,到現在能夠系統化診斷問題,他經歷了一個思維轉變。
以前,他覺得「會寫程式、會設計表結構」就夠了。現在他理解,技術只是第一步——系統上線後,監控才是真正讓系統「活下去」的關鍵。
「你一開始問我『系統是否健康』,」洛基說,「我答不出來,因為沒有數據。」
「現在呢?」大師問。
洛基指向儀表板:「現在我能回答——容量使用率 82%,接近上限需要擴容;有讀取限流,要檢查熱分區;Query 和 Scan 延遲偏高,要優化查詢設計。」
「這就是監控的意義,」大師說,「把『猜測』變成『數據』,把『被動反應』變成『主動預警』。」
洛基想起開場時的時間軸——沒有監控,問題發生 10 分鐘後才發現;有了監控,問題發生前就能預警。
他突然意識到另一個重點:「監控不只是發現問題,更重要的是『提供診斷的依據』。如果沒有 Contributor Insights,我根本不知道是哪個 PK 造成熱分區。」
大師微笑:「對。監控數據,就是診斷的線索。」
「明天,」他繼續說,「我們會學習容量規劃與成本優化——如何根據監控數據,做出正確的容量配置決策。」
洛基點點頭。他現在理解,技術、設計、監控、成本優化...這些都是環環相扣的。
系統設計不是寫完程式就結束,而是一個持續觀察、診斷、優化的循環。
時間設定說明:故事中使用星際曆(SY210 = 西元 2210 年),程式碼範例為確保正確執行,使用對應的西元年份。