本文由云+社區發表本文作者:孫旭,騰訊數據庫開發工程師,9年數據庫內核開發經驗;熟悉數據庫查詢處理,并發控制,日志以及存儲系統;熟悉PostgreSQL(Greenplum,PGXC等)、Teradata等數據庫內核實現機制。
CynosDB 是騰訊數據庫研發團隊推出的自研數據庫,有PostgreSQL和MySQL兩個版本。本文以兼容PostgreSQL版CynosDB為例,介紹我們的架構設計和優化思路。
1、概述
PostgreSQL是世界上最先進的開源數據庫,始于1986年,有30多年的社區演進歷史。其先進的架構、可靠性以及豐富的功能已經獲得業界高度認可。同時,PostgreSQL能夠在多種操作系統上運行,支持多種索引類型和擴展,特別是對PostGIS擴展的支持,可以讓PostgreSQL輕松的處理地理信息數據。
兼容PostgreSQL版CynosDB作為PostgreSQL在NewSQL領域的一個產品,也具有良好的擴展性。由其架構特點帶來的資源池化,可以讓用戶付出更少的成本而獲得同等的性能,并且不損失PostgreSQL數據庫原有的功能特性。
2、基礎架構
現有共有云上的數據庫存在一些不足:
1.網絡IO重。傳統云上的主備架構下,會有大量數據需要寫到磁盤,主要包括:WAL LOG、臟頁數據、防止頁部分寫的Double Write或者Full Page Write。
2.主從實例不共享數據。一方面浪費了大量存儲,另一方面進一步加重了網絡IO。這樣會導致磁盤利用率低、CPU閑置等問題。
而CynosDB可以通過日志下沉、共享存儲來解決上述問題,以實現共有云數據庫的高性價比、高可用性以及彈性擴展。其基礎架構如下:
架構中的組件:
1. master是數據庫的主實例,負責接收應用的讀寫事務請求。
2. slave是數據庫的只讀實例,負責處理應用的讀請求,可以支持多個slave實例。
3. CynosStore Client提供訪問分布式存儲(CynosStore)的接口。DB引擎通過這些接口訪問存儲,完成數據文件的讀寫等操作。
4. CynosStore是一個分布式存儲系統,存放數據庫的數據和日志,并負責日志到數據頁面的轉換。
4. 集群管理服務負責整個系統的管理,例如:存儲擴容,實例創建等。
5. 冷備存儲用來存儲系統的日志。
master實例將數據的變更以日志方式發送到存儲系統(CynosStore)中,同時CynosStore會定期將日志合并到數據頁面上。因此,CynosDB無需將臟頁寫入到存儲中,這點與傳統數據庫是不同的。slave數據庫實例沒有寫事務,不會向存儲發送日志,但是會從存儲中讀取頁面,也會接收master實例的日志來刷新內存中的數據頁面;如果收到的日志所對應的頁面沒有在slave的內存中,則會丟棄這些日志。
從架構上看,CynosDB實現了存儲和計算分離,并把資源進行池化,因此適合云上部署。而且計算和存儲傳輸數據的僅有日志流,無需寫臟頁面,因此也減少了系統中的網絡量。總的來說,CynosDB具有如下優勢:
1.計算能力彈性擴展。可以快速增加slave節點來擴展讀能力,而不必進行全量的數據拷貝。
2. 存儲能力彈性擴展。不像傳統數據庫那樣受單機存儲能力的限制。
3. 充分利用硬件資源。緩解傳統主備架構中的CPU閑置、磁盤利用率不高等問題。
4. 備份容易。備份完全由后臺持續進行,用戶無需干預。
3、兼容PostgreSQL版CynosDB的計算層架構
CynosDB實現了計算與存儲分離,系統也因此被分成兩大塊:計算層和存儲層。計算層負責SQL解析、日志生成等;存儲層負責數據存儲、日志歸檔以及日志合并等。本節以CynosDB的PostgreSQL兼容版本為例來介紹計算層架構。其計算層架構如下圖所示。為了實現這種NewSQL架構,我們對PostgreSQL內核做了新設計:
灰色部分是PostgreSQL內核原生模塊:
1. SQL:PostgreSQL的SQL引擎,包括詞法/語法分析、語義分析、查詢重寫/優化和查詢執行。CynosDB的設計不涉及SQL層改動,因此它兼容PostgreSQL原來的SQL語法和語義。
2. Access:數據庫的訪問層,定義了對象的組織方式和訪問方法。其中包括:
lHeap:表實現以及訪問方法,包括掃描、更新、插入、刪除等。
lbtree/gin/gist/spgist/hash/brin:索引實現,包括各種索引的實現和操作方式,如索引掃描、插入等。
lCLOG/MultiXACT:與事務提交狀態以及并發等。
Access是設計和優化的重點模塊。當表和索引等數據庫對象被修改時,原生的PostgreSQL會生成XLog,并寫入到日志文件中。在CynosDB中,這些對象修改時也會生成日志,但是這些日志不會寫本地的日志文件,而是發送到CynosStore中。
3. storage/buffer:buffer pool和存儲管理,調用文件接口對數據文件進行讀寫。在CynosDB中使用CynosStore Client對CynosStore中的文件進行操作。
5. CynosStore Client提供訪問CynosStore的接口,以完成數據庫對數據文件的操作。包括數據頁面讀取接口、日志發送接口等。
6. 分布式存儲CynosStore是一個基于日志的分布式的塊存儲,在本文中不做重點介紹。
CynosDB的計算層把數據文件修改所生成的日志,通過CynosStore Client發送到分布式存儲CynosStore中,而CynosStore會將日志定時合并到數據頁面上。這里比較重要的一點是,計算層寫出日志并不是PostgreSQL原生的XLog,而是我們自己重新設計的日志系統和日志格式。因此CynosDB不依賴于PostgreSQL的原生日志系統,這種設計也可以讓我們有機會在CynosDB上做更多的性能優化。具體可以參見下節。
4、架構優化
CynosDB計算層的架構設計遵循了如下思路:
1.“極簡IO”。即,降低網絡/磁盤IO
2. 高效的系統設計。異步的日志設計、降低計算層CPU負載
通過這些設計,使CynosDB的性能比云上的同等配置性能要高。本節主要介紹計算層所做的優化手段。
4.1 日志系統
兼容PostgreSQL版CynosDB的底層存儲CynosStore是一個支持日志寫的、可以提供多版本讀的、分布式的塊設備,DB引擎對存儲中文件的修改,都是以日志的方式發送到存儲中。其日志格式是:<頁面號,頁面偏移,修改內容,修改長度>,含義是:在頁面的哪個偏移做了什么內容的修改。這樣設計的日志是冪等的。
以表插入元組為例,PostgreSQL原來的XLog日志格式可能是:
<relfilenode, pageno, offsetnum,informask2,infomask,hoff,tuple_data>:代表在頁面(由relfilenode和pageno來確定一個頁面)的offsetnum位置插入一條元組,插入的元組是在恢復時由informask2, infomask, hoff, tuple_data等信息進行重構。
同樣的操作,在CynosDB中生成的日志可能如下。假設在頁面號為n的頁面上插入元組tuple:
<n,10,(char *) &pd_flag,2> -- 保存頁面頭pd_flag到日志
<n,12,(char *) &pd_lower,2> -- 保存頁面頭pd_lower到日志
<n,14,(char *) &pd_upper,2> -- 保存頁面頭pd_upper到日志
<n,36,(char *) &ItemIdData,4> -- 保存ItemIdData數組的第3個元素到日志
<n,7488,(char *) tuple,172> -- 保存tuple到日志
這些條目記錄了頁面在插入元組時的所有修改,它們最終會在CynosStore Client中形成一個MTR(mini-transaction record:多條日志的集合,代表對數據庫存儲結構的一次原子修改,例如:btree結構、頁面結構的修改;在日志重放的時候需要將一個MTR的所有日志都應用完畢,否則會導致數據庫存儲結構的破壞),并放到日志流中發送到存儲。當存儲需要將這個MTR合并到頁面時,要保證MTR中的所有日志應用完畢,任何不完全的應用都會導致頁面結構不正確。
利用日志特點,我們對PostgreSQL 的內核進行了優化,而優化之后的日志大小開銷與PostgreSQL的原生XLOG差不多。這些優化和設計包括:
1. 移除原本PostgreSQL中full page write(FPW)特性。為了保證系統crash再重啟之后,那些部分寫的頁面(torn page)可以被正確恢復,PostgreSQL在Checkpoint之后,對頁面執行第一次被修改時,會將整個頁面記錄到日志中,這種特性就是FPW,類似MySQL的double write。當crash recovery時,系統會以這個全頁作為基頁面進行日志回放,并將恢復好的頁面寫到存儲,而不必關心存儲頁面中的頁面是否是半頁。由于CynosDB日志的冪等性,當出現半頁寫時,系統直接重新在此頁面上直接進行日志回放,即可將頁面修復到一致狀態。因此CynosDB中無需原生的FPW,從而減少了日志量。
2. 移除系統中臟頁面刷盤操作。CynosDB通過日志保存頁面的修改,并且可以通過在基頁上合并日志而得到最新頁面,因此無需原本系統的刷臟操作,僅僅刷日志就足夠。
通過如上優化,可以很大程度上減少網絡IO和日志量。
3. 除了以上對PostgreSQL內核的優化,CynosDB對日志的記錄方式也進行了精簡和壓縮。CynosDB的日志都有日志頭(LogHeader),如果修改同一個頁面的多條日志共享一個日志頭,則可以省去多個日志頭的開銷,如下圖所示:
LH代表LogHeader,Log Element代表對頁面的頁一次修改。如上圖,有兩條對Block1的修改日志,并且每個修改都有一個日志頭(LH),經過日志頭合并優化后,形成新的MTR中,修改Block1的那些日志共享了同一個日志頭。
如果修改同一個頁面的兩條日志是相鄰的,那么可以將兩條日志進一步合并成一條日志。這種方式減少了日志條目,從而可以提高日志合并和頁面生成速度。
4.2 頁面CRC
在PostgreSQL中,頁面在刷盤前會計算并填充頁面的CRC屬性,而在CynosDB中,如果為CRC也生成了一條日志寫入到存儲中的話,會增加計算節點的CPU負擔和日志條數。為了解決這個問題,我們將CRC的計算任務下放到存儲中,從而減輕了計算層的CPU負擔,以及日志條數。
4.3 異步表擴展
原生的PostgreSQL數據庫使用的是本地文件系統存儲數據,其文件擴展操作同步并實時的反映到磁盤文件上。但是CynosDB的擴展操作是通過日志實現,如果每次擴展都對日志做一次flush操作,讓擴展實時的反應到存儲上,勢必會影響系統的性能。因此,我們實現了文件的異步擴展,即文件擴展的日志先保留在系統的日志buffer中,而不是每次擴展都實時的刷新到存儲中,當事務提交的時候再把這些日志刷到存儲上,對數據批量導入的性能提升很明顯。另外,擴展操作可以一次性在文件中擴展出多個頁面,減少調用擴展操作的次數。
后續
后續我們會在新硬件、多Master架構等領域作更多探索,為云上的數據庫產品形態帶來更多驚喜和亮點。
此文已由作者授權騰訊云+社區發布