開發(fā)分布式SQL數(shù)據(jù)庫的6種技術(shù)挑戰(zhàn)
我們在今年2月跨越了YugaByte DB三年開發(fā)階段,到目前為止,這是一段驚心動魄的旅程,但并非沒有公平的技術(shù)挑戰(zhàn)。有時我們不得不回到繪圖板,甚至篩選學(xué)術(shù)研究,以找到比我們手頭的更好的解決方案,在這篇文章中,我們將概述在構(gòu)建開源,云原生,高性能分布式SQL數(shù)據(jù)庫的過程中我們必須解決的一些最難的架構(gòu)問題。
好的,讓我們開始探討從最簡單到***挑戰(zhàn)性的問題:
1.架構(gòu):亞馬遜Aurora還是谷歌Spanner?
我們早期做出的一個決定是找到一個我們可以用作YugaByte DB架構(gòu)靈感的數(shù)據(jù)庫。我們密切關(guān)注兩個系統(tǒng),Amazon Aurora和Google Spanner。
Amazon Aurora是一個提供高可用性的SQL數(shù)據(jù)庫。它具有與流行的RDBMS數(shù)據(jù)庫(如MySQL和PostgreSQL)的兼容性,使其易于入門并可運(yùn)行各種應(yīng)用程序。Amazon Aurora也是AWS歷史上發(fā)展最快的服務(wù)之一。
Amazon Aurora服務(wù)與MySQL和PostgreSQL兼容,是AWS歷史上發(fā)展最快的服務(wù)。
Amazon Aurora具有可擴(kuò)展的數(shù)據(jù)存儲層,但查詢層不是這樣。以下是我們發(fā)現(xiàn)的Amazon Aurora的一些關(guān)鍵可擴(kuò)展性限制:
- 寫入不是水平可伸縮的。擴(kuò)展寫入吞吐量的唯一方法是垂直擴(kuò)展處理所有寫入的節(jié)點(diǎn)(稱為主節(jié)點(diǎn))。這種擴(kuò)展方案只是到目前為止,因此數(shù)據(jù)庫能處理多少寫入IOPS存在固有的限制。
- 寫入不是全局一致的。許多現(xiàn)代的云原生應(yīng)用程序本質(zhì)上是全局性的,需要跨多個區(qū)域部署底層數(shù)據(jù)庫。但是,Aurora僅支持多主機(jī)部署,在發(fā)生沖突時***一個寫入程序(具有***時間戳)獲勝。這可能導(dǎo)致不一致。
- 通過使用犧牲一致性的從屬副本以獲得讀取的伸縮擴(kuò)展。為了擴(kuò)展讀取,應(yīng)用程序需要連接到從屬節(jié)點(diǎn)才能實現(xiàn)讀取。當(dāng)使用這些從屬節(jié)點(diǎn)實現(xiàn)讀取時,應(yīng)用程序需要面對降級的一致性語義,以及一個單獨(dú)的連接端點(diǎn)。這使得應(yīng)用程序架構(gòu)非常復(fù)雜。
另外,Google Spanner是一個可水平擴(kuò)展的SQL數(shù)據(jù)庫,專為大規(guī)模可擴(kuò)展和地理分布式應(yīng)用程序而構(gòu)建。
Cloud Spanner是唯一為云構(gòu)建的企業(yè)級、全局分布且高度一致的數(shù)據(jù)庫服務(wù),專門用于將關(guān)系數(shù)據(jù)庫結(jié)構(gòu)的優(yōu)勢與非關(guān)系水平擴(kuò)展相結(jié)合。
這意味著Spanner可以無縫擴(kuò)展讀寫,支持需要全局一致性的地理分布式應(yīng)用程序,并在不犧牲正確性的情況下從多個節(jié)點(diǎn)執(zhí)行讀取。
但是,它放棄了RDBMS數(shù)據(jù)庫提供給開發(fā)人員期望的許多熟悉功能集。例如,Google Spanner文檔中突出顯示了不支持外鍵約束或觸發(fā)器的事實。
我們決定采用混合方法。
- YugaByte DB的核心存儲架構(gòu)受到Google Spanner的啟發(fā),該架構(gòu)專為水平可擴(kuò)展性和地理分布式應(yīng)用程序而構(gòu)建。
- YugaByte DB保留了與Amazon Aurora類似的PostgreSQL兼容查詢層,它可以支持豐富的功能集,并支持最廣泛的用例。
2. SQL協(xié)議:PostgreSQL還是MySQL?
我們想要對廣泛采用的SQL方言進(jìn)行標(biāo)準(zhǔn)化。我們還希望它是開源的,并且在數(shù)據(jù)庫周圍擁有成熟的生態(tài)系統(tǒng)。權(quán)衡的自然選擇是PostgreSQL和MySQL?
我們之所以選擇PostgreSQL(而不是MySQL),原因如下:
- PostgreSQL有一個更寬松的許可證,更符合YugaByte DB的開源精神。
- 與任何其他SQL數(shù)據(jù)庫相比,PostgreSQL在過去幾年中的流行度一直在飆升,這絕對沒有受到影響!
在目前排在DB-Engines排名網(wǎng)站前10位的五個SQL數(shù)據(jù)庫中,自2014年以來,只有PostgreSQL的受歡迎程度越來越高,而其他數(shù)據(jù)庫則趨于平穩(wěn)或正在失去理智。
此外,對于許多應(yīng)用程序,PostgreSQL是Oracle的***替代品。組織正在被PostgreSQL所吸引,因為它是開源的,供應(yīng)商中立(MySQL由Oracle擁有),擁有一個參與的開發(fā)者社區(qū),一個繁榮的供應(yīng)商生態(tài)系統(tǒng),一個強(qiáng)大的功能集,以及一個成熟的代碼庫,一直在戰(zhàn)斗 - 經(jīng)過20多年的嚴(yán)格使用而堅固。
3.分布式事務(wù):Google Spanner或Percolator?
關(guān)于我們應(yīng)該如何設(shè)計分布式事務(wù),我們查看了Google Spanner和Percolator。
總而言之,Google Percolator提供高吞吐量但使用單個時間戳。這種方法本質(zhì)上是不可擴(kuò)展的,僅適用于單個數(shù)據(jù)中心,面向?qū)崟r分析(稱為HTAP)的應(yīng)用程序,而不是OLTP應(yīng)用程序。另一方面,Google Spanner的分散時間跟蹤方法對于地理分布式OLTP和單數(shù)據(jù)中心HTAP應(yīng)用程序來說都是一個很好的解決方案。
Google Spanner是在Google Percolator之后構(gòu)建的,用于替換廣告后端中手動分片的MySQL部署,以實現(xiàn)水平可擴(kuò)展性和地理分布式用例。但是,考慮到其真正的分布式特性以及對時鐘偏移跟蹤的需求,Google Spanner的構(gòu)建難度要高一個數(shù)量級。
有關(guān)此主題的更多詳細(xì)信息,您可以詳細(xì)了解Percolator與Spanner的權(quán)衡。
我們決定采用Google Spanner方法,因為它可以支持:
- 更好的水平可擴(kuò)展性
- 高度可用且性能更佳的多區(qū)域部署。
我們堅信,大多數(shù)現(xiàn)代云應(yīng)用都需要上述兩種功能。實際上,GDPR和總共提供100個地區(qū)的公共云等合規(guī)性要求已經(jīng)使這成為現(xiàn)實。
4. Raft是否適用于地理分布式工作負(fù)載?
Raft和Paxos是眾所周知的分布式共識算法,并且已被正式證明是安全的,Spanner使用Paxos,但是,我們選擇了Raft,因為:
- 對于開發(fā)人員和運(yùn)營團(tuán)隊Raft比Paxos更容易理解。
- 它提供動態(tài)更改成員資格的能力,這是至關(guān)重要的(例如:在不影響性能的情況下更改機(jī)器類型)。(banq注:Raft與Paxos主要區(qū)別在于Raft候選人可以是任何一個服務(wù)器節(jié)點(diǎn),不需要專門指定候選人,否則這些候選人全部宕機(jī)怎么辦?如同一些TCC分布式事務(wù)中存在事務(wù)協(xié)調(diào)器一樣有單點(diǎn)風(fēng)險)
然而,為了確??删€性化的讀取,Raft要求接收讀取查詢的每個***在實際提供讀取查詢之前首先將心跳消息傳播到Raft組中的大多數(shù)節(jié)點(diǎn)。在某些情況下,這可能會嚴(yán)重降低讀取性能。這種情況的一個示例是地理分布式部署,其中往返會顯著增加延遲,并且在諸如臨時網(wǎng)絡(luò)分區(qū)之類的事件的情況下增加失敗查詢的數(shù)量。
為了避免Raft高延遲,我們實施了***的租賃機(jī)制,這將允許我們無需往返實現(xiàn)***服務(wù),同時保留了Raft的線性化特性。此外,我們使用單調(diào)時鐘而不是實時時鐘,以容忍時鐘偏差。
5.我們可以構(gòu)建軟件定義的原子鐘嗎?
作為分布式數(shù)據(jù)庫,YugaByte DB支持跨多個節(jié)點(diǎn)的多鍵ACID事務(wù)(快照和可序列化隔離級別),即使存在故障也是如此。這需要一個可以跨節(jié)點(diǎn)同步時間的時鐘。
Google Spanner使用TrueTime,這是一個具有嚴(yán)格錯誤界限的高可用性全局同步時鐘的示例。但是,許多部署中都沒有此類時鐘。
物理時鐘(或掛鐘)不能在節(jié)點(diǎn)之間***同步。因此,他們無法跨節(jié)點(diǎn)排序事件(建立因果關(guān)系)。除非存在中央時間戳權(quán)限,否則諸如Lamport時鐘和向量時鐘之類的邏輯時鐘不會跟蹤物理時間,這成為可擴(kuò)展性瓶頸。
我們的方案: 混合邏輯時鐘(HLC)通過將使用NTP粗略同步的物理時鐘與跟蹤因果關(guān)系的Lamport時鐘相結(jié)合來解決該問題。
YugaByte DB使用HLC作為高可用性群集寬時鐘,具有用戶指定的***時鐘偏差上限值。HLC值在Raft組中用作關(guān)聯(lián)更新的方式,也用作MVCC讀取點(diǎn)。結(jié)果是符合ACID的分布式數(shù)據(jù)庫,如Jepsen測試所示。
6.重寫或重用PostgreSQL查詢層?
***但同樣重要的是,我們需要決定是否重寫或重用PostgreSQL查詢層。
我們的初步?jīng)Q定
YugaByte數(shù)據(jù)庫查詢層在設(shè)計時考慮了可擴(kuò)展性。通過在C ++中重寫API服務(wù)器,已經(jīng)在這個查詢層框架中構(gòu)建了兩個API(YCQL和YEDIS),首先重寫PostgreSQL API似乎更容易和自然。
我們的最終決定
在我們意識到這不是一條理想的道路之前,我們沿著這條路走了大約5個月。與PostgreSQL成熟,完整的數(shù)據(jù)庫相比,其他API要簡單得多。然后我們重新完成整個工作,回到繪圖板并重新開始重新使用PostgreSQL的查詢層代碼。雖然這在開始時很痛苦,但回顧起來它是一個更好的策略。
這種方法也有其自身的挑戰(zhàn)。我們的計劃是首先將PostgreSQL系統(tǒng)表移動到DocDB(YugaByte DB的存儲層),最初支持一些數(shù)據(jù)類型和一些簡單查詢,并隨著時間的推移添加更多數(shù)據(jù)類型和查詢支持。
不幸的是,這個計劃并沒有完全解決。要從psql執(zhí)行看似簡單的最終用戶命令,實際上需要支持大量SQL功能。例如,\d用于列出所有表的命令在內(nèi)部執(zhí)行以下查詢:
- c.relname as "Name",
- CASE c.relkind
- WHEN 'r' THEN 'table'
- WHEN 'v' THEN 'view'
- WHEN 'm' THEN 'materialized view'
- WHEN 'i' THEN 'index'
- WHEN 'S' THEN 'sequence'
- WHEN 's' THEN 'special'
- WHEN 'f' THEN 'foreign table'
- END as "Type",
- pg_catalog.pg_get_userbyid(c.relowner) as "Owner"
- FROM pg_catalog.pg_class c
- LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
- WHERE c.relkind IN ('r','')
- AND n.nspname <> 'pg_catalog'
- AND n.nspname <> 'information_schema'
- AND n.nspname !~ '^pg_toast'
- AND pg_catalog.pg_table_is_visible(c.oid)
- ORDER BY 1,2;
WHERE支持操作符,例如IN,不等于,正則表達(dá)式匹配等。滿足上述查詢需要支持以下功能:
- CASE 條款
- 加入,特別是 LEFT JOIN
- ORDER BY
- 內(nèi)建等 pg_table_is_visible()
顯然,這代表了各種各樣的SQL功能,因此我們必須在創(chuàng)建單個用戶表之前使所有這些功能都可用!我們在Google Spanner架構(gòu)上發(fā)布分布式PostgreSQL - 查詢層突出顯示了查詢層的詳細(xì)工作方式。
結(jié)論
即使對于專家用戶來說,不得不在市場上可用的許多數(shù)據(jù)庫之間進(jìn)行選擇,一開始看起來似乎勢不可擋。這是因為為給定類型的應(yīng)用程序選擇數(shù)據(jù)庫取決于這些數(shù)據(jù)庫在其體系結(jié)構(gòu)中所做的權(quán)衡。
通過YugaByte DB,我們以一種新穎的方式組合了一組非常實用的架構(gòu)決策,以創(chuàng)建一個獨(dú)特的開源分布式SQL數(shù)據(jù)庫。PostgreSQL強(qiáng)大的SQL功能現(xiàn)在可供您使用,零數(shù)據(jù)丟失,水平寫入可擴(kuò)展性,低讀取延遲以及在公共云或Kubernetes中本機(jī)運(yùn)行的能力。