Amazon Aurora:高吞吐量的雲原生關係數據庫的設計考量

譯者:Aceking,極數雲舟技術合夥人,數據庫內核研發專家,負責企業級雲原生數據庫ArkDB等核心數據庫產品,華中科技大學計算機系數據庫方向研究生畢業,原達夢數據庫產品研發工程師,負責達夢數據庫內核開發,長期致力於數據庫原理和源碼開發,精通C/C++,公司內部流傳有他的名言:以後再也不敢說精通C++了。

摘要

Amazon Aurora是亞馬遜網絡服務(AWS)的一個組成部分,對外提供一個OLTP負載類型的關係數據庫服務。本文描述這種架構設計考量。我們確信,高吞吐量的數據處理的主要約束已經從計算、存儲轉移到網絡中來了。Aurora給出一個新穎的架構來解決這個約束,其最顯著的特點是把重做日誌(redo)處理下放到專門爲Aurora設計的存儲服務中。文章會介紹如何既能減少網絡流量,又能快速崩潰恢復(crash recovery), 還能實現複製失效不損失數據,以及存儲上的容錯,自愈。然後介紹Aurora在多存儲節點中的保持持久狀態的一致性的高效異步方案,避免了代價高昂繁複的恢復協議。最後,我們分享了18個多月運營Aurora產品的經驗,這些經驗是從現代雲應用的客戶們對數據庫層的期望中總結的。

關鍵字

數據庫; 分佈式系統; 日誌處理; 仲裁模型; 複製; 恢復; 性能; OLTP

01 導論

越來越多的IT負載轉移到公有云中,這種全行業的轉變的重大原因包括:公有云服務商能夠彈性按需提供容量,以及只需支付運營費用而不用支付資產費用的模式。許多IT負載需要一個OLTP的關係型數據庫。因此,提供一種等效甚至超越的預置數據庫用以支持這種轉變顯得至關重要。

越來越多現代分佈式雲服務,通過解耦計算與存儲,以及跨越多節點複製的方式實現了彈性與可伸縮性。這樣做,可以讓我們進行某些操作,例如替換失誤或者不可達的主機,添加複製節點,寫入節點與複製節點的故障轉移,拓展或者收縮數據庫實例的大小等等。在這種環境下,傳統的數據庫系統所面臨的IO瓶頸發生改變。因爲IO可以分散到多租戶集羣中的多個節點和多個磁盤中,導致單個磁盤和節點不在是熱點。相反,瓶頸轉移到發起這些IO請求的數據庫層與執行這些IO請求的存儲層之間的網絡中。除了每秒包數(packets per second , PPS)以及帶寬這種基本瓶頸外,一個高性能的數據庫會向發起存儲機羣並行的寫入,從而放大了網絡流量。某個異常存儲節點的性能,磁盤或者網絡通路,都會嚴重影響響應時間。

雖然數據庫大部分的操作可以互相重疊執行,但是有一些情況需要同步操作,這會導致停頓和上下文切換(可能是線程上下文切換,也可以是內核態與用戶態切換--譯者注)。情況之一,數據庫由於緩衝緩存(buffer cache)未命中,導致進行磁盤讀,讀線程無法繼續只能等待磁盤讀取完成。將髒頁強制趕出並且刷盤,用以給新頁騰出空間。也會導致緩存(cache)不命中。雖然後臺處理,例如檢查點,髒頁寫入線程能減少這種強制行爲,但是仍然會導致停頓,上下文切換以及資源爭用。

事務提交是另一個干擾源。一個正在提交的事務的停頓,會阻止另一個事務的進行。處理多節點同步協議的提交,如兩階段提交(2PC)對雲級規模(cloud-scale)的分佈式系統來說更是一個挑戰。這些協議不能容忍失敗。但是高規模(high-scale)總是充斥着軟硬件失效的“背景噪聲”。同時還有高延遲,因爲高規模系統總是跨多個數據中心分佈的。

本文中,我們介紹Amazon Aurora,一種新型的數據庫服務,通過大膽激進的使用高分佈性的雲環境中的日誌來解決上述問題。我們給出了一種使用多租戶可伸縮的存儲面向服務的架構(圖1),這種存儲服務與數據庫實例集羣鬆耦合,並且從中提取虛擬的分段的重做日誌(redo log).雖然每個數據庫實例仍然有大部分的傳統的數據庫內核(查詢處理,事務,鎖,緩衝緩存,訪問方法,回滾管理), 但是一些功能(重做日誌,持久存儲,崩潰恢復,備份恢復)已經剝離,放到存儲服務中。
Amazon Aurora:高吞吐量的雲原生關係數據庫的設計考量

我們的架構與傳統方法相比,有三大明顯優勢:首先,把存儲建爲一個容錯、自愈、跨多個數據中心的獨立服務,我們可以保護數據庫,使得網絡層或存儲層的性能差異,暫時或永久的失效,對數據庫不再有影響。我們觀察到持久存儲的失效可以作爲一個長時-可用性事件(longlasting availability event)來建模, 而可用性事件可以作爲一個長時性能變動來建模,長時性能變動已經有一個設計良好的系統統一處理。 其次,通過存儲寫日誌的方式,可以把網絡的IOPS減少一個量級。一旦我們移除了這個瓶頸,就可以大膽的在大量的其他爭用點上做優化,就能獲取比我們基於的MySQL基線源碼更明顯的吞吐量的提升。第三,我們把一些最重要最複雜的功能(如:備份、日誌恢復)從數據庫引擎中一次性代價高昂的操作轉變爲分攤到大型分佈式機羣的連續異步的操作。從而獲得近乎瞬時的崩潰恢復,無需檢查點,同時,備份的代價也不高昂,不會影響前臺處理。

本文介紹了我們的三點貢獻

如何分析雲級規模下的持久性,如何設計具有對相關失效具有彈性的仲裁系統。(段2)

如何使用靈巧的分離存儲,使得到存儲的負載低於傳統的1/4。(段3)

如何在存儲分佈式系統中消除多階段同步,崩潰恢復,檢查點。(段4)

我們把三個創意組合在一起,設計出Auraro總體架構(段5),隨後(段 6)檢視我們的性能結果,(段7)展示我們的運營經驗,(段8)概述相關的工作,(段9)給出結束語。

02 可伸縮的持久性

如果數據庫不做其他的事情,必須滿足如下要求:一旦寫入,必須可讀。不是所有系統可以做到這一點。在本段中我們討論了aurora仲裁模型背後的原理,爲什麼我們要將存儲分段,如何將持久性與可獲得性結合在一起,減少抖動,並且幫助解決大規模存儲機羣的運營問題。

2.1 複製與相關失效

實例的生命週期與存儲的生命週期並沒有太大的相關。實例失效,用戶會將其關閉,他們會根據負載情況調整其大小。這些特點有助於我們將計算層與存儲層解耦。

一旦你這樣做了,這些存儲節點和磁盤仍然會失效,因此需要某種形式的複製實現對失效的彈性。在規模大的雲環境下,存在持續的下層背景噪聲,如節點失效,磁盤、網絡路徑失效。每一種失效有不同的持續時間和破壞半徑。比如,某個節點暫時無法網絡到達,重啓導致的臨時停機時間,磁盤,節點,機架,網關的葉節點或者主幹節點, 甚至整個數據中心的永久失效。

在存在複製的系統中,實現容錯使用一種[]基於仲裁的投票協議。如果一個複製數據項有V個副本,每個副本可以賦予一個投票,一次讀操作或者寫操作分別需要Vr個投票的讀仲裁,或者Vw個投票的寫仲裁。那麼要達到一致性,仲裁必須兩個規則,首先,每次讀操作必須能獲得最新的修改。公式爲:

Vr+Vw > V,

這個規則,保證讀取的節點集與寫節點集有交集,讀仲裁至少能讀到一個最新版本的數據副本。其次,每次寫必須佔副本多數,用以避免寫衝突。即公式:

Vw > V/2,

一個能容忍複製數據項(V=3)一個節點損失的通用的方法是,寫需要2/3的投票仲裁(Vw=2),讀也需要2/3投票仲裁(Vr=2)。

我們確信2/3的投票仲裁數是不充分的,爲了解釋爲什麼,先了解AWS的一個概念,可用區(AZ,availability zone)是區域(region)的子集。一個可用區與其他可用區可以低延遲的連接,但是可以隔離其他可用區的故障,例如,電源,網絡,軟件部署,洪水等等。跨AZ的分佈複製數據可以保證典型的大規模故障模式隻影響一個複製副本,這意味着,可以簡單地放三個副本到在不同的可用區,就可以支持大範圍的事件容錯,但是少量的個別失效事件除外。

然而,在一個大的存儲機羣,故障的背景噪聲意味着,任意給一個時刻,都會有一個節點或者磁盤的子集可能壞掉並修復。這些故障可能在可用區A、B、C中獨立分佈。然而,可用區C由於火災、洪水、屋頂倒塌等等,與此同時,可用區A或者B也有故障(故障背景噪聲),就打破了任意一個副本的仲裁過程。在這一點上,2/3票數的讀取仲裁模型,已經失去了兩個副本,因此無法確定第三個副本是否是最新的。換一句話說,雖然,每個可用區的個體複製節點故障互不相關,但是一個可用區的失效,則與可用區所有的節點和磁盤都相關。仲裁系統要能容忍故障背景噪聲類型的故障與整個可用區的故障同時發生的情況。

在Aurora中,我們選用一種可以容兩種錯誤的設計:(a)在損失整個可用區外加一個節點(AZ+1)情況下不損失數據。

(b)損失整個可用區不影響寫數據的能力。

在三個可用區的情況下,我們把一個數據項6路寫入到3個可用區,每個可用區有2個副本。使用6個投票模型(V=6),一個寫仲裁要4/6票數(Vw=4),一個讀仲裁需要3/6票數(Vr=3),在這種模型下,像(a)那樣丟失整個可用區外加一個節點(總共3個節點失效),不會損失讀取可用性,像(b)那樣,損失2個節點,乃至整個可用區(2個節點),仍然保持寫的可用性。保證讀仲裁,使得我們可以通過添加額外的複製副本來重建寫仲裁。

2.2 分段的存儲

現在我們考慮AZ+1情況是否能提供足夠的持久性問題。在這個模型中要提供足夠的持久性,就必須保證兩次獨立故障發生的概率(平均故障時間,Mean Time to Failure, MTTF)足夠低於修復這些故障的時間(Mean Time to Repair, MTTR)。如果兩次故障的概率夠高,我們可能看到可用區失效,打破了衝裁。減少獨立失效的MTTF,哪怕是一點點,都是很困難的。相反,我們更關注減少MTTR,縮短兩次故障的脆弱的時間窗口。我們是這樣做的:將數據庫卷切分成小的固定大小的段,目前是10G大小。每份數據項以6路複製到保護組(protection Group,PG)中,每個保護組由6個10GB的段組成,分散在3個可用區中。每個可用區持有2個段。一個存儲卷是一組相連的PG集合,在物理上的實現是,採用亞馬遜彈性計算雲(ES2)提供的虛擬機附加SSD,組成的大型存儲節點機羣。組成存儲卷PG隨着卷的增長而分配。目前我們支持的卷沒有開啓複製的情況下可增長達64TB。

段是我們進行背景噪聲失效和修復的獨立單元。我們把監視和自動修復故障作爲我們服務的一部分。在10Gbps的網絡連接情況下,一個10GB的段可以在10秒鐘修復。只有在同一個10秒窗口中,出現兩個段的失效,外加一個可用區的失效,並且該可用區不包含這兩個段,我們纔可以看到仲裁失效。以我們觀察的失效率來看,幾乎不可能,即使在爲我們客戶管理的數據庫數量上,也是如此。

2.3 彈性的運營優勢

一旦有人設計出一種能對長時失效自然可復原(彈性)的系統,自然也對短時失效也能做到可復原。一個能處理可用區長時失效的系統,當然也能處理像電源事故這樣的短時停頓,或者是因糟糕的軟件部署導致的回滾。能處理多秒的仲裁成員的可用性的損失的系統,也能處理短時的網絡的阻塞,單個存儲節點的過載。

由於我們系統有高容錯性,可以利用這點,通過引起段不可用,做一些維護工作。比如 熱管理就直截了當,我們直接把把熱點磁盤或節點的一個段標記爲壞段,然後仲裁系統通過在機羣中遷移到較冷的節點來修復。操作系統或者安全打補丁在打補丁時,也是一個短時不可用事件。甚至,軟件升級也可以通過這個方式做。在某個時刻,我們升級一個AZ,但是確保PG中不超過一個成員(段或者節點)也在進行補丁。因此,我們的系統可以用敏捷的方法論在我們的存儲服務中進行快速部署。

03 日誌即數據庫

本段解釋爲什麼傳統的數據庫系統在採用段2所述的分段的複製存儲系統,仍難以承受網絡IO和同步停頓導致的性能負擔。我們解釋了將日誌處理剝離到存儲服務的方法,以及實驗證實這種方法如何顯著的降低網絡IO,最後講述了最小化同步停頓和不必要寫操作的各種技術。

3.1 寫放大的負擔

我們的存儲卷的分段存儲,以及6路寫入4/6仲裁票數的模型,具有高彈性。但是不幸的是,這種模型會讓傳統的數據庫如MYSQL無法承受這種性能負擔,因爲應用往存儲做一次寫入,會產生存儲中許多不同的真實IO。高量的IO又被複制操作放大,產生沉重的PPS(每秒包數,packets per second)負擔。同時,IO也會導致同步點流水線停滯,以及延遲放大。雖然鏈複製[8]以及其他替代方案能減少網絡代價,但是仍然有同步停頓以及額外的延遲。讓我們看看傳統的數據庫寫操作是怎麼進行的把。像Mysql這樣的系統,一邊要向已知對象(例如,heap文件,B-數等等)寫入頁,一邊要像先寫日誌系統(write-ahead log, WAL)寫入日誌。日誌要記錄頁的前像到後像的修改的差異。應用日誌,可以讓頁由前像變爲後像。
而實際上,還有其他的數據也要寫入。舉個例子,考慮一下如圖2所示以主從方式實現的同步鏡像mysql配置, 用以實現跨數據中心的高可用性。AZ1是主mysql實例,使用亞馬遜彈性塊存儲(EBS),AZ2是從Mysql實例,也用EBS存儲,往主EBS的寫入,均要通過軟件鏡像的方法同步到從EBS卷中。

Amazon Aurora:高吞吐量的雲原生關係數據庫的設計考量

圖2所示,引擎需要寫入的數據類型:重做日誌,binlog日誌,修改過數據頁,double-write寫,還有元數據FRM文件。圖中給瞭如下的實際IO順序:

步驟1步驟2,像EBS發起寫操作,並且會像本地可用區的EBS鏡像寫入,以及操作完成的響應消息。

步驟3,使用塊級鏡像軟件將寫入轉入到從節點。

步驟4和5從節點將傳入的寫入操作執行到從節點的EBS和它的EBS鏡像中。

上述MySQL的鏡像模型不可取,不僅僅因爲它如何寫入的,也因爲它寫進了什麼數據。首先,1,3,5步驟是順序同步的過程,由於有許多順序寫入,產生了額外的延遲。抖動也被放大,哪怕是在異步寫的時候,因爲它必須等待最慢的操作,讓系統任憑異常節點的擺佈。按照分佈式視角來看,這個模型可以視作4/4的寫仲裁,在失效或者異常節點的異常性能面前,顯得十分脆弱。其次,OLTP應用產生的用戶操作,可以產生許多不同類型的寫入,而這些數據寫入方式雖然都不同,但是卻表示同樣的信息。例如,往雙寫緩衝的寫入,主要是爲了防止存儲層頁的損壞,(但是內容與頁的普通寫入是一樣的)

3.2 日誌處理分離到存儲

傳統數據庫修改一個頁,就會產生日誌記錄。調用日誌應用器,應用日誌到內存中該頁數據的前像,得到該頁的後像。事務提交前日誌必須已經寫入,而數據頁則可推後寫入。在Aurora中,通過網絡的寫入只有重做日誌。數據庫層不寫入數據頁,也沒有後臺寫入,沒有檢查點,沒有Cache替換。相反,日誌應用器已經放置到存儲層,存儲層在後臺生成,或者按需生成數據庫的數據頁。當然,從頭開始應用所有修改的,代價讓人難以承受,因此,我們在後臺持續生成數據頁以避免每次抓取時在重新生成這些頁。從正確性的角度來看,後臺生成是完全可選的:就引擎而言,日誌即數據庫 ,存儲所有具體化出來的頁,都可以視作日誌應用的緩存。與檢查點不同,只有那些很長修改鏈的頁纔會要重新具化 ,檢查點由整個日誌鏈長度控制,而Aurora由給定頁的日誌鏈長度來控制。

儘管複製會導致的寫放大,我們的方法明顯能減少網絡負載,而且保證性能與持久性。在令人尷尬的並行寫入,存儲能超載IO而不影響數據庫引擎的寫吞吐能力。舉個例子,圖3所示,是一個一主多從Aurora集羣,部署在多個可用區中。在這個模型中,主庫往存儲寫入日誌,並且把這些日誌與元數據更新發送給從節點。基於公共存儲目標(一個邏輯段,比如,PG)的一批批日誌都是完全有序的。把每個批次以6路傳輸給複製節點,存儲引擎等待4個或4個多的迴應,用以滿足寫仲裁條件,來判斷日誌在持久上或硬化上是否有問題。複製節點應用這些日誌記錄來更新自身的緩存緩衝。

Amazon Aurora:高吞吐量的雲原生關係數據庫的設計考量

爲了測量網絡IO的狀況,我們在以上所述的兩種mysql配置下,進行了100GB的數據集只寫負載的sysbench[9]測試:一種是跨多個可用區的mysql鏡像配置,另一個是Aurora的RDS,在r3.8large EC2的實例中運行了30分鐘。

實驗結果如表1所示。在30分鐘的時間裏,在事務上,Aurora比mysql鏡像多35倍,雖然Aurora的寫放大了6倍,但是每個事務寫IO仍然比mysql鏡像小7.7倍。這裏並沒有記錄EBS的鏈式複製與mysql跨可用區的寫。每個存儲節點,只是6個複製節點中的一個,因此看不到寫放大。這裏的IO數量減少了46倍。寫入了更少的數據,省下的網絡能力,可以激進地通過複製實現持久性與可用性,並在發起並行請求時,最小化抖動的影響。
Amazon Aurora:高吞吐量的雲原生關係數據庫的設計考量

把日誌處理放到存儲服務中,也可以最小化崩潰恢復的時間,來提高可用性,並且消除檢查點、後臺寫、備份等後臺處理所帶來的抖動。我們再看崩潰恢復。傳統數據庫在崩潰之後,必須從最近的檢查點開始,將日誌重演並要報保證所有的保存的日誌都被應用。在Aurora中,持續的日誌應用發生在存儲中,並且它是持續的,異步的,分散在機羣中的。任何一次讀頁請求,如果該頁不是當前版本,則要應用一些重做日誌。因此,崩潰恢復過程已經分散到所有的正常的前臺處理中,不需要在數據庫啓動的時候執行。

3.3 存儲服務的設計點

我們的存儲服務設計的核心原則是最小化前臺寫請求的延遲。我們把大部分的存儲處理移到了後臺。鑑於存儲層前臺的請求峯值與平均值之間的自然變動,我們有足夠的時間在前臺請求之外進行這些處理。而且我們也有機會用cpu換磁盤。比如說,當存儲節點忙於前端請求處理,就沒有必要進行舊頁的垃圾回收(GC),除非該磁盤快滿了。Aurora中,後臺處理與前臺處理負相關。與傳統的數據庫不一樣,傳統數據庫的後臺寫頁、檢查點處理與系統的的前臺的請求量正相關。如果系統中有積壓請求,我們會抑制前端請求,避免形成長隊列。因爲系統中的段以高熵的形式(高混亂程度)分佈在不同的存儲節點中,限制一個存儲節點在4/6仲裁系統中自然的作爲慢節點處理。

現在以更多的細節來觀察存儲節點各種活動。如圖4所示,包含了如下若干步驟:

1、接收日誌記錄並且放入內存隊列中。

2、保存到磁盤並且返回應答。

3、組織日誌記錄,並且識別日誌中的空白,因爲有些批次的日誌會丟失。

4、與對等節點交流(gossip)填住空白。

5、合入日誌到新的數據頁。

6、週期地將新數據頁和日誌備份到S3。

7、週期的回收舊版本。

8、校驗頁中的CRC編碼。

注意,以上各個步驟不全是異步的,1 和 2 在前臺路徑中有潛在的延遲影響。

Amazon Aurora:高吞吐量的雲原生關係數據庫的設計考量

04 日誌前行

本段中,我們介紹數據庫引擎生成的日誌如何在持久狀態,運行狀態,複製狀態始終保持一致。特別是,如何不需要2PC協議高效地實現一致性。首先,我們展示瞭如何在崩潰恢復中避免使用高昂的重做處理。我們解釋正常操作中如何維護運行狀態與複製狀態。最後,我們披露崩潰恢復的細節。

4.1 解決方案草圖:異步處理

因爲我們把數據庫建模爲一個日誌流,事實上日誌作爲有序的修改序列,這點我們也要利用。實際上,每個日誌都有與之關聯的日誌序列號(Log Sequence Number, LSN), LSN是由數據庫產生的單調遞增的值。

這就可以使我們簡化共識協議,我們採用異步方式協議而不是2PC協議。2PC協議交互繁複並且不容錯。在高層次上,我們維護一致性與持久化的點,在接收還未完成的存儲請求的響應時推進這些點,並且是持續的推進。由於單獨的存儲節點可能會丟失一個或者多個日誌記錄,它們可以與同PG的其他成員進行交流,尋找空白並填補空洞。運行狀態是由數據庫維護的,我們可以直接從存儲段讀取而不需要讀仲裁。但在崩潰恢復時候,運行狀態已丟失需要重建的時候除外,這時候仍需要讀仲裁。

數據庫可能有很多獨立的未完成的事務,這些事務可以與發起時順序完全不同的順序完成(達到持久化的結束狀態)。假設數據庫奔潰或重啓,這些獨立事務是否回滾的決策是分開的。則跟蹤並撤銷部分完成事務的邏輯保留在數據庫引擎中,就像寫入簡單磁盤一樣。在重啓過程中,在允許數據庫訪問存儲卷之前,存儲進行自身的崩潰恢復,而不關心用戶層的事務。當然,要保證數據庫看到的存儲系統是單一的視圖,儘管實際上存儲是分佈式的。

存儲服務決定一個最高的LSN,保證在在這個LSN之前的日誌記錄都是可以讀取的。(VCL,volume Complete LSN)。在恢復過程中,對應LSN任何高於VCL的重做日誌都要丟棄。但是,數據庫還可以進一步約束條件,取其子集,用以標記哪些日誌可以丟棄。這個子集就是CPL集(Consistency Point LSNs)。因此,我們還可以定義一個VDL(volume durable LSN)爲小與VCL的最大CPL。舉個例子,雖然數據完成到LSN爲1007,但是數據庫標記的CPL集爲900,1000,1100。在這種情況下,我們丟棄的位置爲1000(1000以後的都要丟棄),也就是我們完成到1007(VCL),但是持久到1000(VDL)。

因此,完成性與持久性是不同的。CPL可以看作存儲事務必須有序接受的某種限制形式。如果客戶端沒有對此加以區別,我們把每個日誌記錄標記爲一個CPL。實際上,數據庫與存儲以如下方式交互:

每個數據庫層面的事務,都被打斷爲多個迷你事務(mini-transactions,MTRs),迷你事務是有序的,而且是原子的方式執行(就是不可分割地執行)。

一個迷你事務由多條連續的日誌記錄組成(要多少有多少)。

迷你事務的最後一條日誌記錄就是一個CPL

在崩潰恢復過程中,數據庫告訴存儲層爲每個PG建立一個持久點,用來建立一個VDL,然後發起丟棄高於VDL日誌的命令。

4.2 正常操作

我們現在描述數據庫的“正常操作”, 依次關注寫、讀、提交與複製。

4.2.1 寫

在Aurora中,數據庫不停地與存儲服務交互,並且維護狀態以建立仲裁,推進卷持久性(volume durablity),並且在提交時註冊事務。舉個例子,在正常/前轉路徑中,當數據庫接收到響應消息爲每批日誌建立寫仲裁,則推進當前VDL。在任意給定時刻,數據庫可能有大量的併發事務活動。每個事務均產生日誌,數據庫又爲每條日誌分配唯一的有序的LSN,但是分配受限於一條原則,LSN不能大於當前VDL與一個常量值的和。這個常量稱爲LSN分配限值(LAL, LSN Allocation limit)(目前設置爲一千萬)。這個限值保證數據庫的LSN不超出存儲系統太遠,在存儲或者網絡跟不上時,提供一個反壓,用以調節來入的寫請求。

注意,每個PG的每個段僅僅能看到存儲卷日誌記錄的子集,該子集的日誌僅僅影響駐留於該段的頁。每個日誌記錄包含一個指向PG中前一個日誌記錄的反向鏈接。這些反向鏈接可以跟蹤達到每個段的日誌記錄的完成點,用以建立段完成LSN(Segment Complete LSN, SCL), SCL是表示一個最大LSN,在該LSN之下所有的日誌記錄均已經被該PG接收到。存儲節點使用SCL相互交流,用來查找並交換獲得自己確實的那部分日誌。

4.2.2 提交

在Aurora中,事務提交是異步完成的。當客戶端提交一個事務,處理事務提交的線程記錄一個“提交LSN”(commit lsn)到一個單獨的等待提交的事務列表中,然後將事務擱置去處理其他任務。這相當於WAL協議是基於事務提交的完成,也即當且僅當最新的VDL大於或等於事務的提交LSN的時候。隨着VDL的推進,數據庫在正在等待提交事務識別符合條件的,由專有線程向正在等待的客戶端發送提交響應消息。工作線程不會因爲提交事務而暫停,它們僅是把其他的掛起的請求拉起繼續處理。

4.2.3 讀

在Aurora中,和大部分的數據庫一樣,頁由buf和cache提供。只有頁是否在Cache中還存疑的時候,纔會導致存儲IO請求。

如果buf cache已滿,系統找出受害頁,將其趕出cache。在傳統數據庫中,如果受害頁是“髒頁“,替換前要刷盤。這就保證後續的頁的提取,總是最新數據。然而Aurora在驅逐頁的時候並沒有將頁寫出,但它強制執行一個保證:在buff或cache中的頁始終是最新的版本。實現這一保證的措施是,僅僅將“頁LSN(Page LSN)“(與頁相關最新的日誌LSN)大於或者等於VDL。這個協議保證了:(a)頁的所有修改在日誌中都已硬化, (b)在cache沒命中的時候,獲得最近的持久化版本的頁,請求當前VDL版本頁就足夠了。

數據庫在正常情況下不需要使用讀仲裁來建立一致性。當從磁盤讀取一個頁,數據庫建立一個讀取點(read-point)

表示讀取發起時候的VDL。數據庫可以選擇一個相對於讀取點數據完整存儲節點,從而取得到最新版本的數據。存儲節點返回的數據頁一定與數據庫的迷你事務(mtr)的預期語義一致。因爲,數據庫管理着往存儲節點日誌的饋送,跟蹤這個過程(比如,每個端的SCL),因此它知道那些段滿足讀取要求(那些SCL大與讀取點的段)。然後直接向有足額數據的段發起讀請求。

考慮到數據庫知道那些讀操作沒有完成,可以在任何時間在每個PG的基礎上計算出最小的讀取點LSN(Minimum Read Point LSN). 如果有存儲節點交流寫的可讀副本,用來建立所有節點每個PG的最小讀取點,那麼這個值就叫做保護組最小讀取點LSN(Protection Group Min Read Point LSN, PGMRPL). PGMRPL用來標識“低位水線”,低於這個“低位水線”的PG日誌記錄都是不必要的。換句話說,每個存儲段必須保證沒有低於PGMRPL的讀取點的頁讀請求。每個存儲節點可以從數據庫瞭解到PGMRPL,因此,可以收集舊日誌,物化這些頁到磁盤中,再安全回收日誌垃圾。

實際上,在數據庫執行的併發控制協議,其數據庫頁和回滾段組織方式,與使用本地存儲的傳統數據庫的組織方式並無二致。

4.2.4 複製節點(replicas)

在Aurora中,在同一個存儲卷中,一次單獨的寫最多有15個讀複製。因此,讀複製在消耗存儲方面,並沒有增加額外的代價,也沒有增加額外的磁盤寫。爲了最小化延遲,寫生成的日誌流除了發送給存儲節點,也發送所有的讀複製節點。在讀節點中,數據庫依次檢查日誌流的每一天日誌記錄。如果日誌引用的頁在讀節點的緩衝緩存中,則日誌應用器應用指定的日誌記錄到緩存中的頁。否則,簡單丟棄日誌記錄。注意從寫節點的視角來看,複製節點是異步消費日誌流的,而寫節點獨立於複製節點響應用戶的提交。複製節點在應用日誌的時候必須遵從如下兩個重要規則:(a)只有LSN小於或等於VDL的日誌記錄才能應用。(b)只有屬於單個迷你事務(MTR)的日誌記錄才能被應用(mtr不完整的日誌記錄不能應用),用以保證複製節點看到的是所有數據庫對象的一致視圖。實際上,每個複製節點僅僅在寫節點後面延遲一個很短的時間(20ms甚至更少)。

4.3 恢復

大部分數據庫(如ARIES)採用的恢復協議,取決於是否採用了能表示提交事務所有的精確的內容的先寫日誌(write ahead log,WAL)。這些系統週期的做數據庫檢查點,通過刷髒頁到磁盤,並在日誌中寫入檢查點記錄,粗粒度地建立持久點。在重啓的時候,任何指定的頁都可能丟失數據,丟失的數據可能是已經提交了的,也可能包含未提交的。因此,在崩潰恢復的過程中,系統從最近的檢查點開始處理日誌,使用日誌應用器將這些日誌應用日誌到相關數據庫頁中。通過執行相應的回滾日誌記錄,可以回滾崩潰時正在運行的事務,這個過程給數據庫帶來了故障點時候的一致性狀態。崩潰恢復是一個代價高昂的操作,減少檢查點間隔可以減少代價,但是這會帶來影響前端事務的代價。傳統數據庫需要在兩者做權衡,而Aurora則不需要這樣的權衡。

傳統數據庫有一個重要的簡化原則是,使用同一個日誌應用器,推進數據庫狀態與進行同步的日誌恢復。並且此時從前端看來,數據庫庫是脫機的。Aurora設計也遵循同樣的原則。但是日誌應用器已經從數據庫中解耦出來,直接在存儲服務中在總是在後臺並行運行。一旦數據庫開始執行卷恢復,它會與存儲服務合作來進行。結果是,Aurora數據庫恢復速度非常快(通常在10秒以下),哪怕是每秒運行100,000條語句時候崩潰後情況下做恢復。

Aurora數據庫不需要在崩潰之後重建運行狀態。在崩潰的情況下,它會聯繫每個PG,只要數據滿足了寫仲裁的條件,段的讀仲裁完全可以保證能發現這些數據。一旦數據庫爲每個PG建立了讀仲裁,則可以重新計算VDL,生成高於該VDL的丟棄範圍(truncation range),保證這個範圍後的日誌都要丟棄,丟棄範圍是這是數據庫能可以證明的最大可能保證未完成的日誌(不是完整MTR的日誌)可見的結束LSN。然後,數據庫可以推斷出LSN分配的上限,即LSN的上限能超過VDL多少(前面描述的一千萬)。丟棄範圍使用時間數字(epoch number)作爲版本,寫在存儲服務中,無論崩潰恢復還是重啓,都不會弄混丟棄日誌的持久化。

數據庫仍然需要做undo恢復,用來回滾崩潰時正在進行的事務。但是,回滾事務可以在數據庫聯機的時候進行,在此之前,數據庫已經通過回滾段的信息,建立了未完成事務列表。

05 放在一起

本段中,我們描述如圖5所示Aurora的各個模塊。

Aurora庫是從社區版的mysql/innodb數據庫fork出來的分支,與之不同的主要在讀寫磁盤數據這塊。在社區版的Innodb中,數據的寫操作修改buffer中的頁,相應的日誌也按LSN順序寫入WAL的緩存中。在事務提交的時候,WAL協議只要求事務的日誌記錄寫入到磁盤。最終,這個被修改的緩存頁使用雙寫技術寫入到磁盤中,使用雙寫的目的是爲了避免不完整的頁寫(partial page writes)。這些寫操作在後臺執行,或者從cache驅逐頁出去時候執行,或者在檢查點時候執行。除IO子系統外,innodb還包含事務子系統,鎖管理器,B+樹的實現,以及“迷你事務”(MTR)。innodb的將一組不可分割執行(executed atomically)的操作稱爲一個迷你事務(例如,分裂/合併B+樹頁)。

Amazon Aurora:高吞吐量的雲原生關係數據庫的設計考量

在Aurora的Innodb變種中,mtr中原先那些不可分割執行日誌,按照批次組織,分片並寫入到其所屬的PG中,將mtr最後一個日誌記錄標記爲一致點(consistency point), Aurora在寫方面支持與社區mysql一樣的隔離等級(ANSI標準級別,Snapshot 隔離級,一致性讀)。Aurora讀複製節點可以從寫節點獲取事務開始和提交的持續信息,利用這些信息支持本地只讀事務的snapshot隔離級。注意,併發控制是完全有數據庫實現的,對存儲服務沒有任何影響。存儲服務爲下層數據提供了展現了一個統一視圖,與社區版本innodb往本地存儲寫數據的方式,在邏輯上完全等效。

Aurora使用亞馬遜關係數據庫服務(RDS)作爲控制平臺。RDS包含一個數據庫實例上的代理,監控集羣的健康情況,用以決定是否進行故障切換,或者是否進行實例替換。而實例可以是集羣中的一個,集羣則由一個寫節點,0個或者多個讀複製節點組成。集羣中所有的示例,均在同一個地理區域內(例如,us-east-1,us-west-2等)。但是放置在不同的可用區內。連接同一個區域的存儲機羣。爲了安全起見,數據庫、應用、存儲之間的連接都被隔離。實際上,每個數據庫實例都可以通過三個亞馬遜虛擬私有云(VPC)進行通信:用戶VPC,用戶應用程序可以通過它與數據庫實例進行交互。RDS VPC,通過它數據庫節點之間,數據庫與控制平臺之間進行交互。存儲VPC,用以進行數據庫與存儲之間的交互。

存儲服務部署在EC2虛擬機集羣中。這些虛擬機在一個區域內至少跨三個可用區,共同負責多用戶提供存儲卷、以及這些卷的讀寫、備份、恢復。存儲節點負責操作本地SSD,與對等節點或數據庫實例進行交互,以及備份恢復服務。備份恢復服務持續的將數據的改變備份到S3,並且依據需求從S3恢復。存儲控制平臺使用亞馬遜DynamoDB服務存儲集羣的永久數據、存儲卷配置、卷的元數據、備份到S3的數據的描述信息。爲了協調長時間的操作,如數據庫卷恢復操作,修復(重新複製)存儲節點失效等,存儲控制平臺使用亞馬遜簡單工作流服務(Amazon Simple Workflow Service)。維持高可用性,需要在影響用戶之前,主動地、自動地、儘早地檢測出真正的或潛在的問題。存儲操作各個關鍵的方面都要通過metric收集服務進行持續監控,metric會在關鍵性能、可用性參數值指示出現異常進行報警。

06 性能結果

在本段中,我們分享了自2015五月Aurora達到“GA”(基本可用)之後的運營經驗。我們給出工業標準的基準測試結果,以及從我們客戶獲得的性能結果。

標準基準測試結果

這裏展示了在不同的試驗下,Aurora與mysql的性能對比結果。實驗使用的是工業標準的基準測試,如sysbench和TPC-C的變種。我們運行的mysql實例,附加的存儲是有30K IPOS的EBS存儲卷,除非另有說明,這些mysql運行在有32 vCPU和244G內存的r3.8xlarge EC2實例中,配備了Intel Xeon E5-2670 v2 (Ivy Bridge) 處理器,r3.8xlarge的緩存緩衝設置到170GB。

07 學到的經驗

我們可以看到客戶運行的大量的各種應用,客戶有小型的互聯網公司,也有運行大量Aurora集羣的複雜組織。然而許多應用的都是標準的用例,我們關注於這些雲服務應用中共同的場景和期望,以引領我們走向新方向。

7.1 多租戶與數據庫整合

我們許多客戶運營軟件即服務(SaaS)的業務,有專有云業務客戶,也有將企業部署的遺留系統轉向SaaS的客戶。我們發現這些客戶往往依賴於一種不能輕易修改應用。因此,他們通常把他們自己的不同用戶通過一個租戶一個庫或模式的方式整合到一個單實例中。這種風格可以減少成本:他們避免爲每個用戶購買一個專有實例,因爲他們的用戶不可能同時活躍。舉個例子,有些SaaS的客戶說他們有50,000多用戶。

這種模式與我們衆所周知的多租戶應用如Saleforce.com不同,saleFore.com採用的多租戶模式是,所有用戶都在同一模式統一的表中,在行級記錄中標記租戶。結果是,我們看到進行數據庫整合的用戶擁有巨量的表。生產實例中擁有超過150,000個表的小數據庫非常的普遍。這就給管理如字典緩存等元數據的組件帶來了壓力。這些用戶需要(a)維持高水平的吞吐量以及併發的多用戶連接,(b)用多少數據就購買提供多少的模式,很難進一步預測使用多少存儲空間,(c)減少抖動,最小化單個用戶負載尖峯對其他租戶的影響。Aurora支持這些屬性,對這些SaaS的應用適配得很好。

7.2 高併發自動伸縮負載

互聯網負載需要處理因爲突發事件導致的尖峯流量。我們的主要客戶之一,在一次高度受歡迎的全國性電視節目中出現特殊狀況,經歷了遠高於正常吞吐量峯值的尖峯,但是並沒有給數據庫帶來壓力。要支持這樣的尖峯,數據庫處理大量併發用戶連接顯得十分的重要。這在Aurora是可行的,因爲下層存儲系統伸縮性如此之好。我們有些用戶運行時都達到了每秒8000個連接。

7.3 模式升級

現代web應用框架,如Ruby on Rails,都整合了ORM(object-relational mapping, 對象-關係映射)。導致對於應用開發人員來說,數據庫模式改變非常容易,但是對DBA來說,如何升級數據庫模式就成爲一種挑戰。在Rail應用中,我們有關於DBA的第一手資料:“數據庫遷移“,對於DBA來說是"一週做大把的遷移“,或者是採用一些迴避策略,用以保證將來遷移的不那麼痛苦。而Mysql提供了自由的模式升級語義,大部分的修改的實現方式是全表複製。頻繁的DDL操作是一個務實的現實,我們實現了一種高效的在線DDL,使用(a)基於每頁的數據庫版本化模式,使用模式歷史信息,按需對單獨頁進行解碼。(b)使用寫時修改(modify-on-write)原語,實現對單頁的最新模式懶更新。

7.4 可用性與軟件升級

我們的客戶對雲原生數據庫如何解決運行機羣和對服務器的補丁升級的矛盾,有着強烈的期望。客戶持有某種應用,而這些應用有主要使用Aurora作爲OLTP服務支持。對這些客戶來說,任何中斷都是痛苦的。所以,我們許多客戶對數據庫軟件的更新的容忍度很低,哪怕是相當於6周30秒的停機時間。因此,我們最近發佈了零停機補丁(zero downtime patch)特性。這個特性允許給用戶補丁但是不影響運行的數據庫連接。

如圖5所示,ZDP通過尋找沒有活動事務的瞬間,在這瞬間將應用假脫機到本地臨時存儲,給數據庫引擎打補丁然後更新應用狀態。在這個過程中,用戶會話仍然是活動的,並不知道數據庫引擎已經發生了改變。
Amazon Aurora:高吞吐量的雲原生關係數據庫的設計考量

08 相關工作

在本段中,我們討論其他人的貢獻,這些貢獻與Aurora採用的方法相關。

存儲與引擎解耦雖然傳統的數據都被設計成單內核的守護進程。但是仍有相關的工作,將數據庫內核解耦成不同的組成部分,例如,Deuteronomy就是這樣的系統,將系統分爲事務組件(Tranascation Component, TC)和數據組件(Data Componet ,DC).事務組件提供併發控制功能已經從DC中做崩潰恢復的功能,DC提供了一個LLAMA之上的訪問方法。LLAMA是一個無閂鎖的基於日誌結構的緩存和存儲管理系統。Sinfonia 和 Hyder則從可拓展的服務抽出了事務性訪問方法,實現了可以抽象使用這些方法的數據庫系統。Yesquel系統實現了一個多版本分佈式平衡樹,並且把將併發控制從查詢處理中獨立出來。Aurora解耦存儲比Deuteronomy、Hyder、Sinfonia更爲底層。在Aurora中,查詢處理,事務,併發,緩存緩衝以及訪問方法都和日誌存儲解耦,並且崩潰恢復作爲一個可拓展的服務實現的。

分佈式系統 在分區面前,正確性與可用性的權衡早已爲人所知,在網絡分區去情況下,一個副本的可串行化是不可能的,最近Brewer的CAP理論已經證明了在高可用系統在網絡分區的情況下,不可能提供“強”一致性。這些結果以及我們在雲級規模的經驗,激發了我們的一致性目標,即使在AZ失效引起分區的情況下也要一致性。

Brailis等人研究了高可用事務系統(Highly Available Transactions, HATs)的問題。他們已經證明分區或者高網絡延遲,並不會導致不可用性。可串行化、snapshot隔離級、可重複讀隔離級與HAT不兼容。然而其他的隔離級可以達到高可用性。Aurora提供了所有的隔離級,它給了一個簡單的假設,即任何時候都只有一個寫節點產生日誌,日誌更新的LSN,是在單個有序域中分配的。

Google的Spanner提供了讀和寫的外部一致性,並且提供了跨數據在同一個時間戳上全球一致性讀。這些特性使得spanner可以一致性備份,一致性分佈式查詢處理以及原子模式更新,這些特性都是全球規模的,哪怕是正在運行的事務的時候,也支持。正如Bailis解釋的一樣,Spanner是爲google的重-讀(read-heavy)負載高度定製的。採用兩階段提交,讀/寫事務採用兩階段鎖。

併發控制弱一致性與弱隔離級模型在分佈式數據庫中已爲人所知。由此產生了樂觀複製技術以及最終一致性系統在中心化的系統中,採用的其他的方法,有基於鎖的悲觀模式(),採用如多版本併發控制樂觀模式,如Hekaton,如VoltDB採用分片方法,Deuteronomy和Hyper採用時間戳排序的方法。而Aurora給數據庫系統提供了一個持久存在的抽象的本地磁盤,允許引擎自己處理隔離級與併發控制。

日誌結構存儲1992年LFS就引入了日誌結構存儲系統。最近Deuteronomy和LLMA的相關工作,以及Bw-tree以多種方式在存儲引擎棧中採用日誌結構技術,和Aurora一樣,通過寫入差量而不是寫整頁減少寫放大。Aurora與Deuteronomy都實現了純重做日誌系統,並且跟蹤最高持久化的LSN,用以確認提交。

崩潰恢復 傳統數據庫依賴於ARIES的恢復協議,現在有些數據庫爲了性能而採用了其他途徑。例如,Hekaton與VoltDB採用某種形式的更新日誌來重建崩潰後的內存狀態。像Sinfonia系統採用如進程對以及複製狀態機的技術來避免崩潰恢復。Graefe描述一種使用每頁日誌記錄鏈的系統,可以按需逐頁重做。可以提高崩潰恢復的速度。Aurora與Deuteronomy不需要重做崩潰恢復的過程。這是因爲Deuteronomy會將事務延遲,以便只有提交的事務的修改纔會寫入到持久存儲中。因而,與Aurora不同,Deuteronomy能處理的事務大小必然受到限制。

09 結論

我們設計出了高吞吐量的OLTP數據庫Aurora,在雲級規模環境下,並沒有損失其可用性與持久性。主要思想是,沒有采用傳統數據庫的單核架構,而是從計算節點中把存儲解耦出來。實際上,我們只從數據庫內核中移走了低於1/4的部分到獨立的彈性伸縮的分佈式服務中,用以管理日誌與存儲。由於所有的IO寫都要通過網絡,現在我們的基本的約束就在於網絡。所以我們關注於能緩解網絡壓力並能提高吞吐量的技術。我們使用仲裁模型來處理大規模的雲環境下的相關的複雜失效。並且避免了特殊節點帶來的性能懲罰。使用日誌處理減少總的IO負擔。採用異步共識方法消除多階段提交同步協議的繁複交互與高昂的代價,以及離線的崩潰恢復,和分佈式存儲的檢查點。我們的方法導致了簡化系統架構,能降低複雜性,易於伸縮能爲未來的發展打下基礎。

文章個人解讀

圖1中所示的Data Plane中,數據庫引擎那個框包住了Caching,而存儲服務那個框,也包住了Caching。耐人尋味。可以猜測,Cache是共享內存,數據庫引擎和存儲服務都可以訪問。而且Cache不光是數據庫引擎的一部分,而且也是存儲服務的一部分。全文大部分篇幅講如何優化寫請求帶來的網絡負擔,而隻字未提讀請求是否帶來網絡負擔。因此,可以大膽猜測,一次寫操作,不光是把日誌應用到磁盤存儲中,同時也把已經在cache的頁也應用了,因此大部分的讀請求不需要真實的存儲IO,除非該頁在cache中沒有命中。從段6的Aurora測試可以看到,Aurora實例擁有的緩存相當大,一般的應用軟件的數據庫數據都能放在內存中。從圖3也可以看到,AZ1主實例,也會向AZ2,AZ3數據庫從實例發送日誌與元數據,既然已經將日誌處理從數據庫引擎解耦出來了,發送的日誌由誰應用呢?個人認爲,AZ2與AZ3接收到日誌,仍然是有存儲服務應用的,只不過Cache是存儲服務與數據庫引擎共享的。

而且有這個猜測,我們進一步解讀零停機補丁的實現方式。ZDP如何保持用戶連接實現假脫機?個人認爲,應用到數據庫之間有proxy。使用proxy來保持應用程序的連接,並重新與更新後數據庫實例建立連接,而且要做到用戶會話無感覺,Cache是共享內存是必要的。因爲Cache保存了大部分數據庫運行狀態,重啓後的數據庫實例仍然繼續進行用戶會話的查詢。這個和使用共享內存的postgre有點像,你隨意殺死postgre某些進程,並不影響用戶會話的查詢。

此外,個人認爲,Aurora的計算節點,有可能也是存儲節點。雖然邏輯上是存儲與計算分離的,但是也可以享受不分離帶來的好處,比如,存儲服務可以直接往Cache應用日誌,使得數據庫引擎在大部分讀請求不需要真實的IO。

Aurora有沒有checkpoint?

Aurora的PGMRPL可以認爲是檢查點。LSN小於這個點的數據頁已經刷盤,而大於這個點的頁可以刷盤,也可以不刷盤,LSN小於這個點的日誌都可以丟棄。PGMRPL在邏輯上與存儲引擎的檢查點是等效的。可以認爲是PG上的檢查點。

Aurora與polardb

Aurora與Polardb都是存儲與計算分離、一些多讀、共享分佈式存儲的架構。很顯然,polardb在寫放大上面沒有做優化。從節點需要同步主庫的髒頁,共享的存儲,私有的髒頁,是個很難解決的矛盾。因此polardb的讀節點會影響寫節點。而Aurora可以認爲沒有髒頁。日誌即數據庫,可以把日誌應用器看作更慢的存儲,存儲服務與緩存都可以認爲是日誌應用器的Cache。

從節點與主節點之間有延遲,而aurora存儲的數據頁有多版本,文中明確指出存儲有舊頁回收處理。從節點依據讀取點LSN讀取到指定版本的頁,文中段4.2.4指出,寫節點到讀複製節點之間的延遲小於20ms,因此,不可回收的舊版本頁應該不會太多。

臨時表

每個讀節點雖然只有查詢操作,但是查詢操作會生成臨時表用以保存查詢的中間結果。生成臨時表數據是不會產生日誌的。但是這裏仍有寫IO,個人認爲,Aurora有可能直接寫在本地存儲,這樣不會產生網絡上的負擔。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章