PostgreSQL復制特性歷史漫談
就復制功能來說,從遠不能勝任,到功能完備種種包含在內,PG雖然腳步略遲,但很快地走完了這些路徑,的確當得起一個“功能***大的開源數據庫”的稱呼。
原本我準備的下一個話題,是PostgreSQL的Redo的討論,但就PG的實現看,對運維來說,redo的機制很少需要特別關注,所以就把redo話題下的復制主題,單獨拉出來整理了一下,其中***部分,就是PG的復制這個特性的歷史來由。
復制,曾經是PGer心中永遠揮之不去的傷口,在PG 9.0之前的版本,如果想要做一個PG的數據庫主從,只能人工(PG內置了歸檔文件的shell調度操作)不斷地復制wal日志(并且只能復制到當前在寫的wal的前一個WAL日志)到從庫(姑且這么叫它),這個從庫需要設置為恢復模式,不可對外提供服務。
從我個人的看法而言,PG在功能性上,的確是強于MySQL的。但是為什么在互聯網業務早期不被看好并選用,主要原因之一,就是其無法進行讀能力的擴展,而在互聯網業務之外,對事務,數據安全講究的人來說,PG對比Oracle,其差距也是肉眼可見的。
MySQL還是一個跑在文件的SQL執行器(其衍生出來的MyISAM真的最多只能說是一個帶B樹的文件訪問器)的時候,就已經做出來復制這個關鍵特性,并用這個機制,終于等到InnoDB的引入,成為了一個“真正的數據庫”。對于早年的互聯網業務,MySQL這種快速擴容從庫,擴展讀能力的機制,對互聯網業務這種先天讀遠多于寫的形態,簡直是天作之合。
而PG,在那個時代而言,的確是比較被動的,但終于隨著時間推移,逐步實現了主從復制的種種特性。
9.0的異步redo復制實現
時間來到2010年(***波互聯網大潮早已過去,第二波互聯網大潮也將要過去,大創業時代即將來臨),PG發布了版本9.0,其中最重要的特性之一,就是流復制機制的實現,解決了兩個問題:一個是通過網絡連接,從庫直接去拉redo日志(redo復制,這個也是MySQLer心中痛點啊),而非從主庫走操作系統命令復制過來,避免了對這個復制機制本身維護的復雜性;第二個,就是改造出來Hot Standby機制,也就是在從庫應用redo的同時,也允許從庫提供只讀的select服務。
這里實現的復制,當然還只是異步復制,而且機制我認為很怪異:首先,把從主庫走操作系統命令復制wal日志(不包含***的正在被寫入的wal文件)全部執行recovery之后,再發起一個網絡連接到主庫讀***的wal記錄,主庫的連接信息從recovery.conf文件中讀取(MySQL是change master命令之后,直接存在masterinfo文件里面,或者每次start slave的時候,手工指定)。
而如果從庫想要中斷主從復制,不是執行一個stop slave(MySQL)命令,而是在操作系統設置一個trigger文件。
9.1的(半)同步復制實現
2011年,pg 9.1發布,其中引入的新復制機制,就是同步復制這個大殺器了。
在MySQL中,有個被稱為“半同步復制”的機制,就是說當主庫收到客戶端的commit發出來之后,直到從庫接受完成這個事務所有的event,并且確認flush到relay日志之后,這個commit才會返回給客戶端,客戶端收到commit的時候,就代表即便主庫宕機,數據也必然已經存在于從庫,也就是“沒有數據丟失”。
簡單概括來說,PG在9.1中實現的同步復制,也是這么一回事,把關鍵字event替換成redo,relay日志替換為從庫wal日志就可以。
注:下圖僅為相關流程的簡化圖,僅保留了與復制相關的邏輯,對于WAL子系統沒有詳細展開,后續其他圖片一樣,都是簡化圖.
但實現的細節上,還是有很多區別的(MySQL的討論基礎版本一律為MySQL 8.0——好吧,我知道這個有些不公平)。
比如允許在回話或者單個事務級別控制,區分“重要”事務與“不重要”的事務,MySQL中,半同步會在遇到從庫響應超時的時候,進行自動的全庫降級(rpl_semi_sync_master_wait_no_slave控制)。
比如其設置哪些從庫是半同步復制的時候,是通過逗號切割的方式指定一個或者多個(但只支持其中的某一個作為同步從庫,首先選取***個作為同步節點,如果***個出問題,則選取第二個,以此類推),而MySQL的半同步復制,則是保障“只要有rpl_semi_sync_master_wait_for_slave_count個從庫接收到”這個邊界點。
比如由于rollback實現機制問題,PG的rollback不會受到同步復制阻塞,而MySQL在特定情況(可以參考https://my.oschina.net/llzx373/blog/282768 這篇文中,討論的在rollback情況下,也會產生binlog的討論)下,即便是rollback,也需要等待半同步響應。
比如事務可見性上,PG是直接通過事務id控制,而MySQL在5.7引入rpl_semi_sync_master_wait_point來處理(MySQL 半同步復制在5.7之前,主庫的其他事務可以看到在等待半同步復制返回的事務的數據,即便這個事務尚未對客戶端返回commit)
順帶一提,pg_basebackup這個命令也是這個版本中引入,主要用來搞從庫和數據庫備份。
PG的從庫也可以通過報告查詢所需要的最老事務點給主庫的方式,避免主庫的數據清理(vacuum)清理掉從庫查詢所需要的數據,當然,從庫的長時間查詢也會導致主庫文件放大,具體使用決策上,值得考量。
而在控制復制啟停的方式上,也提供了sql函數調用,而非只是純粹依賴觸發文件。(pg_wal_replay_pause(),pg_wal_replay_resume())
9.2的級聯復制
級聯復制,也就是A->B->C這種形態的復制,其***的意義,是降低主庫的復制負載壓力。
復制負載這個問題的來源,是類似互聯網業務中,寫少讀多,一個主庫,可能要承擔幾十個從庫(記得有次春節時候,我們給一個主庫擴出幾十個從庫)的日志發送行為,無論從磁盤壓力還是網絡帶寬來說,級聯復制都是有必要引入的。
當然,級聯復制的C,就必然是異步復制了。
pg_basebackup也可以在從庫執行備份了,可以避開備份對主庫的性能影響。
而在同步復制上,新增了一個remote_write 級別,意思是,只要從庫接收到wal日志就可以,不需要保障必須flush到磁盤的情況下,就可以確認commit成功了。
9.3的性能與易用性
這個版本沒有本質性的特性變更,但在易用性和性能上,做了相當大的改進。
比如當多個從庫中的某一個從庫被提升為主庫之后,其他從庫可以直接切換過去,而在之前,必須重新同步。
比如pg_basebackup可以直接生成一個recovery.conf文件。
9.4邏輯日志導出與延遲備份
這個版本開始,xlog(wal日志)支持邏輯解析方式的導出,用于邏輯復制,或者跨數據庫類型復制這種操作,甚至邏輯復制連同步復制都可以支持,唯一的限制,是邏輯復制只能作用于單庫而非全局。
而replication slot的概念,也是這個版本開始引入。
之前提到過,為了避免主庫清理掉從庫尚需使用的數據,從庫需要給主庫報告所需要的事務點信息,在9.4開始,這個機制被單獨提出來,稱為replication slot,其主要的作用,是為物理復制,以及邏輯復制,提供維持事務點信息的視圖,避免從庫連接斷開等原因導致的數據清理。
而在復制應用上,這個版本開始,PG增加了延遲復制這個特性,對于誤刪除操作等諸多問題,這個特性可以讓數據恢復時間盡可能地縮短了。
9.5性能與易用性
這個版本沒有大的變更,主要是以下一些內容:
允許WAL日志以壓縮形態傳輸到從庫,以主從庫CPU換取較低的網絡消耗。
recovery.conf的主庫連接信息,可以以URI(postgres://)的形式來寫。
新增了wal_retrieve_retry_interval 參數來控制從庫失敗后的重試。
9.6多個同步復制從庫以及真-同步復制
說個題外話,PG在9.6開始,支持了并行查詢,并在隨后的版本中做到了很大的增強,這點也是我認為PG對比MySQL上,有絕對優勢的一個特性。而PG的主要槽點之一vacuum凍結,也是在這個版本引入不再重復處理已經完全凍結的數據塊這個重要特性的。
前文中提到,MySQL有個參數rpl_semi_sync_master_wait_for_slave_count控制同步的從庫數量,而PG則是從設定的列表中選取某一個,在9.6開始,這個設計被變更為,可以等到wal被確認寫入多個從庫之后,再返回commit。
synchronous_standby_names參數也不僅僅是逗號切割的列表,變成了 n(s1,s2,s3)這種形態,讓前n個數據庫達到wal條件后確認commit。
在前面同步復制的討論中,提到PG的同步復制,與MySQL半同步復制實現機理基本類似,但在9.6開始,新增了remote_apply 這個同步點,也就是,直到從庫應用了對應的wal日志,主庫才能返回commit成功,可以做到主庫提交從庫立即可見的效果。我相信不止我一個人被開發問到,說我主庫寫入的語句,到從庫去查,為啥查不著的問題,而在當代的大型項目中,可能上層應用直接調用讀寫的假設,就是寫入立即可讀,下層如果貿然采用了傳統的讀寫分離手段,可能就會導致上層應用無法馬上看到數據的問題了,這個問題再摻和上主從延遲的種種糾結,是我在傳統行業客戶中,遇到最多的問題之一。
10 發布訂閱式邏輯復制
邏輯復制在這個版本得到了極大的增強。
首先,是支持了邏輯復制這個特性本身。雖然9.4開始,xlog就已經可以解析并提供給邏輯復制使用,但PG在當時,并沒有內置邏輯復制的相關組件,在10版本開始,支持了到表級別的邏輯復制,并且支持跨大版本,跨操作系統,跨機器架構之間的邏輯復制,靈活性上遠勝物理復制(MySQL的binlog復制就是如此)。
synchronous_standby_names再次變更了語法,包括first和any兩個語義,first指定的列表的話,會按照順序優先級確認返回的從庫響應達到指定數量(比如first 1(s1,s2)就類似舊的實現,選取***個作為同步點,如果***個s1失敗了,再選取第二個s2作為同步點),而any語義的話,舉個例子,類似any 2(s1,s2,s3)這種,則是允許三個從庫中,任意兩個從庫只要返回同步成功,就可以確認commit了。any來說,更類似MySQL中半同步的確認語義。
recovery模式的恢復目標點,除了timestamp,事務id之外,也支持了LSN號,恢復的時候更加靈活了。
另外估計是由于slot維持wal,導致一些為臨時會話維護的slot導致wal積累(比如pg_basebackup,每次備份都需要創建slot,備份完成后,slot還需要處理掉),這個版本開始,slot支持僅為當前會話提供的臨時slot避免這個問題。
11 邊角修補的復制特性
11大版本的主要功能變化,是分區表終于可以用了,而不是必須得采用第三方插件,包括hash分區,分區表上的主鍵,外鍵,全局索引,觸發器這些,都終于支持了。
而在復制特性上,這個版本基本上沒有什么大的變化,都是些邊邊角角的修補。
比方在做備份的時候,增加對數據塊的校驗。
比如pg_stat_wal_receiver 視圖,增加了機器與端口信息。
乏陳可述,但就PG目前在復制上已經做的事情來看,的確也沒有更強的需求來驅動這方面的進一步增強。
結語以及個人思考
從遠不能勝任,到功能完備種種包含在內,PG雖然腳步略遲,但很快地走完了這些路徑,就功能性而言,的確當得起一個“功能***大的開源數據庫”的稱呼。
佛門講修行,其中一道障礙稱為“知見障”,是說對一個東西認知越多,看其他東西的時候,成見也就越多,也就越難有一個清晰的認知(注:這個是知見障的一種解釋,而且應該是佛教禪宗本土化后的頓宗解釋,原始佛教中,這個詞應該是類似六根不凈的一個概念,不是我這里要表達的意思)。
的確,我自己的感覺也好,和其他人聊數據庫時候的感覺也罷,每個人都有以自己熟悉的,認知的“數據庫”來去看其他數據庫的習慣。
以我自己來說。
比如我對pg的vacuum這么糾結,是因為這個問題上,在MySQL,Oracle這倆我熟知的數據庫中,都是(相對)很好地解決了這個問題的,而PG非得別別扭扭地不處理——是的,是可以有很多人工策略可以搞,是有很多參數設置合適可以避開,但為什么非得我去管?它自個安安靜靜地處理好不就得了?哪怕是***限度的單表上的并行vacuum也可以啊(MySQL 5.6之前單線程purge也是一個大坑,后來就改多線程了)。
比如對于DB2,這個是我學校學習數據庫時候的教材數據庫,我的諸多知識都是從這個數據庫上學會的,當我之后不久去看Oracle的時候,***個反應是,隔離級別到哪里去了?——Oracle的隔離級別,除了RC,Serializable之外,其他隔離級別都是通過變通方式實現的,而DB2,是有很齊全的4個隔離級別的(雖然各個隔離級別名字和SQL標準的名字不是很一致)——后來我給別人講隔離級別課程的時候,都是用DB2講課,而不是拉一個其他數據庫出來(MySQL InnoDB在RR處理了幻讀,那Serializable級別和RR的區別,解釋起來就很費工夫,而PG的幾個隔離級中,讀未提交讀不到臟讀,可重復讀讀不到幻讀就更不用說了),我不知道有多少人拿著Oracle這種講隔離級別,但就我個人的感覺來說,哪怕到現在,依然感覺非常別扭。
如果說要打破成見,莫過于了解下其他的東西,切切實實地了解其優缺點,做到在“應該用的地方去用”,在技術成長上,相信會有更好地進步。
另外,由于不同數據庫之間,雖然以外部視角看,都是SQL語言控制的數據庫,但內部實現的種種細節卻都是全然不同的,作為DBA來說,如果不想要讓自己的職業生命,被迫維系在某一個數據庫上的話(DB2前車之鑒,我當初差點入走了這條路),那么打開思路,去切實了解一下其他數據庫的種種特點,也不失為一個很好的防御措施。
作者:劉偉,云和恩墨軟件開發部研究院研究員;前微博DBA,主要研究方向為開源數據庫,分布式數據庫,擅長自動化運維以及數據庫內核研究。
參考
http://mysql.taobao.org/monthly/2015/12/05/
https://www.postgresql.org/docs/11/release.html
https://dev.mysql.com/doc/refman/8.0/en/replication-semisync.html
https://my.oschina.net/llzx373/blog/282768 mysql復制對事務的處理