本月初,GitHub數據庫基礎架構組的高級軟件工程師Shlomi Noach在GitHub Engineering網站上發文宣告了gh-ost的開源發布。這對MySQL社區是一件大事,宣告停滯許久的MySQL表在線修改表定義操作又有了新的解決方案。
Shlomi這樣總結:
gh-ost是GitHub最近幾個月開發出來的,目的是解決一個經常碰到的問題:不斷變化的產品需求會不斷要求更改MySQL表結構。gh-ost通過一種影響小、可控制、可審計、操作簡單而且安全的方式來改變線上表結構。
目前,MySQL在線修改表定義的任務主要是通過這三種途徑完成的:
在從庫上修改表定義,修改之后再提升為新的主庫。 通過MySQL 5.6開始提供的InnoDB在線DDL功能。 使用修改表定義工具。現在最流行的是Percona公司的pt-online-schema-change和Facebook的OSC,也有人使用LHM或最早的oak-online-alter-table。在從庫上修改表定義的方案耗時長,需要更多的服務器,這種方案需要非常細致的管理工作,并且修改完畢后主從切換過程也會造成短暫的停服。InnoDB在線DDL功能在主庫上操作的確是可以滿足需求的,但過程不可中斷,并且操作完成會造成主從庫之間較大的延遲,對許多高可用方案及讀寫分離等會造成較大困擾。被各大公司DBA廣泛接受的作法主要是通過Percona公司的pt-online-schema-change和Facebook的OSC在線操作。
所有在線更改表定義的工具運行原理都是相似的:創建一張與原始表定義相同的臨時表,趁上面沒有數據時先把臨時表改好表定義,然后慢慢地、用增量方式把數據從原始表拷到臨時表,同時不斷地把進行中的原始表上的數據操作(所有應用在原始表上的插入、刪除、更新操作)也應用過來。當工具把所有數據都拷貝完畢,兩邊數據同步了之后,它就用這張臨時表來替代原始表。修改過程就結束了。
像pt-online-schema-change、LHM和oak-online-alter-table這些工具用的都是同步復制的方式,對表的每一條數據修改都會立刻在同一個事務里就應用到臨時表上。Facebook的OSC工具用的則是異步模式,先把修改操作都記在一張修改日志表里,然后再取出來執行,把修改操作應用到臨時表上。這些工具全都使用觸發器來提取那些應用在目標表上的操作。
除了在修改過程中的性能、運維等問題之外,在最后的表切換——用臨時表替代原始表——的過程中也非常容易出各種問題:切換失敗、丟數據、阻塞原始表業務操作過久等。為此Shlomi曾在博客上基于較優的Facebook OSC的行為連續寫了三篇文章(一、二、三)進行分析,進行了細致討論,并最終給出了一種比較簡單而又無損的方案。
GitHub每天數據庫基礎架構組幾乎每天都會收到幾次對表結構進行修改的請求,因此各種可能的丟失數據、復雜管理、進度不可見等行為都是不可忍受的。最終他們經過周密的討論、研發和測試,推出了自己的工具:gh-ost,這個名字是gitHub's Online Schema Transmogrifier/Transfigurator/Transformer/Thingy的縮寫,讀音同Ghost。
gh-ost有以下特點:
無觸發器:這也是其他工具最受詬病之處。觸發器方案會對MySQL的性能造成比較大的影響,嚴重時甚至會拖垮主庫。 輕量級:gh-ost獲取數據表修改操作的方法是偽裝成從庫連入,獲取并解析二進制日志,對臨時表插入數據也是增量、可控制的,因此對MySQL主庫的性能幾乎無影響。 可暫停:當原主庫處于業務高峰期時,完全可以暫停gh-ost的操作,暫停就意味著對主庫沒有寫入和更新,這是非常受歡迎的。 動態可控:gh-ost的操作不但可以暫停,還可以動態修改,因此在各種情況下修改了配置之后都不必從頭開始重新運行整個修改過程,這是非常節約資源的。 可審計:gh-ost的狀態是可以非常容易獲取到的,包括當前任務進度、主要配置參數、相關MySQL實例的情況等。gh-ost通過監聽TCP或者unix socket文件來獲取命令,因此就給了運維人員極大的靈活性。 可測試:gh-ost支持在從庫上進行測試,以觀察對系統負載的影響、驗證正確性等。GitHub生產環境的每一張表都這樣用gh-ost在從庫上做過好多次修改測試,他們也呼吁大家用這種方式先體驗gh-ost的功能,再考慮上線應用。 可靠性高:經過充分的測試之后,現在GitHub生產環境的修改表定義操作已經全部由gh-ost完成了,而且它還有暫停、延遲切換、準確估計任務進度等功能,審計和在線控制功能可以讓它輕松地與監控系統結合起來,必然非常受運維人員喜愛。 完美解決切換問題:表切換操作是在線修改表定義的最后一步,其它工具操作到這一步時常常會出現各種問題。Facebook OSC也曾詳細分析過這個問題,但它的最終方案是個非原子性切換:先把原始表改名,再把臨時表改名頂上。可惜在兩次改名中間會有一小段表不存在的時間,在這期間運行的業務語句都會失敗,因為目標表不存在。Shlomi等經過嚴密的論證和實驗,給出了原子性的兩階段切換方案:用一條連接去持有鎖,另一條連接做原子性的rename操作。在rename操作之前,會創建一張信號表,用它來阻塞rename操作,直到所有要求的表切換前提條件就緒。根據這個方案,表切換或者成功,皆大歡喜;或者失敗,則對業務無影響,也不會丟失數據,還會釋放鎖讓業務繼續,DBA只需要再一次用gh-ost重新嘗試切換即可。gh-ost按照MIT許可協議向開源社區發布。盡管現在已經穩定了,GitHub仍在計劃繼續改進它,也非常歡迎來自開源社區的力量來一起試用和提出改進意見。gh-ost是用GO語言實現的。
感謝杜小芳對本文的審校。