本文是 CockroachDB vendor overview 的 implementation-layer deep article。Overview 已界定 CockroachDB 在 distributed SQL 譜系的定位、本文聚焦 HLC + Raft + range + leaseholder 四層機制 — 解釋為什麼 distributed SQL 的 latency / 容量曲線跟 PostgreSQL single-primary 完全不同、以及怎麼從 production 訊號倒推它對團隊的成本結構。寫作參照 vendor deep article methodology


為什麼這篇先講 HLC + Raft

團隊評估 CockroachDB 替代 PostgreSQL streaming replication 時、會同時看到兩個訊號:「跨 region 強一致」很吸引人、「每次寫都經過 Raft majority」又讓人害怕。前者是賣點、後者是成本結構 — 不先把 HLC / Raft / range / leaseholder 拆清楚、後面講 survival goal、locality、transaction retry 都會卡在「為什麼這個機制存在」這層。

讀者最常問的三題:

  • Spanner 用 TrueTime 原子鐘做線性化、CockroachDB 沒硬體時鐘怎麼保證 ordering?
  • Raft 每次寫要等 majority ack、不是比 PostgreSQL 慢得多?
  • HLC clock skew 超出容忍區間時會發生什麼?節點隨機 panic 嗎?

三題都不只是 spec 問題、而是 production 容量規劃跟 incident 訊號的根本前置

問題情境最常見的 trigger:9.C39 DoorDash 在 2020-04-17 高峰 Aurora Postgres 撞到 1.636 M QPS、multi-hour outage。這個數字是 Aurora 在那個時間點撞牆的痛點、case 自己警示「不是 CockroachDB 撐到 1.636 M QPS 的 throughput claim」。case 沒揭露遷移後單一 CockroachDB cluster 的峰值、只說「跑更多 cluster、alert volume 反而下降」。要把 CockroachDB 當寫入容量解法評估、就得先理解 Raft per range 怎麼把寫入從 single-primary 分散到多 node。

9.C40 Netflix 則提供另一條訊號:380+ cluster / 60+ multi-region、最大單區 cluster 60 nodes / 26.5 TB。這個規模證明 Raft 維運在 production 可承擔、但也揭露容量規劃顆粒不是「全公司一條容量曲線」、是「每 cluster 各自規劃」— artery of small DBs。

核心機制:HLC + Raft + range + leaseholder 四層

CockroachDB 的線性化保證來自四層機制疊加、缺一層都解釋不通實際 latency / failure 行為。

HLC:軟體時鐘把 wall clock + logical counter 混在一起

Hybrid Logical Clock 結合 physical time(NTP 同步的牆鐘)跟 logical counter(單調遞增的事件序號)、給每個事件一個 (physical, logical) timestamp。對比 Spanner TrueTime 直接靠 GPS + atomic clock 給「時鐘 uncertainty bound」、CockroachDB HLC 不依賴硬體、用軟體保證「節點之間時鐘最多差 max-offset(default 500ms)、超過就 panic」。

1Node A 收到 write at wall=12:00:00.123, last_seen=12:00:00.100
2  → HLC = (12:00:00.123, 0)
3
4Node A 收到 RPC from B at wall=12:00:00.140, B.HLC=(12:00:00.200, 5)
5  → A 跳到 B 的 physical (12:00:00.200)、logical = 6
6  → HLC = (12:00:00.200, 6)

HLC 的契約 只要節點間時鐘差不超過 max-offset、所有 transaction 仍是 linearizable。production 必跑 NTP / chronyd — 一旦本機時鐘飄超過 500ms、節點自動 panic 保護 cluster 一致性、不會發出錯誤 commit。

跟 Spanner TrueTime 對比:

維度CockroachDB HLCSpanner TrueTime
硬體依賴無(純軟體 + NTP)GPS + atomic clock(每資料中心配)
Uncertainty由 max-offset 上界、固定 500ms動態 uncertainty interval(通常 < 7ms)
Commit 等待不需要 wait out uncertainty需要 wait out(commit-wait)
部署彈性任何雲 / on-prem 都可跑只在有 TrueTime infra 的 GCP region

兩條路徑解同一個 event ordering 問題、用不同 trade-off。CockroachDB 把硬體成本換成軟體 max-offset 容忍度、結果是「可以跨雲跨 on-prem 跑、但 NTP 維運是必要條件」。

Raft:每個 range 一個獨立的 majority consensus group

Raft 把寫入流程切成 propose → replicate to majority → commit 三段。每個 range 維護自己的 Raft group、預設 3 replica、寫入要至少 2 個 replica ack 才能 commit。

1Client → Leaseholder (Raft leader)
2   1. Propose log entry (write intent)
3   2. Replicate to 2 follower replicas
4   3. Wait for majority ack (本身 + 1 個 follower)
5   4. Commit、apply to state machine
6   5. Reply to client

關鍵差異跟 PostgreSQL streaming replication 比:

  • PostgreSQL primary:1 個節點 ack 就 commit(async replication)、replica 可能落後
  • PostgreSQL sync replication:1 個 standby ack 才 commit、但仍是「primary 是 single point of write」
  • CockroachDB Raft:majority(2 of 3)ack 才 commit、任何 replica 都可以是 leaseholder、寫入分散到所有節點

寫入 latency 因此 結構性 高於 PostgreSQL — 多了一次 cross-node round trip。但寫入 吞吐 可以線性擴展、因為不同 range 的 Raft group 跑在不同節點上。

Range:把 key space 切成 ~512 MB 的可分裂單位

CockroachDB 用 Range Sharding 把整個 key space 切成 range、每個 range 預設上限 ~512 MB、超過自動 split。每個 range 是一個獨立的 Raft group、有自己的 3 replica 分佈。

對比其他 distributed DB 的等價概念:

  • DynamoDB partition:固定 hash 分區、自動 split 但 hot partition 容易撞 ceiling
  • Spanner split:類似 range、但配置 / placement 語法不同
  • Vitess keyspace:application 端決定 shard key、不透明 split

CockroachDB range 是 系統內建透明 的 — application 只看到 SQL table、不需要 shard key 設計。但 hot range 仍會發生(後面 failure mode 段展開)。

Leaseholder:每個 range 的 read / write entry point

每個 range 在任一時間點有一個 Leaseholder(通常等於 Raft leader)、承擔該 range 的所有 read / write coordination。leaseholder 也是 follower read 的 timestamp 邊界 holder。

leaseholder 概念對 production 訊號的影響:

  • 寫入 latency 主要來自 leaseholder → follower replicas 的 Raft round trip
  • leaseholder 集中在某節點 → 該節點 CPU 飽和(hot range 的根因之一)
  • leaseholder 換手(lease transfer)短期 p99 spike — rebalance 期間 / 節點 graceful drain 都會觸發

操作流程:配置 + 驗證 + rollback 邊界

Cluster 起手配置

最小可運行配置是 3 節點(Raft quorum 下界)、production 通常 9 節點以上(3 region × 3 replica)。每個節點啟動時必須帶 locality tag、讓 Raft placement 知道副本怎麼分佈:

1cockroach start --insecure \
2  --locality=region=us-east1,zone=us-east1-a \
3  --max-offset=500ms \
4  --join=node1:26257,node2:26257,node3:26257

--max-offset 是 HLC 容忍上界、超過會 panic — 不要為了「避免 panic」加大這個值、會犧牲 linearizability 保證。

NTP / chronyd 是 必要前置、不是 nice-to-have。production 應該在每個節點配置:

  • NTP server 至少 3 個獨立 source(避免單一 server drift)
  • 監控 chronyc tracking 的 offset、超過 100ms 就應該 alert(遠在 500ms panic 邊界之前)

驗證點

 1-- 看每節點當前 clock offset 跟 cluster 其他節點
 2SELECT node_id, address, offset_min_nanos, offset_max_nanos
 3FROM crdb_internal.gossip_nodes;
 4
 5-- 看 Raft 健康(每個 range 的 leaseholder 跟 replica 分佈)
 6SELECT range_id, lease_holder, replicas
 7FROM crdb_internal.ranges
 8WHERE table_name = 'orders'
 9LIMIT 5;
10
11-- 看 cluster max-offset 設定
12SHOW CLUSTER SETTING server.clock.persist_upper_bound_interval;

Rollback 邊界

HLC + Raft 對 rollback 的態度跟 PostgreSQL 不同:

  • HLC 時鐘前進不可回滾 — 不能「改一下 max-offset 後重啟試試看」
  • Raft commit 不可回滾 — 一旦 majority ack、log entry 持久化
  • 想還原業務狀態 只能新交易補償、不能 reverse Raft log

實務上的影響:incident 時不要嘗試「強制回到舊版本」、應該走 transaction-level rollback / compensation。對應 transaction boundary 卡 跟業務層補償設計。

失敗模式:clock skew / majority lost / hot range / retry storm

Clock skew panic

最常見:NTP 服務掛、節點時鐘漂移超過 max-offset、節點自動 panic。production incident 訊號:

  • chronyc tracking 顯示 offset 持續成長
  • CockroachDB log 出現 clock synchronization error
  • Prometheus metric clock_offset_meannanos 接近 max-offset

修法:先恢復 NTP service、節點重啟前再次驗證時鐘已同步、不要動 --max-offset。對比 PostgreSQL primary 不關心 time skew、distributed SQL 把時鐘變成 first-class operational concern。

Raft majority lost

3 節點 cluster 失去 2 個、剩 1 個無法 commit、cluster 全 read-only(甚至連 read 都可能受影響、因為 leaseholder 拿不到 valid lease)。對比 PostgreSQL primary 失效後 streaming replica 仍可 read、CockroachDB 的 fault tolerance 是 quorum-based、不是 primary-replica

production 規劃要點:跨 AZ / region 分佈時、必須保證任何 單一 failure domain 失敗後仍有 majority 存活。3 節點配 1 AZ → AZ 失敗 = cluster down。最小 production 配置是 3 AZ × 1 node 或 3 region × 3 node。

Hot range:leaseholder 節點 CPU 飽和

某個 range 寫流量集中(例:訂單 table primary key 是時間序 / 自增 ID)、leaseholder 節點變成熱點。徵兆:

  • CockroachDB Console「Leaseholder count per node」分佈不均
  • 某節點 CPU 飽和、其他節點閒置
  • crdb_internal.ranges 顯示該 range 的 QPS 遠高於其他 range

修法:

  • 手動 ALTER TABLE ... SPLIT AT VALUES (...) 強制 split hot range
  • 改 primary key 設計、避免時間序 / 自增 ID(用 UUID / hash-prefixed key)
  • partition by region、把 hot range 切到不同 region 的 leaseholder

Transaction retry storm

serializable contention 嚴重時 application 端 retry loop、CPU 雪崩。這個議題的 application contract 重塑屬獨立議題、見 transaction retry pattern

Range split / rebalance 期間 p99 spike

自動 split 大 range、leaseholder 換手期間有 ~100ms 的 lease transfer 視窗、p99 短期 spike。production 訊號:CockroachDB Console「Rebalance queue size」非零 + p99 latency 同期波動。一般是良性 — rebalance 完就回穩。但連續波動代表 range 在「split → 寫熱 → 再 split」循環、要從 schema 層解。

容量與觀測:per-cluster 顆粒 + 來源分層

必看 metric

  • Raft log queue size:Raft replication 延遲訊號、持續高代表 follower 跟不上
  • Range count per node:range 分佈是否均勻、不均代表 placement 有偏
  • Leaseholder count per node:leaseholder 分佈是否均勻、不均直接導致 CPU 熱點
  • HLC offset distribution:時鐘同步健康
  • Transaction retry rate:contention 訊號(細節在 transaction retry pattern

Per-cluster 容量規劃顆粒(9.C40 Netflix 揭露、F4.7)

Netflix 的 380+ cluster 模型揭露一個反直覺結論:production scale 不是「全公司一條容量曲線」、而是 artery of small DBs。每個 cluster 對應一個 application boundary、cluster sizing 從幾個 node 到 60 nodes 不等、最大單區 60 nodes / 26.5 TB(case 觀察段表格揭露)。

容量規劃顆粒對齊 application boundary 的好處:

  • 每個 cluster 各自規劃 capacity、不必預測「全公司加總 QPS」
  • blast radius 限縮在單一 app — 某 cluster 撞 hot range / Raft majority lost、其他 cluster 不受影響
  • upgrade / backup 可分批跑、不必整廠 maintenance window

但也帶來 ops 成本:380+ cluster 需要 專屬 Database Platform Team(含 backup、upgrade、incident response、capacity review)— Netflix case 直接揭露這個前置條件。沒這量級團隊就走 Cockroach Cloud managed、不要 self-host。

per-app cluster vs shared cluster 的決策軸主寫於 aurora-dsql-spanner-decision-tree、本篇 cross-link 不展開。

寫入 latency 預算(屬通用工程估算、case 未揭露具體數字)

以下數字屬通用工程估算 / 物理光速下界推導、DoorDash / Netflix / Hard Rock 三個 direct case 都沒揭露單一 cluster p99 latency。引用時必須明示來源層次:

  • single-region 3-replica write p99 3-5ms(通用估算、跨 AZ Raft round trip)
  • multi-region 跨洲 write p99 100-150ms(光速下界 — 跨洲 round trip 物理 ~70-80ms × 2)
  • 單一 range 寫 throughput ~1000 QPS(通用估算、實際依 row size / contention 而定)
  • 整 cluster scale-out 加 range、寫入吞吐近線性擴展(理論、實際依 hot range 分佈)

這些是「合理的工程估算量級」、不是 case 揭露的 p99 數字。讀者用這些做容量規劃時、應該 自己 benchmark 而不是直接套。

DoorDash 1.636 M QPS 引用紀律(F4.1、case 自帶警示)

DoorDash case 揭露的 1.636 M QPS 是 Aurora Postgres single-primary 在 2020-04-17 高峰撞牆的痛點(multi-hour outage)、不是 CockroachDB throughput claim。case 明確警告不要把這個數字當「CockroachDB 撐 1.636 M QPS 的證據」。case 沒揭露遷移後單一 CockroachDB cluster 的峰值、只說「跑更多 cluster、alert volume 反而下降」。

引用這個數字時的口徑:

  • 寫成「Aurora 撞牆訊號」、不寫成「CockroachDB 容量證明」
  • single-primary 撞牆的轉折點是 primary CPU + WAL flush rate(DoorDash 策略段 1)、不是 IOPS
  • 「換引擎」前先評估「兩階段紓壓」— DoorDash 路徑是先把 hot table 拆到獨立 Aurora cluster(紓壓)、再規劃 Aurora → CockroachDB 換引擎(1.6 database migration playbook

回路徑

邊界與整合

Sibling deep articles

跟 Aurora 對照

Aurora 是 storage-level quorum(4 of 6 storage replica)、compute 仍是 single primary。CockroachDB 是 range-level Raft(每個 range 獨立 majority)、compute 跟 storage 在每節點。兩者解的是不同 layer 的 consensus、結果是 Aurora 寫入仍受 primary 限制、CockroachDB 寫入隨節點線性擴。

Aurora DSQL / Spanner 對比

完整三家 distributed SQL 對比、撞牆訊號分型、PostgreSQL 相容性 audit、團隊規模 vs vendor sizing barrier 等議題在 aurora-dsql-spanner-decision-tree

1.x 章節互引

何時不用本文

  • single-region OLTP + 寫入未撞 PostgreSQL primary 天花板 → PostgreSQL 已足夠
  • 對 cross-region quorum 100-150ms latency 預算無法接受 → 走 async replication 路線
  • 沒 NTP 維運能力 → distributed SQL 把時鐘變 ops concern、沒準備好不要硬上

相關連結