天天看點

[翻譯]Triggerless design.md

源文将持續更新,請點選此處閱讀原文

介紹gh-ost不使用Trigger背後的邏輯和算法,其次是這樣設計的含義,優點,以及缺點。

基于觸發器遷移的理念

這裡有兩個比較流行的online DDL工具:

  • pt-online-schema-change
  • Facebook OSC

前者使用同步設計原理:它在原表上建立三個觸發器(AFTER INSERT,AFTER UPDATE,AFTER DELETE)。每個觸發器将原表上的操作重播到ghost表中。是以,原表上的每一個UPDATE都會被重播到gh-ost表中,INSERT/DELETE也是如此。觸發器和原表的操作存在與同一個事務空間裡。

後者使用異步設計原理:它在原表上建立三個觸發器(AFTER INSERT,AFTER UPDATE,AFTER DELETE)。它也會建立一個changelog表。觸發器不會直接将原表操作重播到ghost表中。取而代之的是,它會在changelog中添加一條記錄。原表上的UPDATE操作在changelog表中插入一條記錄像”原表上有一個UPDATE操作,将這個值變為了另外一個值”,INSERT/DELETE也是如此。一個背景的線程會實時去檢視changelog表的變化,并應用所有的變更應用到ghost表上。這是一個異步的操作,因為gh-ost重播操作的過程與原表的操作不在相同的事務空間中了,并且gh-ost的重播操作可能會延遲。值得注意的是,changelog的記錄寫入操作還是與原表操作存在于相同的事務空間中。

不基于觸發器的異步遷移理念

gh-os使用不基于觸發器的異步遷移方案。但是它不需要觸發器,因為它不像FB工具那樣需要一個changelog表。因為它不是基于changelog表做資料遷移,而是基于MySQL二進制日志(binlog)。

gh-ost基于binlog_format=row(RBR)擷取來源于原表的記錄,你也可以基于binlog_format=statement(SBR)擷取記錄,詳情請點選。

RBR格式的二進制日志将複雜的SQL語句(可能存在多表)分解為不同的單表單行記錄,這樣更易于将binlog應用到gh-ost上。

gh-ost将自己僞裝成MySQL從庫:gh-ost連上主庫,并且從主庫擷取binlog。是以,它擷取binlog日志,并且通過過濾得到原表的操作事件。

gh-ost可以直接連接配接到主庫上,但是gh-ost更喜歡連接配接到一個從庫上。但是該從庫需要設定參數

log_slave_updates=1

binlog_format=row

(binlog_format也可以通過調整gh-ost參數來進行自動設定)。

讀取二進制日志,特别是在在從庫上讀取二進制,進一步強調了算法的異步性。當有一個事物寫入到了binlog中,它依舊需要等gh-ost僞裝成一個從庫之後,才開始擷取binlog并且應用到gh-ost中。

異步設計也意味着有很多值得注意的因素,稍後将做讨論。

工作流程概覽

整個工作流程包括:原表上讀取表資料,binlog讀取操作事件,檢查主從延遲以及其它的節流參數,将變更應用到ghost表中(通常是master上的ghost表),通過二進制日志流發送提示等等。

工作流程如下:

  1. 初始化設定&驗證

    初始化設定不是一個并發操作。

    • 連接配接從庫(推薦)/主庫,檢查主庫标志
    • 預驗證ALTER語句
    • 初始化驗證:權限以及表是否存在
    • 建立changelog和ghost表
    • 在ghost表上執行ALTER語句
    • 對比原表和ghost表的結構,檢查共享列,共享唯一鍵,驗證是否有外鍵,選擇共享的唯一鍵,這個鍵用于處理表的唯一辨別,比如資料遷移等
    • 開始監聽binlog,監聽changelog表的事件
    • 在changelog表上注入”good to go”的記錄(被二進制日志攔截)
    • 開始監聽原表DML的binlog事件
    • 擷取之前原表和ghost表的共享唯一鍵在原表上的最小值和最大值
  2. 資料複制流程

    該步驟包括多個移動部分,所有操作互相協調并發執行。

    • 設定一個心跳機制:頻繁的寫入到changelog表(這是一個低負載的操作)
    • 心跳機制不斷的更新狀态
    • 定期(頻繁)檢查潛在的節流資訊或者提示
    • 在原表上通過行範圍控制,将原表資料拆分為一個chunk一個chunk,并且添加到資料遷移任務隊列中
    • 通過binlog擷取原表的DML語句,并且添加到binlog重播任務隊列中
    • 處理資料遷移任務隊列和binlog重播任務隊列,并将其順序的應用到ghost表上(當遇到節流操作或者hint提示時,将會暫停該操作)
    • 當資料遷移與binlog重播完成後,将會在changelog表上注入/攔截”copy all done”的記錄
    • -postpone-cut-over-flag-file

      參數設定的檔案存在時,将會推遲接下來的cut-over操作(但是原表的DML操作依舊會通過binlog應用到ghost表上)
  3. 結束操作:交換表流程
    • 将原表加上寫鎖,binlog事件會繼續應用在gh-ost上(該步驟是個異步操作,是以即使表被鎖住,gh-ost仍然可以處理隊列中未處理完的binlog事件)
    • 将原表rename為_tablename_del表,ghost則rename為tablename表
    • 清理工作:删除需要被清除的表

異步設計優點

  1. Cut-over階段

    異步設計最複雜的地方在于cut-over階段:原表和ghost表的交換。在同步設計中,由于原表操作與觸發器操作是在同一個事物空間中的,是以原表和ghost表的資料始終是同步的,是以一個簡單的原表和ghost表交換(Rename)是可以存在的。

    在異步設計中,即使我們對原表加鎖,管道中仍然會存在一些事件,binlog日志依舊會将來自原表的事件重播到ghost表上。采用同步設計中交換表的方式是不可取的,因為這意味着,即使還沒有對來自原表的事件重播完畢,就開始使用重命名後的ghost表了,這将造成資料不一緻。

    Facebook的使用”中斷機制”,二步重命名法:

    • 鎖住原表,處理積壓的事件(backlog)
    • 将原表重命名
    • 将ghost重命名為原表的名字

    在兩個表交換的時候,會有一個表不存在的階段,是以會存在”表中斷”的問題。

    gh-ost通過”two-step”算法解決這個問題,”two-step”算法會阻塞表寫入,然後将兩表交換。它使操作很安全,要麼成功,要麼失敗則復原到cut-over階段之前。

    更多的資訊請閱讀cut-over

  2. 分離去耦

    不使用觸發器的異步設計最大的影響在于工作負載的去耦。使用觸發器的設計,不管是同步還是異步方法,原表上的每一個寫入意味着需要立刻在另外一張表上寫入。

    We will break down the meaning of workload decoupling, shortly. But it is important to understand that gh-ost interprets the situation in its own time and acts in its own time, yet still makes this an online operation.

    The decoupling is important not only as the tool’s logic goes, but very importantly as the master server sees it. As far as the master knows, write to the table and writes to the ghost table are unrelated

  3. 寫負載

    不使用觸發器意味着主庫不需要有過多的寫負載用在存儲過程上,以及對gh-ost表的鎖争用上。

    将原表操作重播到ghost表上完全由gh-ost工具完成。是以gh-ost可以決定何時将資料寫入到ghost表中。為了分解原表的寫負載,gh-ost工具選擇使用一個單獨的線程将原表操作重播到ghost表上。

    MySQL對一個表大量并發寫入的時候性能不是很好,這個時候鎖變成了一個很大的問題。This is why we choose to alternate between the massive row-copy and the ongoing binlog events backlog such that the server only sees writes from a single connection。

    更有趣的是,gh-ost是唯一寫入到ghost表的程式,沒有人意識到ghost表的存在。是以,gh-ost工具不存在由觸發器産生的高并發性問題以及資源高争用問題。

  4. 可暫停性

    當gh-ost暫停工作(節流導緻),此時将會沒有任何資料寫入到ghost表中。因為gh-ost不存在觸發器,寫負載是與gh-ost寫負載分開的。并且由于gh-ost使用異步設計的方法,gh-ost算法已經處理了主庫寫入與ghost表寫入的時間差。對于gh-ost來說,幾毫秒的時間差與幾小時的時間差并沒有任何差別,對于gh-ost的運作并沒有任何的影響。

    當gh-ost進行節流操作的時候,不管是因為主從複制延遲,還是達到

    max-load

    設定閥值等,原表還是正常操作。僅僅隻是對ghost表沒有任何的寫操作。除了changelog表上的heartbeat在不斷的更新,但這個操作帶來的性能影響是可以忽略不計的。
  5. 可測試性

    我們甚至可以測試資料遷移(migration)步驟:就像我們将資料遷移的操作與主庫的負載分開一樣,我們不在主庫上應用所有的變更,我們選擇一個從庫,這樣我們可以在從庫上進行表的資料遷移(migration)。

    這本身是一個很不錯的功能;它為我們提供了測試的可能性:正如我們完成資料遷移一樣,我們從庫的複制(stop slave)。gh-ost會進行cut-over階段,但是gh-ost也會復原回去。gh-ost測試時不會删除任何的表。結果是從庫上的原表和ghost表都會存在,也不會做進一步的變更操作(因為此時主從複制已經停止)。我們可以對兩張表進行對比。

    這個方法可以用于驗證gh-ost工具的正确性:在多個生産從庫上不斷的重複的做”資料遷移”(實際上并不會修改列)。每次資料遷移後都對原表和ghost表做一次資料校驗。gh-ost預計是所有的表資料校驗都是一緻的。

  6. 多表并發操作

    gh-ost可以運作多個不同的表并發操作(當然不是在相同的表上并發操作)。gh-ost異步設計的方法是支援多個不同的表并發操作的。事實上沒有觸發器的存在,多個不同的表并發操作gh-ost對于主庫來說,隻是并發的多個連接配接而已。每個gh-ost都可以控制自己的節流,或者全部一次性控制它們的節流操作。

  7. Going outside the server space

    More to come as we make progress

異步設計缺點

  1. 增加流量

    現有的工具都是通過觸發器來重播原表上的操作。gh-ost是通過自己來擷取原表操作,然後重播到gh-ost表上。gh-ost當然更喜歡在從庫上擷取原表操作,然後在主庫上重播到gh-ost表上。這也意味着,主庫機器和從庫機器之間存在資料的傳輸。并且gh-ost使用的MySQL用戶端不支援壓縮功能, and so during a migration you can expect the full volume of a table to transfer on the wire。

  2. 增加代碼複雜性

    基于觸發器同步方法的online DDL工具相對來說代碼較少。大量的資料遷移是基于觸發器完成的。復原,資料類型以及cut-over階段都是由資料庫隐式處理的。gh-ost的異步方法讓它的代碼變的更複雜。它分别連接配接到主庫和從庫,僞裝成從庫,向主庫寫入心跳事件,在從庫上讀取binlog事件并寫入到主庫上,它還需要管理連接配接失敗,主從複制延遲等等。

    是以,gh-ost擁有更大的代碼庫以及更複雜的異步并發邏輯。