Cosmos DB 5 Consistency Levels:Session 預設、Bounded staleness、Strong 邊界跟跨 collection 分流策略
Cosmos DB 文件列 5 個 consistency level(Strong / Bounded staleness / Session / Consistent prefix / Eventual)、用 PACELC 講概念、但沒給具體工程判斷標準。這篇整理 Cosmos DB account 預設 level 與 per-request override 的選法:五個 level 各自保證什麼、Session 為什麼是多數互動式產品的預設、同一個 application 內不同操作怎麼分到不同 level;Strong 與 multi-region write 不能並用的理由在 multi-region-write-conflict。
Case anchor 是 Minecraft Earth(用 session consistency 撐 AR 全球同步、5 level 跨 collection 分流)+ ASOS(Black Friday 用較弱 consistency 換 throughput)。
Cosmos DB workload 適配判讀(四層 framing):遷移路徑是保留 + 補周邊、同 DB 換託管還是同 model 換 vendor / RU 思維轉換成本 / multi-model 差異化是否真用上 / 跨雲 hedging vs 單雲 lock-in — 判讀軸詳見 mongodb-api-vs-sql-api 開頭四層 framing。本文聚焦 consistency level 選擇操作層、是 已選 Cosmos DB 後 的 read / write 語義決策;若 workload 不適用 Cosmos DB、level 選擇無法救回 vendor 選錯的取捨。
問題情境
典型觸發場景:team 啟動 Cosmos DB account、setup wizard 問「預設 consistency level」 — 5 個選項、文件講概念、不知道實際業務該選哪個。production 上線後使用者反映「加入購物車後立刻看『我的購物車』讀到舊狀態」、「跨 region 看到玩家瞬移回舊位置」 — debug 發現是 consistency level 沒選對。
讀者徵兆:
- 「Session 跟 Eventual 看起來差不多、為什麼 Session 是預設」
- 「Bounded staleness 的 K 跟 T 該設多少」
- 「Strong 在 multi-region account 為什麼有額外限制」
- 「跨 region read 拿到舊版本、是 consistency 設錯還是 partition key 問題」
真實壓力:
- 購物車場景:加入購物車後立刻看「我的購物車」、結果讀到舊狀態(user 體驗破洞)
- 遊戲場景:玩家位置同步、跨 region 看到「玩家瞬移」回舊位置(遊戲體驗 bug)
- 金融場景:跨服務寫入後立即 read confirm、看不到剛寫的 — 業務邏輯誤判「沒寫進去」、重試 / rollback
consistency level 是 影響 user-facing 行為 的 selection 決策,而不是 config 細節,必須在 selection 階段釐清。
核心機制:5 個 level 的精確語義
Strong
- 機制:read 拿到最新 commit、提供 linearizable read
- 限制:single-write region 限制;multi-region write 不可同時用 Strong(時間敏感 claim、查 最新文件);跨 region 配 Strong 還要付 Cross-Region Quorum 的物理 latency tax(跨洲 100-200ms)
- 適合:金融交易、庫存扣減、status 機器寫後 read confirm
- 為什麼互斥:詳見 multi-region-write-conflict 的 AP 取捨段、本篇不展開
Bounded staleness
- 機制:read 落後 不超過 K 個 version 或 T 秒(取較嚴格者);單 region 內 linearizable、跨 region 有 bounded lag、跟 Freshness Token 是兩種「跨層 read-after-write」協議的選擇(Bounded staleness 由 Cosmos DB 內建、Freshness Token 由 application 自己帶)
- 設定:K(version 上限)+ T(時間上限)兩個參數
- 適合:multi-region 但需要「有 bound 的 staleness 保證」、如 trading system 跨 region read with SLA
Session(預設、最常用)
- 機制:同一 session token 內讀寫一致;session 之外 eventual
- 適合:多數互動式產品的甜蜜點 — 使用者寫入後自己立刻讀得到、其他 session 可接受 eventual
- 為什麼是預設:cost 接近 eventual(不像 Strong 多 2x RU)、體驗接近 Strong(自己讀寫一致)— 是 trade-off 的甜蜜點
Consistent prefix
- 機制:同一筆交易(transactional batch)裡寫入的多份 document 一起可見,讀者不會只看到其中一部分的新版本;單一 document 的寫入在這個 level 下是 eventual
- 適合:多份 document 用同一筆交易寫入、讀取時要看到一致的一組版本的場景
- 風險:常被誤用為 Session 替代、跨 session 一樣 stale、但比 Eventual 多保證 順序
Eventual
- 機制:最便宜、無順序保證
- 適合:完全可 stale + 不需順序的場景(分析、log 聚合、推薦系統)
跟 Cosmos DB account / container 的關係
- account 預設一個 level
- 單一 request 可以 降級(讀更弱 level)、不可升級(讀更強)
- container 層 無法獨立設定 consistency level(時間敏感、查最新文件)— 分流靠 拆成不同 account + per-request override
RU 成本差異
- Strong / Bounded read ≈ 2x Session / Eventual 的 Request Unit
- write 成本不直接受 read level 影響、但 multi-region replication 開銷會(寫入成本隨 region 數倍增:N 個 region 就是單一 region 的 N 倍)
- selection 階段要把 consistency level 當「RU 倍數」進入容量公式、見 ru-cost-model-sizing
跟通用 consistency 卡片的對應
Cosmos DB 是 少數把 5 level 都商品化 的服務、其他系統通常只給 2-3 級(MongoDB read concern majority / local / linearizable、DynamoDB strong / eventual)。對應 consistency-level 卡片的概念分層。
跟 linearizability 的關係:Cosmos DB Strong = single-region linearizable、不是 跨 region external consistency(跟 Spanner 的 TrueTime + Paxos 不同)。這個區別是 selection 階段的常見誤判 — 別把 Cosmos DB Strong 當成 Spanner 替代品。
對應 knowledge cards:consistency-level / linearizability / stale-read。
進階設計策略:同一 application 內不同操作選不同 level
Minecraft Earth 案例的平台特性段揭露「一致性是 spectrum、不是 binary」 — AR 遊戲玩家位置稍 stale OK(用 session / eventual)、庫存交易需要 strong;同一 application 內不同資料配不同 consistency 是進階策略、不一定是 account 一刀切。
container 層無法獨立設定 consistency level(時間敏感、查最新文件)、所以分流靠:
- Account 切分:高一致需求的資料放獨立 account、預設 Strong;低一致需求放另一 account、預設 Session
- Per-request override:account 預設設成需要的最強 level(例如 Bounded staleness)、一般互動讀取降到 Session、批次分析降到 Eventual;用 SDK 的
RequestOptions.ConsistencyLevel
Per-request override 範例(C# SDK)
1// account 預設 Bounded staleness(需要的最強 level)
2// 一般互動讀取降到 Session
3var response = await container.ReadItemAsync<Item>(
4 id: "item-123",
5 partitionKey: new PartitionKey("user-456"),
6 requestOptions: new ItemRequestOptions {
7 ConsistencyLevel = ConsistencyLevel.Session
8 });
9
10// 批次分析、降到 Eventual 換成本
11var queryOptions = new QueryRequestOptions {
12 ConsistencyLevel = ConsistencyLevel.Eventual
13};
14var iterator = container.GetItemQueryIterator<Item>(query, requestOptions: queryOptions);注意 不可升級 的限制:account 預設 Eventual、per-request 不能升 Strong(會 error)。要保留升級彈性、account 預設應該是 最強需要的 level、再 per-request 降級。
跟 partition-key-design 的關係
partition 失衡時即使設 Strong consistency 也看到 throttle、application 看到的是 429 retry 後的高 latency、不是 stale data — consistency level 跟 partition key 共同決定 真實一致性體驗。partition skew 把 Strong 的 SLA 拉到比 Session 還差、見 partition-key-design 的 latency budget 拆解段。
操作流程
account 層設定
1# Portal / ARM template / CLI
2az cosmosdb update --name mycosmos --resource-group myrg \
3 --default-consistency-level Sessionaccount 預設 level 可以隨時修改,但已經在跑的 SDK client 要重新建立(重啟 application)才會用上新的預設;production 切換另外需要 audit 所有 client 的 session 邏輯(特別是 Strong → Session 的降級會讓「跨 session read 變 stale」)。
Request 層 override
SDK 傳 RequestOptions.ConsistencyLevel(C# / Java / Node SDK 行為一致)。注意 只能降級、升級會 reject。
Session token 管理
每次寫入之後,Cosmos DB 在 response 裡回一個更新過的 session token;client 讀取時帶上它,讀到的至少是那次寫入之後的版本。跨 service 共享 token 需要顯式傳遞(不然每個 service 自己一個 session)。
1// 寫入的 response 帶回這次寫入之後的 session token
2var response = await container.UpsertItemAsync<Item>(item, pk);
3var sessionToken = response.Headers["x-ms-session-token"];
4
5// 跨 service 傳遞(如 HTTP header)
6httpClient.DefaultRequestHeaders.Add("X-Cosmos-Session-Token", sessionToken);
7
8// 下游 service 取得 token、用在 SDK request
9var requestOptions = new ItemRequestOptions { SessionToken = sessionToken };
10var downstreamResponse = await container.ReadItemAsync<Item>(id, pk, requestOptions);驗證 level 行為
寫入後立即 read 同 partition key、量 staleness window。用 Cosmos DB Diagnostic Log 看 request 的實際 consistency level;對照 SDK 設定確認沒被預設 override。
Rollback boundary
account 預設可改、但 production 切換 level 需要 audit 所有 client 的 session 邏輯;container 層無法獨立設定(時間敏感、查最新文件)。
失敗模式
全用 Strong consistency
互動式產品 Session 即足夠、用 Strong 浪費 2x RU + 限制 multi-region write、cost 暴漲且 multi-region 配置受限。徵兆是「RU consumption 明顯偏高、且 multi-region write 開不起來」 — 才發現預設選 Strong。
修:
- 盤點業務需求、絕大多數讀寫場景 Session 就夠
- 把需要 Strong 的少數 collection 拆獨立 account、其他 default Session
- 計算 cost:Session vs Strong 在多數 workload 差距 1.5-2x、長期成本顯著
Session token 沒回傳
read 後拿 token、下次 read 沒帶、實際變 Eventual;徵兆是「自己的寫立刻 read 看不到」、debug 才發現 SDK 設定漏。SDK 預設會自動管理 session token、但跨 service 傳遞時容易漏。
修:
- 同一 service 內用 SDK 預設行為、不要關 session token cache
- 跨 service 通信時把 session token 隨 HTTP header 傳遞
- 或改 account 層 Bounded staleness(提供跨 session 的 K/T bound、不依賴 token)
跨 service 共享 session 假設
service A 寫、service B 讀、B 沒拿到 A 的 session token → 看不到 A 的寫。常見場景:order service 寫訂單、notification service 立刻 read 訂單寄通知 — notification 沒拿到 order 的 token、讀到舊狀態(或讀不到)。
修:
- service A 寫完、把 session token 進 message(Kafka event / HTTP response)傳給 B
- B 用 token 做 read、保證讀到 A 的寫
- 或業務上接受 eventual、design notification 有 retry / reconcile 機制
Bounded staleness 設太鬆
K 與 T 設得遠大於業務能接受的延遲時,Bounded staleness 的上限永遠碰不到,行為跟 Session 以下的 level 分不出來,team 卻以為自己有保護。K/T 有平台下限:single-region account 最小是 10 次寫入或 5 秒,multi-region account 最小是 100,000 次寫入或 300 秒(時間敏感、查最新文件)。
修:
- 根據業務 read-after-write SLA 設 T,並先確認這個 SLA 高於 account 的 T 下限;multi-region account 要求 300 秒以內的 read-after-write 時,Bounded staleness 給不了,改用 Session token 或 Strong
- K 通常設成「peak QPS × T」的合理倍數
- 量測:production 觀察實際 staleness 分布、調整 K/T
multi-region write 配 Strong
文件不允許 / 行為退化(時間敏感、查最新)— 必須改 Bounded / Session。這是 AP 取捨的硬約束、不是 config 問題;詳見 multi-region-write-conflict 的 AP 取捨段。
修:在 selection 階段就決定「要 active-active write 還是要 Strong」、不能事後補;要全球 linearizable 轉 Spanner / Aurora DSQL、要 active-active 接受 eventual / session / bounded。
Consistent prefix 誤用
把它當 Session 用、跨 session read 還是 stale;它比 Eventual 多的保證只限於同一筆交易寫入的多份 document 一起可見。常見誤判:「我要『順序對』、所以選 Consistent prefix」 — 但實際業務需求是「自己讀到自己寫的」、應該是 Session 而非 Consistent prefix。
修:
- Consistent prefix 適合 時序敏感但可跨 session stale 場景(新聞 feed、event log)
- 「自己讀到自己寫的」場景用 Session
- 跨 session 也要強一致用 Bounded / Strong
容量與觀測
- 必看 metric:
NormalizedRUConsumption、TotalRequestUnits、ReplicationLatency(跨 region lag) - Diagnostic Log:每個 request 的實際 consistency level、確認沒被預設 override
- 成本計算:Strong / Bounded read 算 2x RU;multi-region 開後寫入成本 × region 數;level 跟 region 數的 cost matrix 是規劃必算
- 回 9.6 容量規劃模型:consistency level 當「RU 倍數」進入容量公式
- Alert:
ReplicationLatency突增(跨 region 同步異常)- Diagnostic log 偵測 Strong read 突增(成本失控)
- 跨 service session token 缺失導致 stale read 比例上升
邊界與整合
- 同 vendor 的其他文章:partition-key-design(partition key 跟 consistency 共同決定真實一致性體驗)、ru-cost-model-sizing(RU 倍數量化)、multi-region-write-conflict(multi-region 下 consistency 的特殊行為、Strong 與 multi-region write 為什麼不能並用)、mongodb-api-vs-sql-api(MongoDB read concern → Cosmos DB consistency level 對應)
- 跟 Spanner vendor 對比:external consistency vs Cosmos DB Strong 不是同一個 thing
- 跟 DynamoDB vendor 對比:DynamoDB 只 strong / eventual 兩級、Cosmos DB 5 級提供細粒度
- 主章節:1.11 全球分散式 OLTP(Cosmos DB 5 level 跟 Spanner external consistency 並陳)
- Knowledge cards:consistency-level / linearizability / stale-read
- Anti-recommendation:別把 Cosmos DB Strong 跟 Spanner external consistency 等同視之;產品需要真正全球 linearizable transaction 時、Cosmos DB 不是替代品 — 轉 Spanner / Aurora DSQL
相關連結
- Cosmos DB vendor overview — Cosmos DB 其他深度文章的列表
- Minecraft Earth case — session consistency + 跨 collection 分流主案例
- ASOS case — 高 throughput + 較弱 level 補充
- multi-region-write-conflict — Strong 與 multi-region write 為什麼不能並用、multi-region write 的 conflict 怎麼解
- Consistency Level 卡片 / Linearizability 卡片 / Stale Read 卡片 — 概念基底
- 官方:Cosmos DB consistency levels / Consistency level overrides
#backend #database #cosmosdb #consistency #session-token #deep-article