MVCC 時光機:在 TiDB 的時空自由穿梭丨渡渡鳥復興會賽隊訪談

語言: CN / TW / HK

TiDB 處理各種災難故障可謂輕車熟路,但是常言道“天災易躲,人禍難防”,對於各種誤操作、bug 寫入錯誤資料、甚至刪庫跑路,目前還沒什麼招。我們專案最初也是為了處理這些“意料之外”的事故。專案最初的名字叫 TiDB Flashback,後來又覺得這個名字過於貧瘠,無法體現專案內容的優越性,後來索性改成了“MVCC 時光機”。

——渡渡鳥復興會戰隊

在 TiDB Hackathon 2021 賽事中,渡渡鳥復興會賽隊的作品“MVCC 時光機”充分利用 MVCC 特性,加強 MVCC 資料的查詢、整理、恢復的能力,提高問題處理的效率,摘得了賽事的 “三等獎” 。

一個非常 smart 和輕量級的實現,效果很不錯,期待儘快釋出上線。

——評委馮光普

這個專案是給運維同學在某些時候救命的功能。它通過 SQL 很好地解決了運維的操作問題。更讚的是,該專案引入了多 Safepoint 機制,也就是可以給 TiDB 叢集定期做一些全域性 Snapshot,進行快速輕量級的邏輯備份。

——評委唐劉

關於專案

天災易躲,人禍難防

作為一款分散式資料庫,完善的高可用和容災機制可以說是 TiDB 的核心特性。然而在生產中,理論上的災難恢復和實際上要面對的卻可能大相徑庭。不可預知的硬體故障、自然災害導致的斷網斷電固然可怕,而把生產環境當成測試環境的誤操作、有漏洞被黑客攻擊、業務出 Bug 寫壞資料反而可能是更高頻的事故。

隊員之一 @disking 曾經就職於遊戲公司,對這種“人為的失誤”感受頗深:業務同事寫的程式碼中出了一個 bug,在版本上線後的幾個小時內被別有用心玩家利用獲得了許多不正當的收益,這時候就要進行回檔操作,讓遊戲資料回到某個具體的時間點,需要實現的就是類似“時光機”的功能。手動回檔、資料丟失造成的損失都會給團隊帶來很大的麻煩。

現在物理上的故障處理 TiDB 已經給出了像 BR 工具、兩地多中心方案等解決措施,而一句rm -rf /*,或者“實習生誤操作”導致的錯誤資料寫入帶來的風險卻更加不可控,這也是 TiDB 缺失的一大塊拼圖。

Make MVCC Great Again!

因為 TiDB 的事務是基於 MVCC 的,所以一段時間內的舊版本都在,理論上對於上述人禍,都是可以進行手動恢復的。但是現有的功能和計劃中的功能相對較弱:

  • 很可能需要排查資料損壞的情況,目前只能指定 ts 去讀一個時間點的資料,要檢視某條記錄的變化歷史太麻煩
  • RECOVER TABLE 只能恢復 DROP/TRUNCATE 這種 DDL 操作,對 DML 沒招
  • GC Safepoint 之前的資料恢復不了,如果想保留長時間的資料,又太費空間了
  • 恢復資料要先把資料 dump 出來再重新寫入,太慢了

因此充分利用 MVCC 特性,加強 MVCC 資料的查詢、整理、恢復的能力,就能提高問題處理的效率。MVCC 不只是可以用來暫時性地處理事務隔離,也完全可以做為冷備,相比於外部的備份,其優點是可以更省空間,恢復資料也更方便更快。

@disking 其實最早在第二屆 Hackathon 時就有了這個想法,靈感來源於 Oracle 的 Flashback 功能。當時他就把這個想法放到了研發的討論群裡面卻沒有得到迴應,而當時的他還沒有足夠的精力來組建自己的戰隊。雖然不至於說耿耿於懷,但是這麼有趣、有用的想法當然要找個機會實現一下,這次的 Hackathon 就是一個實現想法的好機會。

渡渡鳥復興會就這樣開始組建了。

1.jpg

團隊用20個渡渡鳥的對話情況來進行 demo

關於比賽

如何製造一個時光機?

誰掌握了過去,誰就掌握了未來;誰掌握了現在,誰就掌握過去。

——喬治·奧威爾《1984》

總的來說就是圍繞 TiDB 的 MVCC 機制做了一些(乍)看起來比較 fancy 的操作,比如查詢表記錄的 MVCC 歷史、篡改 MVCC 記錄以及根據 MVCC 記錄做到瞬時版本回退(Flashback 操作)等。

整個專案比較核心,或者說比較實用的點還是 GC SavePoint 和 Flashback 功能的組合拳,通過設定定期的快照備份,利用 TiDB MVCC 的機制做到 TiDB 儲存內部的“冷備“,在一些關鍵時刻還是有救命效果的,畢竟只需要一條 SQL 就可以達成整表資料的 Flashback。

整個專案分為了三個相對獨立的模組:

1.MVCC Query in SQL -> 操縱過去

  • 參考 _tidb_rowid的實現,增加 _tidb_mvcc_ts_tidb_mvcc_op虛擬列。
  • 當查詢虛擬列時,TiDB 傳送給 TiKV 的請求中要帶上標記,指明要查詢 MVCC 虛擬列。
  • 修改 TiKV 的 MVCC 讀取邏輯,當需要查詢虛擬列時,需要掃描所有版本,而不是隻掃描最新版本。然後設定每條資料對應的虛擬列值。 _tidb_mvcc_ts為事務的 commit_ts_tidb_mvcc_op為事務的操作型別,可以是 PUTDELETE

2.jpg

下面是一次演示,可以看到我們可以能用各種姿勢來查詢 MVCC 記錄!

3.jpg

甚至還可以去篡改某一次的 MVCC 記錄。

4.jpg

2.GC Savepoint -> 掌控現在

  • 新增 gc_savepoint系統表,可以通過 SQL 增刪改查來進行管理。
  • GC 進行時,需要將 gc_savepoint表的資料,與原本的 gc_safepoint一同存放到 PD。
  • 修改 GC 邏輯,回收資料時考慮 gc_savepoint。因為 GC 有傳統 GC 和 compaction GC 兩種,時間關係可以只做一種。 在設定 gc_save_point_interval = ‘5m’後,在 gc_safe_point之前,本來會被回收 MVCC 記錄每 5 分鐘保留一個版本。

5.jpg

3.Subsecond Flashback -> 著眼未來

  • 新增 flashback table tsSQL 語句,用於指定 table 進行資料還原。意義是將表還原至不超過 ts 時間戳指定的版本。
  • 將時間範圍寫入 table schema 並觸發 DDL 操作,DDL 同步完成即可返回操作成功。
  • TiDB 請求 TiKV 時,需要將要忽略的 ts 區間放在請求中發給 TiKV。
  • 修改 MVCC 讀取邏輯,要根據指定的區間跳過對應的版本。
  • 當 ts 區間超出了 GC 範圍後,需要被清理。
  • 結合上面的 MVCC 查詢,我們可以看到現在表資料中的“變化渡渡鳥”記錄在某個時間節點前還是“時間渡渡鳥”,通過 Flashback 操作,我們成功讓資料變回了曾經的模樣,把“時間渡渡鳥”召喚了回來。

6.jpg

在實際的災難恢復場景中,如果我們一不小心錯誤地修改了某個表的幾條資料,甚至是誤刪了整個表,都可以通過 Flashback SQL 來將其一鍵恢復到任意 MVCC 記錄版本。

未來展望

目前的實現僅基於 TableScan 進行了 Demo,還有一些 IndexScan 和點查詢的適配工作沒有進行;有些 TiDB 的生態工具是越過 SQL 層進行資料查詢的,這方面的相容性也是接下來需要考慮的問題。

除此之外,如果能夠擴充套件 Flashback 操作和 MVCC Query 的組合拳,就能夠實現更多的功能,比如檢視 Flashback 記錄、撤銷 Flashback 操作、修改 Flashback 記錄等等。

關於隊員

怎麼來到 TiDB 的世界的?

@JmPotato 剛剛結束了自己的學生生涯,現在是一名 PingCAP 的研發工程師,@RinChanNOW 是他的本科室友,也加入到了本次的專案之中。他們的另一位室友也在位元組跳動分散式系統研發的部門實習。

——一個宿舍的基礎軟體工程師!

對於很多學生來說,成為一名應用開發者或者演算法工程師或者進入 AI 行業都是更加主流的選擇,為什麼這麼“巧”,他們不約而同地加入這個行業?

@JmPotato 表示,是他開了這個“頭”。2019 年年底,他在聽播客的時候偶然聽到了東旭關於分散式資料庫的分享,那個時候也不知道 PingCAP。後來進行相關的學習,在學習 Raft 的過程中接觸到了 TiKV、TiDB 這些專案,才慢慢了解到原來它們都是 PingCAP 的產品。那時候 PingCAP 剛好在招聘實習生,自己也覺得心馳神往,做了很多相關的準備就開始面試、進入公司實習直到現在正式加入。

@RinChanNOW 也表示,是 @JmPotato 的這份實習為整個宿舍都打開了一個全新的世界,還記得當時大家一起實現一個簡單的 Raft 協議,從那個時候就能感受到分散式系統的奇妙,不是被動的內卷,而是一種發自內心的熱愛和嚮往。

不懂 TiDB,怎麼快速加入 Hackathon 並且開始工作的?

雖然 @RinChanNOW 之前學習過分散式系統的相關知識,但是本身在學習和工作中都沒有實際接觸 TiDB 的經驗。作為一名外部開發者,如何加入到這場關於 TiDB 的 Hackathon 中?這也是很多同學對於 Hackathon 活動望而卻步的重要原因。@RinChanNOW 就完全沒有這樣的擔心。一方面 TiDB 的文件豐富,體系化的學習起來並不費力;另一方面 TiDB 的社群非常活躍,無論是 AskTUG 還是 TiDB internals 或者說就是 GitHub 上,都能遇到很多志同道合的夥伴,他們也都願意幫助 new TiDBer 快速融入社群。

@RinChanNOW 也與大家分享了一些具體的學習經驗:

除了 TiDB 與 TiKV 的開發環境準備之外,需要做的一個準備工作就是了解 TiDB 和 TiKV 的程式碼結構與它們的資料流,也就是要去大致瞭解它們的原始碼,而這也是最耗時間的一個過程,所以我的程式碼量並不大,但是卻花了很長時間才寫完。 我根據需要改動的部分,結合 PingCAP 的官方部落格,對原始碼進行了一波學習:

玩真的:TiDB Hackathon 有什麼不同?

隊伍中的 @disking 可以算是 Hackathon 的老玩家了,除了這次的 TiDB Hackathon,@JmPotato 和 @RinChanNOW 也都程度或深或淺地參與到了其他類似的程式設計競賽中,談及不同賽事體驗的差異,他們有著統一的看法:很多程式設計競賽更多是面向學生的,留一些有著明確目標甚至是標準答案的作業,更像一場檢驗程式設計能力的考試,比拼的可能是誰的實現更優雅,效果更佳。而參與 TiDB Hackathon 就是一種完全不同的體驗。 TiDB Hackathon 更偏向實操,沒有明確的選題,是一場未知的冒險,比起程式碼實現更重要的是創造力和思考,只有真正在用、真正參與到產品迭代中的開發者才能感受到其中的樂趣。

但是受到疫情的影響,近兩年的 TiDB Hackathon 雖然熱鬧,但還是少了一點氣氛。如果有機會,還是期待明年的 Hackathon 能夠和各位選手來到同一空間現場交流,來一場真正的 48 小時密集開發。