模式演化
更改事件結構可能破壞依賴它的資料傳送程式、查詢、儀表板、警示和匯出。Telemetry 接受新的 JSON 欄位,無需預先移轉。但更改現有欄位的型別或含義時,仍需制定計畫,處理使用舊定義的程式碼和已儲存的資料列。
保持現有欄位的含義和型別不變。需要改變其中任意一項時,新增欄位或版本。
相容性矩陣
| 提議的改變 | 攝入相容性 | 查詢相容性 | 推薦方法 |
|---|---|---|---|
| 新增可選欄位 | 通常相容 | 舊行沒有返回值 | 新增、衡量採用情況,然後更新消費者 |
| 新增巢狀物件 | 通常相容 | 舊行沒有巢狀路徑 | 保持每個巢狀路徑的鍵入和穩定 |
| 新增受控狀態值 | 資料型別相容 | 詳盡的過濾器可能會漏掉它 | 發布前更新並測試消費者 |
| 停止傳送可選欄位 | 行可以省略 | 消費者看到缺失值 | 首先棄用並衡量剩餘讀者 |
| 重新命名或移動欄位 | 建立一個不同的欄位 | 老消費者繼續看舊名字 | 雙寫、遷移、然後退出 |
| 將數字更改為字串 | 與既定型別不相容 | 計算不再只有一種型別 | 建立一個新的輸入正確的欄位 |
| 更改單位而不重新命名 | 型別可能仍然匹配 | 結果悄無聲息地變得錯誤 | 新增特定於單位的欄位,例如 _ms |
| 更改事件粒度 | 行仍在攝取 | 計數和連線變得無效 | 發布新的事件名稱或主要版本 |
新增資料在技術上很容易。相容性還取決於每個下游定義,尤其是受控值、單位、行粒度、標識和時間語義。
新增欄位而不破壞消費者
假設api_request_completed已經記錄:
{
"event_id": "evt_api_01",
"route": "/v1/query/:id",
"status_code": 200,
"latency_ms": 184,
"release": "2026.07.2"
}
您想要新增有界故障類別:
{
"event_id": "evt_api_02",
"route": "/v1/query/:id",
"status_code": 503,
"latency_ms": 921,
"release": "2026.07.3",
"error_type": "upstream_unavailable"
}
分階段部署:
- 記錄允許的值、隱私類別、所有者和設定欄位的分支。
- 新增一個成功的夾具,無需
error_type和每個預期的故障類別。 - 釋放生產者,而現有查詢仍然忽略該欄位。
- 按版本和狀態衡量現場覆蓋範圍。
- 僅在足夠的相關行包含儀表板和警示後才更新儀表板和警示。
- 至少在保留的遷移視窗內保持查詢對舊行的容忍。
日誌 API 刪除空值、空物件和空陣列。因此,“不存在”是沒有值的可選欄位的預期儲存狀態。
在依賴領域之前衡量採用率
使用發布版或顯式生產者版本來查詢部分遷移的程式碼:
SELECT
release,
COUNT(*) AS failed_requests,
SUM(CASE WHEN error_type IS NULL THEN 1 ELSE 0 END) AS missing_error_type,
100.0 * SUM(CASE WHEN error_type IS NULL THEN 1 ELSE 0 END)
/ NULLIF(COUNT(*), 0) AS missing_rate_pct
FROM api_request_completed
WHERE status_code >= 500
AND timestamp_utc >= now() - INTERVAL '24 hours'
GROUP BY release
ORDER BY release;
請勿使用 COALESCE(error_type, 'none'),除非“無”是每個舊值和缺失值的預期類別。它可以隱藏損壞的生產者推出。
重新命名、移動或重新定義欄位
將 latency_ms 重新命名為 duration_ms 不是儲存事件資料中的就地重新命名。使用雙寫遷移:
{
"latency_ms": 184,
"duration_ms": 184,
"schema_version": 2
}
在遷移視窗期間,明確優先順序:
SELECT
route,
approx_percentile_cont(
COALESCE(duration_ms, latency_ms),
0.95
) AS p95_duration_ms
FROM api_request_completed
WHERE timestamp_utc >= now() - INTERVAL '7 days'
GROUP BY route;
然後:
- 更新每個儲存的查詢、儀表板、警示、匯出和消費者;
- 驗證當前生產者沒有隻傳送舊欄位;
- 等待約定的相容期;
- 停止寫入舊欄位;
- 記錄保留的舊行的歷史查詢行為。
當查詢必須區分定義時,請使用 schema_version,而不是作為穩定欄位名稱的替代品。當行粒度、結果含義或巢狀結構一起更改時,版本特別有用。
切勿就地更改欄位型別
這種改變是不安全的:
{ "account_id": 8421 }
{ "account_id": "acct_8421" }
第二個生產者與第一個生產者建立的數字 account_id 發生衝突。即使儲存層可以分別表示這兩個值,連線和過濾器也將不再共享一種可靠的型別。
新增新的字串欄位(例如 account_key),僅在您具有經過審查的確定性對映時才回填,並遷移使用者。同樣的規則適用於:
- 作為格式化字串傳送的數字持續時間;
- 布林值替換為
"yes"和"no"; - 時間戳被特定於語言環境的字串替換;
- 一個從物件變為標量的巢狀路徑;
- 識別符號從一種實體型別更改為另一種實體型別。
將狀態值視為模式
將 cancelled 新增到之前記錄為 success 或 failed 的欄位不會更改其字串型別,但仍然會破壞邏輯:
SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END)
該查詢靜默地將 cancelled 視為未失敗。在發出新值之前確定它是否屬於失敗、排除或單獨的結果。在查詢登錄檔中搜尋詳盡的狀態過濾器並首先更新裝置。
驗證推出和回滾
生產前測試代表性事件:
| 夾具 | 它證明了什麼 |
|---|---|
| 舊版本成功 | 現有的行和查詢仍然有效 |
| 新版本成功 | 新增的欄位具有預期的型別 |
| 新版本失敗 | 僅錯誤欄位存在且有界 |
| 缺少可選欄位 | 空處理仍然是有意的 |
| 重試或重複 | 計數保留記錄的顆粒 |
| 回滾生產者 | 較舊的部署可以安全共存 |
部署完成後,按版本比較接受的事件量、必填欄位覆蓋率、受控值分佈以及關鍵查詢結果。僅當舊生產者仍然可以寫入已建立的模式並且新消費者容忍其丟失的欄位時,回滾才是安全的。
歷史資料和回填
圖式演化改變了未來的事件;它不會自動重寫保留的歷史記錄。回填前:
- 定義事實的確切來源和確定性轉換;
- 保留原始事件時間和穩定的識別符號;
- 防止事件或遷移識別符號重複;
- 在有限的時間間隔內測試行計數和聚合總數;
- 記錄哪些日期和版本被重寫;
- 決定儀表板是否應顯示混合或回填歷史記錄。
如果舊資料不能支援新含義,則將其遺漏並顯示覆蓋範圍邊界。發明一個值會產生更清晰的圖表,但分析的可信度較低。
架構更改清單
- 說明當前和提議的行粒、型別、單位和含義。
- 庫存生產者和每個下游查詢、儀表板、警示和匯出。
- 更喜歡加法領域;使用新事件或版本進行粒度更改。
- 新增成功、失敗、缺失、重試和回滾固定裝置。
- 當兩者都必須更改時,請在新寫入器之前部署相容的讀取器。
- 透過發布來衡量採用情況,而不是假設已完成部署。
- 保留記錄的雙讀或雙寫視窗。
- 僅在瞭解使用情況和保留歷史記錄行為後才刪除舊路徑。
繼續使用 事件資料型別與可空性、查詢巢狀JSON、事件模式目錄 和 必填欄位空率配方。