具代表性的面試主題

後端面試:如何無停機把 UUIDv4 遷移到 UUIDv7?

後端困難
Offer.cc 編輯團隊發佈 更新

題幹

多地區服務使用隨機 UUIDv4 主鍵,索引區域性與分頁成本變差。請設計遷移到 PostgreSQL 18 uuidv7() 的漸進方案,不能阻塞寫入或破壞客戶端,並解釋鍵形狀、雙寫、回填、外鍵、排序、複製、驗證與回滾。

題目與範圍

服務有大規模 orders 表、子表、公開 URL、事件載荷與副本。PostgreSQL 18 提供原生 uuidv7() 產生器。請在保持 UUIDv4 合約的同時讓新紀錄逐步使用 UUIDv7,避免表重寫和長時間阻塞鎖。

核心能力是跨 API、儲存與非同步消費者的線上身分與 Schema 遷移,因此歸入 backend

面試官考察什麼

第一,能否區分識別格式與時間真相?UUIDv7 含有時間排序資訊,但不是嚴格全域提交序列,不能取代業務時間戳。

第二,能否維持公開身分?舊 URL、快取、冪等鍵、事件與外鍵在遷移期間都要繼續解析。

第三,能否設計雙讀雙寫階段並確定唯一事實來源?

第四,能否在多副本、多地區環境分批回填,同時控制寫入放大和複製延遲?

第五,能否用實測驗證區域性和延遲,而不是假設 UUIDv7 對所有負載都更快?

先釐清的問題

  • UUID 是否對外暴露、作為主鍵,還是兩者都是?
  • 客戶端能否接受新識別,還是舊 UUID 必須維持規範身分?
  • 有多少子表、索引、事件和搜尋文件引用該鍵?
  • 所有寫入器和副本使用哪些 PostgreSQL 版本與擴充?
  • 寫入速率、複製延遲 SLO 和回滾期限是多少?
  • 排序需求用於分頁、稽核展示,還是只有索引區域性?

30 秒回答框架

「我會保留 UUIDv4 作為穩定公開識別,新增 UUIDv7 映射欄位,先上線雙寫再回填。讀取同時接受兩種鍵,子表引用與事件保持相容。回填使用有界主鍵範圍,監控延遲與鎖;完成一致性檢查後再切換內部索引與查詢路徑。UUIDv7 可以改善區域性和時間範圍掃描,但業務時間仍需顯式保存。全部消費者驗證前,用特性開關支援回滾。」

分步作答

第一步:選擇相容形狀

不要靜默改變現有 UUID 的含義。保留 order_id_v4 作為外部合約,新增 order_id_v7 與唯一約束,或使用獨立內部鍵與持久映射。明確每個階段子表和事件使用哪個鍵。

sql
ALTER TABLE orders ADD COLUMN order_id_v7 uuid;
CREATE UNIQUE INDEX CONCURRENTLY orders_order_id_v7_uq
  ON orders(order_id_v7) WHERE order_id_v7 IS NOT NULL;

只有目標 PostgreSQL 提供 uuidv7() 時才直接使用;否則先部署一致的產生器。

第二步:先部署寫入

新寫入在同一交易產生兩種識別並記錄映射。暫時無法填入新欄位的舊寫入仍有效。讓重試冪等,避免重複命令產生兩個映射。

第三步:增加雙讀解析

API 接受任一識別,解析到同一個規範訂單,舊回應繼續回傳 v4。新端點可在版本化合約中暴露 v7。快取鍵使用解析後的規範身分,避免兩種形式分叉。

第四步:分批回填

使用穩定游標回填,小交易執行;複製延遲、鎖等待或寫入延遲超閾值就暫停。資料足夠後再並行建立新索引。父映射持久化前不要更新子表。

第五步:遷移引用和事件

重疊期間保持子表外鍵和事件 Schema 相容。v7 欄位先設可選,同時發布兩個值;所有消費者升級後再設為必填。切換約束前先對帳缺失與重複映射。

第六步:切換內部存取路徑

一致性檢查通過後,在區域性或時間掃描受益的內部連接和分頁中使用 UUIDv7。業務排序仍使用顯式 created_at 和穩定 tie-breaker。

第七步:驗證與觀測

在匹配負載下比較索引大小、頁分裂、快取、插入延遲、範圍掃描延遲、複製延遲和錯誤率。確認 v4 與 v7 查詢返回同一行,事件消費者仍冪等。

第八步:回滾窗口後再退役

所有客戶端、回放工具、匯出、複製和副本通過遷移窗口前,保留映射、舊索引和雙讀。舊約束分獨立部署移除,並準備回滾。

示例回答

「我不會原地改寫公開 UUID 合約,而是新增 UUIDv7 欄位和唯一映射,先雙寫,再讓雙讀解析兩種形式。回填使用有界交易,監控鎖、複製延遲與寫入延遲。子表與事件在過渡期同時攜帶兩種 ID,消費者升級後再切換。完成一致性與區域性實測後,切換內部連接和分頁,但業務排序保留 created_at。特性開關在舊路徑退役前支援反向切換。」

常見錯誤

  • 立即替換公開 UUID → 連結和事件中斷 → 保留規範合約與映射。
  • 把 UUIDv7 當嚴格時間序 → 分頁跳過或重排 → 使用顯式時間戳與 tie-breaker。
  • 一筆大交易回填 → 鎖與複製延遲尖峰 → 分批執行。
  • 映射未完成就加外鍵 → 過渡寫入失敗 → 先父表、再子表、最後約束。
  • 混合不支援版本產生 UUID → 語意不一致 → 固定版本或統一產生器。
  • 忽略重試 → 重複映射 → 讓雙寫冪等。
  • 過早刪除 v4 → 舊匯出和回放失敗 → 等待回滾窗口結束。

追問

追問 1:UUIDv7 能取代 created_at 嗎?

不能。它可改善區域性並攜帶時間位,但業務排序需要顯式時間戳和確定性 tie-breaker。

追問 2:v4 和 v7 能共用 UUID 欄位嗎?

UUID 型別可以容納兩種格式,但遷移元資料、公開合約和排序語意仍需明確規劃。

追問 3:為什麼先雙讀再切換寫入?

如此在回填和消費者升級未完成時,新舊紀錄都能一致解析。

追問 4:如何限速回填?

採用有界交易,複製延遲、鎖等待、CPU 或寫入延遲超閾值就暫停,從持久游標恢復。

追問 5:事件必須包含什麼?

過渡期間發布兩個 ID 或穩定映射引用,版本化 Schema,消費者升級後再強制 v7。

追問 6:什麼時候刪除舊欄位?

客戶端、匯出、回放、副本和回滾檢查通過約定窗口後,再分獨立部署刪除約束與索引。

公開來源

同類題目