<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Backward-Compatible on Tarragon</title><link>https://tarrragon.github.io/blog/tags/backward-compatible/</link><description>Recent content in Backward-Compatible on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Fri, 19 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/backward-compatible/index.xml" rel="self" type="application/rss+xml"/><item><title>Schema 版本演進策略</title><link>https://tarrragon.github.io/blog/monitoring/02-log-schema/schema-versioning/</link><pubDate>Fri, 19 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/monitoring/02-log-schema/schema-versioning/</guid><description>&lt;p>Schema 版本演進的目標是讓新版 SDK 和舊版 SDK 產生的事件能被同一個 collector 正確處理。核心策略是 backward compatible 的增量變更 — 儘量用「新增選填欄位」代替「修改現有欄位」。&lt;/p>
&lt;h2 id="不需要改版的變更">不需要改版的變更&lt;/h2>
&lt;h3 id="新增選填欄位">新增選填欄位&lt;/h3>
&lt;p>在 data 區域新增欄位。舊版 SDK 送來的事件不包含這個欄位，collector 和查詢工具用「欄位不存在則忽略」的邏輯處理。&lt;/p>
&lt;p>例：v=1 的事件沒有 &lt;code>data.duration_ms&lt;/code>，v=1 的 SDK 升級後開始送 &lt;code>data.duration_ms&lt;/code>。Collector 不需要改 — 新欄位出現在 data 自由區域，不影響 schema 驗證。查詢時用 optional access。&lt;/p>
&lt;h3 id="新增事件名稱">新增事件名稱&lt;/h3>
&lt;p>新功能加入新的事件名稱（&lt;code>enrollment.qr.scan&lt;/code>）。事件名稱不受 schema 版本控制 — schema 定義的是事件的結構，不是事件名稱的清單。&lt;/p>
&lt;h2 id="需要改版的變更">需要改版的變更&lt;/h2>
&lt;h3 id="新增核心必填欄位">新增核心必填欄位&lt;/h3>
&lt;p>在核心區域（type、name、timestamp、source 同層）新增必填欄位。舊版 SDK 不會送這個欄位，collector 需要根據版本號決定是否要求這個欄位。&lt;/p>
&lt;p>例：v=2 新增必填的 &lt;code>environment&lt;/code> 欄位（production / staging / development）。v=1 的事件沒有這個欄位，collector 對 v=1 不要求 environment，對 v=2 要求 environment。&lt;/p>
&lt;h3 id="改變欄位型別">改變欄位型別&lt;/h3>
&lt;p>把 &lt;code>duration&lt;/code> 從 string（&lt;code>&amp;quot;320ms&amp;quot;&lt;/code>）改成 integer（&lt;code>320&lt;/code>）。同一個欄位的兩種型別需要不同的解析邏輯，collector 用版本號區分。&lt;/p>
&lt;h3 id="刪除或重新命名欄位">刪除或重新命名欄位&lt;/h3>
&lt;p>刪除欄位或改名（&lt;code>error_msg&lt;/code> → &lt;code>error_message&lt;/code>）需要改版。Collector 對舊版本讀舊欄位名，對新版本讀新欄位名。&lt;/p>
&lt;h2 id="collector-的多版本支援">Collector 的多版本支援&lt;/h2>
&lt;p>Collector 同時接收不同版本的事件。處理策略：&lt;/p>
&lt;h3 id="版本分派">版本分派&lt;/h3>
&lt;p>收到事件後先讀 v 欄位，分派到對應版本的處理器。每個版本的處理器知道該版本的欄位結構和驗證規則。&lt;/p>
&lt;h3 id="正規化">正規化&lt;/h3>
&lt;p>不同版本的事件正規化成統一的內部格式後儲存。正規化層處理欄位名稱對應（&lt;code>error_msg&lt;/code> → &lt;code>error_message&lt;/code>）和型別轉換（string → integer）。查詢時只面對正規化後的格式。&lt;/p>
&lt;h3 id="版本淘汰">版本淘汰&lt;/h3>
&lt;p>當所有 SDK 都升級到 v=2 後（從事件記錄中確認不再收到 v=1），可以移除 v=1 的處理器。淘汰前確認沒有離線 buffer 中的 v=1 事件尚未送達。&lt;/p>
&lt;h2 id="實務建議">實務建議&lt;/h2>
&lt;p>&lt;strong>遲改版優於早改版&lt;/strong>。每次改版增加 collector 的複雜度（多一個版本的處理器）。如果變更可以用「新增選填欄位」解決，優先選擇不改版。&lt;/p>
&lt;p>&lt;strong>一次改版包含多個變更&lt;/strong>。如果確定要改版，把多個計畫中的 breaking change 合併到同一次版本升級。v=1 → v=2 包含三個 breaking change，比 v=1 → v=2 → v=3 → v=4 各包含一個 breaking change 的維護成本低。&lt;/p>
&lt;p>&lt;strong>Schema 文件和版本號同步&lt;/strong>。每個版本的 schema 有對應的文件，記錄該版本和前一版本的差異。&lt;/p>
&lt;h2 id="下一步路由">下一步路由&lt;/h2>
&lt;ul>
&lt;li>完整欄位定義 → &lt;a href="https://tarrragon.github.io/blog/monitoring/02-log-schema/event-schema-fields/" data-link-title="event.schema.json 完整欄位解說" data-link-desc="監控事件的 JSON Schema 定義 — 每個欄位的語意、必填/選填、資料型別和設計理由">event.schema.json 完整欄位解說&lt;/a>&lt;/li>
&lt;li>欄位設計原則 → &lt;a href="https://tarrragon.github.io/blog/monitoring/02-log-schema/field-design-principles/" data-link-title="欄位設計原則" data-link-desc="source 標明來源、data 自由欄位、v 版本演進 — 三個設計原則讓 schema 在不同階段都能使用">欄位設計原則&lt;/a>&lt;/li>
&lt;li>和 OpenTelemetry 的比較 → &lt;a href="https://tarrragon.github.io/blog/monitoring/02-log-schema/otel-comparison/" data-link-title="跟 OpenTelemetry 的 schema 差異對照" data-link-desc="自架 event schema 和 OTLP 的設計差異 — 為什麼 client-side 監控用簡化 schema、什麼時候切換到 OTLP">跟 OpenTelemetry 的 schema 差異對照&lt;/a>&lt;/li>
&lt;/ul></description><content:encoded><![CDATA[<p>Schema 版本演進的目標是讓新版 SDK 和舊版 SDK 產生的事件能被同一個 collector 正確處理。核心策略是 backward compatible 的增量變更 — 儘量用「新增選填欄位」代替「修改現有欄位」。</p>
<h2 id="不需要改版的變更">不需要改版的變更</h2>
<h3 id="新增選填欄位">新增選填欄位</h3>
<p>在 data 區域新增欄位。舊版 SDK 送來的事件不包含這個欄位，collector 和查詢工具用「欄位不存在則忽略」的邏輯處理。</p>
<p>例：v=1 的事件沒有 <code>data.duration_ms</code>，v=1 的 SDK 升級後開始送 <code>data.duration_ms</code>。Collector 不需要改 — 新欄位出現在 data 自由區域，不影響 schema 驗證。查詢時用 optional access。</p>
<h3 id="新增事件名稱">新增事件名稱</h3>
<p>新功能加入新的事件名稱（<code>enrollment.qr.scan</code>）。事件名稱不受 schema 版本控制 — schema 定義的是事件的結構，不是事件名稱的清單。</p>
<h2 id="需要改版的變更">需要改版的變更</h2>
<h3 id="新增核心必填欄位">新增核心必填欄位</h3>
<p>在核心區域（type、name、timestamp、source 同層）新增必填欄位。舊版 SDK 不會送這個欄位，collector 需要根據版本號決定是否要求這個欄位。</p>
<p>例：v=2 新增必填的 <code>environment</code> 欄位（production / staging / development）。v=1 的事件沒有這個欄位，collector 對 v=1 不要求 environment，對 v=2 要求 environment。</p>
<h3 id="改變欄位型別">改變欄位型別</h3>
<p>把 <code>duration</code> 從 string（<code>&quot;320ms&quot;</code>）改成 integer（<code>320</code>）。同一個欄位的兩種型別需要不同的解析邏輯，collector 用版本號區分。</p>
<h3 id="刪除或重新命名欄位">刪除或重新命名欄位</h3>
<p>刪除欄位或改名（<code>error_msg</code> → <code>error_message</code>）需要改版。Collector 對舊版本讀舊欄位名，對新版本讀新欄位名。</p>
<h2 id="collector-的多版本支援">Collector 的多版本支援</h2>
<p>Collector 同時接收不同版本的事件。處理策略：</p>
<h3 id="版本分派">版本分派</h3>
<p>收到事件後先讀 v 欄位，分派到對應版本的處理器。每個版本的處理器知道該版本的欄位結構和驗證規則。</p>
<h3 id="正規化">正規化</h3>
<p>不同版本的事件正規化成統一的內部格式後儲存。正規化層處理欄位名稱對應（<code>error_msg</code> → <code>error_message</code>）和型別轉換（string → integer）。查詢時只面對正規化後的格式。</p>
<h3 id="版本淘汰">版本淘汰</h3>
<p>當所有 SDK 都升級到 v=2 後（從事件記錄中確認不再收到 v=1），可以移除 v=1 的處理器。淘汰前確認沒有離線 buffer 中的 v=1 事件尚未送達。</p>
<h2 id="實務建議">實務建議</h2>
<p><strong>遲改版優於早改版</strong>。每次改版增加 collector 的複雜度（多一個版本的處理器）。如果變更可以用「新增選填欄位」解決，優先選擇不改版。</p>
<p><strong>一次改版包含多個變更</strong>。如果確定要改版，把多個計畫中的 breaking change 合併到同一次版本升級。v=1 → v=2 包含三個 breaking change，比 v=1 → v=2 → v=3 → v=4 各包含一個 breaking change 的維護成本低。</p>
<p><strong>Schema 文件和版本號同步</strong>。每個版本的 schema 有對應的文件，記錄該版本和前一版本的差異。</p>
<h2 id="下一步路由">下一步路由</h2>
<ul>
<li>完整欄位定義 → <a href="/blog/monitoring/02-log-schema/event-schema-fields/" data-link-title="event.schema.json 完整欄位解說" data-link-desc="監控事件的 JSON Schema 定義 — 每個欄位的語意、必填/選填、資料型別和設計理由">event.schema.json 完整欄位解說</a></li>
<li>欄位設計原則 → <a href="/blog/monitoring/02-log-schema/field-design-principles/" data-link-title="欄位設計原則" data-link-desc="source 標明來源、data 自由欄位、v 版本演進 — 三個設計原則讓 schema 在不同階段都能使用">欄位設計原則</a></li>
<li>和 OpenTelemetry 的比較 → <a href="/blog/monitoring/02-log-schema/otel-comparison/" data-link-title="跟 OpenTelemetry 的 schema 差異對照" data-link-desc="自架 event schema 和 OTLP 的設計差異 — 為什麼 client-side 監控用簡化 schema、什麼時候切換到 OTLP">跟 OpenTelemetry 的 schema 差異對照</a></li>
</ul>
]]></content:encoded></item></channel></rss>