概述

在共享儲存一寫多讀的架構下,資料檔案實際上只有一份。得益於多版本機制,不同節點的讀寫實際上並不會衝突。但是有一些資料操作不具有多版本機制,其中比較有代表性的就是檔案操作。
多版本機制僅限於檔案內的元組,但不包括檔案本身。對檔案進行建立、刪除等操作實際上會對全叢集立即可見,這會導致RO在讀取檔案時出現檔案消失的情況,因此需要做一些同步操作,來防止此類情況。
對檔案進行操作通常使用DDL,因此對於DDL操作,PolarDB提供了一種同步機制,來防止併發的檔案操作的出現。除了同步機制外,DDL的其他邏輯和單機執行邏輯並無區別。

術語

  • LSN:Log Sequence Number,日誌序列號。是WAL日誌的唯一標識。LSN在全域性是遞增的。
  • 回放位點:Apply LSN,表示只讀節點的回放位點。

同步DDL機制

DDL鎖

同步DDL機制利用AccessExclusiveLock(後文簡稱DDL鎖)來進行RW/RO的DDL操作同步。

圖1 DDL鎖和WAL日誌的關係 DDL鎖是資料庫中最高階的表鎖,對其他所有的鎖級別都互斥,會伴隨著WAL日誌同步到RO節點上,並且可以獲取到該鎖在WAL日誌的寫入位點。當RO回放超過Lock LSN位點時,就可以認為在RO中已經獲取了這把鎖。DDL鎖會伴隨著事務的結束而釋放。如圖1所示,當回放到ApplyLSN1時,表示未獲取到DDL鎖;當回放到ApplyLSN2時,表示獲取到了該鎖;當回放到ApplyLSN3時,已經釋放了DDL鎖。 ![非同步回放ddl鎖.png](pic/46_DDL_2.png) 圖2 DDL鎖的獲取條件 當所有RO都回放超過了Lock LSN這個位點時(如圖2所示),可以認為RW的事務在叢集級別獲取到了這把鎖。獲取到這把鎖就意味著RW/RO中沒有其他的會話能夠訪問這張表,此時RW就可以對這張表做各種檔案相關的操作。 > 說明:Standby有獨立的檔案儲存,獲取鎖時不會出現上述情況。

圖3 同步DDL流程圖 圖3所示流程說明如下:

  1. RO會話執行查詢語句。
  2. RW會話執行DDL,在本地獲取DDL鎖並且寫到WAL日誌中,等待所有RO回放到該WAL日誌。
  3. RO的回放程序嘗試獲取該鎖,獲取成功後將回放位點返回給RW。
  4. RW獲知所有RO均獲取到該鎖。
  5. RO開始進行DDL操作。

如何保證資料正確性

DDL鎖是PG資料庫最高級別的鎖,當對一個表進行DROP/ALTER/LOCK/VACUUM(FULL) table等操作時,需要先獲取到DDL鎖。RW是通過使用者的主動操作來獲取鎖,獲取鎖成功時會寫入到日誌中,RO則通過回放日誌獲取鎖。

  • 主備環境:熱備存在只讀查詢,同時進行回放,回放到該鎖時,如果該表正在被讀取,回放就會被阻塞直到超時。
  • PolarDB環境:RW獲取鎖需要等待RO全部獲取鎖成功才算成功,因為需要確保主備都不再訪問共享儲存的資料才能進行DDL操作。

當以下操作的物件都是某張表,“<”表示時間先後順序時,同步DDL的執行邏輯如下:

  1. 本地所有查詢操作結束 < 本地獲取DDL鎖 < 本地釋放DDL鎖 < 本地新增查詢操作
  2. RW本地獲取DDL鎖 < 各個RO獲取本地DDL鎖 < RW獲取全域性DDL鎖
  3. RW獲取全域性DDL鎖 < RW進行寫資料操作 < RW釋放全域性DDL鎖

結合以上執行邏輯可以得到以下操作的先後順序:各個RW/RO查詢操作結束 < RW獲取全域性DDL鎖 < RW寫資料 < RW釋放全域性DDL鎖 < RW/RO新增查詢操作。
可以看到在寫共享儲存的資料時,RW/RO上都不會存在查詢,因此不會造成正確性問題。在整個操作的過程中,都是遵循2PL協議的,因此對於多個表,也可以保證正確性。

RO鎖回放優化

上述機制中存在一個問題,就是鎖同步發生在主備同步的主路徑中,當RO的鎖同步被阻塞時,會造成RO的資料同步阻塞(如圖1所示,回放程序的3、4階段在等待本地查詢會話結束後才能獲取鎖)。PolarDB預設設定的同步超時時間為30s,如果RW壓力過大,有可能造成較大的資料延遲。 RO中回放的DDL鎖還會出現疊加效果,例如RW在1s內寫下了10個DDL鎖日誌,在RO卻需要300s才能回放完畢。資料延遲對於PolarDB是十分危險的,它會造成RW無法及時刷髒、及時做檢查點,如果此時發生崩潰,恢復系統會需要更長的時間,這會導致極大的穩定性風險。

非同步DDL鎖回放

針對此問題,PolarDB進行了RO鎖回放優化。

圖4 RO非同步DDL鎖回放 優化思路:設計一個非同步程序來回放這些鎖,從而不阻塞主回放程序的工作。 整體流程如圖4所示,和圖3不同的是,回放程序會將鎖獲取的操作解除安裝到鎖回放程序中進行,並且立刻回到主回放流程中,從而不受鎖回放阻塞的影響。 鎖回放衝突並不是一個常見的情況,因此主回放程序並非將所有的鎖都解除安裝到鎖回放程序中進行,它會嘗試獲取鎖,如果獲取成功了,就不需要解除安裝到鎖回放程序中進行,這樣可以有效減少程序間的同步開銷。 該功能在PolarDB中預設啟用,能夠有效的減少回放衝突造成的回放延遲,以及衍生出來的穩定性問題。在AWS Aurora中不具備該特性,當發生衝突時會嚴重增加延遲。

如何保證資料正確性

在非同步回放的模式下,僅僅是獲取鎖的操作者變了,但是執行邏輯並未發生變化,依舊能夠保證RW獲取到全域性DDL鎖、寫資料、釋放全域性DDL鎖這期間不會存在任何查詢,因此不會存在正確性問題。

企業級分散式開源資料庫 PolarDB for PostgreSQL-阿里雲開發者社群