為什么規(guī)劃數(shù)據(jù)庫容量如此困難?那么怎么簡化?可以使用開源NoSQL數(shù)據(jù)庫ScyllaDB來演示示例。
調(diào)整數(shù)據(jù)庫的大小看起來很簡單:用數(shù)據(jù)集的大小和所需吞吐量除以節(jié)點(diǎn)的容量。很容易,不是嗎?
如果有人曾經(jīng)嘗試規(guī)劃數(shù)據(jù)庫容量,就會知道這有多難。即使是做出粗略的估計(jì)也很具挑戰(zhàn)性。那么為什么這么難?
以下是估算集群大小的步驟:
(1)對使用模式做出假設(shè)。
(2)估計(jì)所需工作量。
(3)決定數(shù)據(jù)庫的高級配置。
(4)將工作負(fù)載、配置和使用模式提供給數(shù)據(jù)庫的性能模型。
(5)收入。
這個流程雖然容易理解,但在實(shí)踐工作中卻不那么容易。
例如,在對數(shù)據(jù)庫配置(例如復(fù)制因子和一致性級別)做出決策時,做出的決策會受到預(yù)先設(shè)想答案的影響。而當(dāng)成本變得非常昂貴時,而進(jìn)行一些復(fù)制似乎有點(diǎn)過分。
將數(shù)據(jù)庫的規(guī)模調(diào)整看作一個設(shè)計(jì)過程,需要意識它是迭代的,并且支持需求和使用的發(fā)現(xiàn)和研究。與任何設(shè)計(jì)過程一樣,最佳規(guī)模在經(jīng)濟(jì)上和操作上都不理想,其投入的時間和資源也很有限。
在設(shè)計(jì)過程的簡單性和成本與準(zhǔn)確性之間存在內(nèi)在的權(quán)衡。畢竟,復(fù)雜的模型可以更好地預(yù)測數(shù)據(jù)庫性能,但其成本可能與構(gòu)建數(shù)據(jù)庫本身一樣高,而且需要太多的輸入,以至于其應(yīng)用不切實(shí)際。
帶來哪些問題?
數(shù)據(jù)庫的工作負(fù)載通常被描述為查詢的吞吐量和它必須支持的數(shù)據(jù)集大小。這將會引發(fā)出一些問題:
•這個數(shù)字是最大吞吐量還是平均值?(工作量通常有周期性變化和峰值)
•是否應(yīng)該分離某些類型的查詢?(例如讀取和寫入)
•如果還沒有使用這個數(shù)據(jù)庫,那么如何估計(jì)查詢的數(shù)量?數(shù)據(jù)集多大?
•熱門數(shù)據(jù)集是什么?數(shù)據(jù)庫存儲的數(shù)據(jù)比它們在任何給定時間點(diǎn)所能提供的數(shù)據(jù)多得多。
•數(shù)據(jù)模型呢?從經(jīng)驗(yàn)中知道,數(shù)據(jù)模型對查詢數(shù)量、性能和存儲大小有很大的影響。
•預(yù)期增長是多少?希望構(gòu)建一個可以隨工作負(fù)載擴(kuò)展的數(shù)據(jù)庫。
•查詢的服務(wù)等級目標(biāo)(SLO)是什么?設(shè)計(jì)的延遲目標(biāo)是什么?
有些人很幸運(yùn),擁有一個可以正常工作的系統(tǒng),也許還有運(yùn)行正常的數(shù)據(jù)庫,他們可以很容易地從中提取或推斷出這些問題的答案。但在通常情況下,一些被迫使用猜測方法和粗略計(jì)算。這并不像聽起來那么糟糕。例如,可以了解這個使用蒙特卡羅工具的模型,如下圖所示。
估計(jì)工作量很有趣。但是出于簡單的分級目的,人們感興趣的是吞吐量和數(shù)據(jù)集的最大值,并將只區(qū)分兩種查詢:讀取和寫入,其原因?qū)⒃诤竺娼忉尅?/p>
還可以假設(shè)對數(shù)據(jù)模型的控制程度很高。對于任何NoSQL數(shù)據(jù)庫,其目標(biāo)是通過一個查詢來處理所有需要的數(shù)據(jù)--如果用戶想優(yōu)化讀取或?qū)懭?,需要做出決定。
通常的基本步驟是:
(1)估計(jì)峰值數(shù)據(jù)集大小和工作負(fù)載。
(2)初步繪制數(shù)據(jù)模型,對優(yōu)化目標(biāo)進(jìn)行高層決策。
(3)根據(jù)數(shù)據(jù)模型估計(jì)讀/寫比率。
構(gòu)建數(shù)據(jù)庫的性能模型
數(shù)據(jù)庫的性能模型必須在一些有時相互沖突的需求之間取得平衡,它必須考慮足夠的性能和容量安全裕度,但需要在成本、可靠性與性能、持續(xù)和峰值負(fù)載之間實(shí)現(xiàn)平衡,但仍然可以簡化,即使在沒有特定應(yīng)用程序的情況下使用,同時提供明確的結(jié)果。
這確實(shí)是一項(xiàng)具有挑戰(zhàn)性的任務(wù)。但它可以簡單得多。例如,以下是它如何與Scylla一起工作,Scylla是一個兼容Apache Cassandra的開源NoSQL數(shù)據(jù)庫。
查詢vs操作
工作負(fù)載是根據(jù)查詢(通常是CQL)指定的。CQL查詢可能非常復(fù)雜,并生成數(shù)量不一的基本操作,這些操作的性能相對可預(yù)測。以下面的CQL查詢?yōu)槔?
1.SELECT*FROM user_stats WHERE id=UUID
2.SELECT*FROM user_stats WHERE username=USERNAME
3.SELECT*FROM user_stats WHERE city=”New York”ALLOW FILTERING
查詢#1將使用主鍵定位記錄,因此會立即在正確的分區(qū)上執(zhí)行--根據(jù)一致性級別,它仍然可能分解為幾個操作,因?yàn)閷⒉樵兌鄠€副本(稍后詳細(xì)介紹)。
盡管查詢#2看起來非常相似,但它會基于二級索引查找記錄,分解為兩個子查詢:一個子查詢到全局二級索引以查找主鍵,另一個子查詢從分區(qū)檢索行。此外,根據(jù)一致性級別,這可能會生成多個操作。
查詢#3甚至更極端,因?yàn)樗绶謪^(qū)掃描;它的表現(xiàn)將是糟糕的和不可預(yù)測的。
另一個例子是UPDATE查詢:
1.UPDATE user_stats SET username=USERNAME,rank=231,score=3432 WHERE ID=UUID
2.UPDATE user_stats SET username=USERNAME,rand=231,score=3432 WHERE ID=UUID IF EXISTS
查詢#1可能會直觀地分解為讀取和寫入這兩個操作--但在CQL UPDATE查詢中是UPSERT查詢,只會生成1個寫入操作。
然而,查詢#2盡管看起來很相似,但它不僅要求在所有副本上先讀取后寫入,而且還要求進(jìn)行編排的輕量級事務(wù)(LWT)。
類似地,查詢生成的磁盤操作數(shù)量可能會有很大的不同。大多數(shù)數(shù)據(jù)庫在排序字符串表(SSTable)存儲格式中使用日志結(jié)構(gòu)的合并樹(LSM)結(jié)構(gòu)。他們從不修改磁盤上的SSTable文件,它們是不變的。寫入被持久化到只追加提交日志以進(jìn)行恢復(fù),并寫入內(nèi)存緩沖區(qū)(memtable)。當(dāng)memtable變得太大時,它會被寫入磁盤上的一個新的SSTable文件,并從內(nèi)存中刷新。這使得寫路徑非常高效,但在讀取時引入了一個問題:數(shù)據(jù)庫必須在多個SSTables中搜索一個值。
為了防止這種讀取失控放大,數(shù)據(jù)庫定期將多個SSTables壓縮到數(shù)量更少的文件中,只保留最新的數(shù)據(jù)。這減少了完成查詢所需的讀取操作數(shù)量。
這意味著將磁盤操作歸因于單個查詢實(shí)際上是不可能的。磁盤操作的確切數(shù)量取決于SSTables的數(shù)量、它們的排列、壓縮策略等。開源的NoSQL數(shù)據(jù)庫旨在最大限度地利用存儲空間,所以只要磁盤速度足夠快并且不是瓶頸,就可以忽略這個維度。這就是推薦快速本地NVMe磁盤的原因。
雖然這個示例主要關(guān)注CQL,但預(yù)測查詢成本并不是唯一的問題。事實(shí)上,查詢語言越豐富、功能越強(qiáng)大,就越難以預(yù)測其性能。例如,由于SQL的強(qiáng)大功能,它的性能可能難以預(yù)測。因此,復(fù)雜的查詢優(yōu)化器是RDBMS的重要組成部分,它確實(shí)提高了性能,但其代價是使性能更加難以預(yù)測。這是NoSQL采取的另一種折衷方法:優(yōu)先考慮可預(yù)測的性能和可擴(kuò)展性,而不是功能豐富的查詢語言。
一致性的難題
分布式可用數(shù)據(jù)庫需要可靠地將數(shù)據(jù)復(fù)制到多個節(jié)點(diǎn)。每個鍵空間可以配置副本的數(shù)量,稱之為復(fù)制因子。從客戶端的角度來看,這可以同步發(fā)生,也可以異步發(fā)生,這取決于寫入的一致性級別。
例如,當(dāng)一致性級別為1時,數(shù)據(jù)最終會寫入所有副本,但客戶端只會等待一個副本確認(rèn)寫入。即使有些節(jié)點(diǎn)暫時不可用,數(shù)據(jù)庫也應(yīng)該在這些節(jié)點(diǎn)可用時緩存寫入和復(fù)制(這稱為暗示切換)。在實(shí)踐中,可以假設(shè)每個寫查詢都會在每個副本上生成至少一個寫入操作。
然而,對于讀取來說,情況有些不同。一致性級別為ALL的查詢必須從所有副本讀取數(shù)據(jù),從而生成與集群的復(fù)制因子一樣多的讀取操作,但一致性級別為1的查詢只從單個節(jié)點(diǎn)讀取數(shù)據(jù),從而實(shí)現(xiàn)更便宜、更快的讀取。這允許用戶以犧牲一致性為代價從集群中擠出更多的讀吞吐量,因?yàn)橐粋€節(jié)點(diǎn)在被讀取時可能還沒有獲得最近的更新。
最后,還有輕量級事務(wù)(LWT)需要考慮。如上所述,輕量級事務(wù)(LWT)需要利用Paxos算法對所有副本進(jìn)行編排。輕量級事務(wù)(LWT)不僅強(qiáng)制每個副本讀取然后寫入該值,而且還需要維護(hù)事務(wù)的狀態(tài),直到Paxos輪結(jié)束。由于輕量級事務(wù)(LWT)的行為方式不同,需要將其視為每個核心能夠支持的吞吐量的獨(dú)立性能模型。
所有操作都是平等的,但有些操作比其他操作更平等
現(xiàn)在已經(jīng)將CQL查詢分解為基本操作,那么可以詢問一些問題:每個操作需要多少時間(和資源)?CPU核心能支持的容量是多少?同樣,其答案有點(diǎn)復(fù)雜。作為一個例子,可以考慮一個簡單的讀取并遵循節(jié)點(diǎn)中的執(zhí)行步驟:
(1)在內(nèi)存表中查找值。
(2)在緩存中查找值。
(3)在SSTables中逐層查找值并合并值。
(4)響應(yīng)協(xié)調(diào)者。
顯然,如果它們在基于內(nèi)存的memtable或行級緩存中,讀取將更快地完成,這沒什么好奇怪的。此外,步驟#3和#4可能會產(chǎn)生更高的成本,這取決于從磁盤讀取、處理和通過網(wǎng)絡(luò)傳輸?shù)臄?shù)據(jù)的大小。如果需要讀取10MB的數(shù)據(jù),這可能需要相當(dāng)長的時間。這可能是因?yàn)榇鎯υ趩蝹€單元格中的值很大,也可能是因?yàn)榉秶鷴呙璺祷卦S多結(jié)果。但是,在值很大的情況下,Scylla不能將它們分成更小的塊,必須將整個單元格加載到內(nèi)存中。
一般來說,建議對數(shù)據(jù)進(jìn)行建模,使分區(qū)、行和單元不要增長得太大,以確保減少性能的差異。然而在現(xiàn)實(shí)中,總會有一些差異。
當(dāng)涉及到分區(qū)訪問模式時,這種差異尤其顯著。許多數(shù)據(jù)庫用于跨節(jié)點(diǎn)擴(kuò)展和傳播數(shù)據(jù)的策略是將數(shù)據(jù)分塊到彼此獨(dú)立的分區(qū)中。但獨(dú)立也意味著分區(qū)可能會經(jīng)歷不均勻的負(fù)載,導(dǎo)致所謂的“熱分區(qū)”問題,即單個分區(qū)會遇到節(jié)點(diǎn)容量限制,盡管集群的其余部分有足夠的資源。這個問題的發(fā)生在很大程度上取決于數(shù)據(jù)模型、數(shù)據(jù)集中分區(qū)鍵的分布以及工作負(fù)載中鍵的分布。由于預(yù)測熱分區(qū)通常需要分析整個數(shù)據(jù)集和工作負(fù)載,因此在設(shè)計(jì)階段是不切實(shí)際的,因此可以提供某些已知的分布作為模型的輸入,或者對分區(qū)的相對熱進(jìn)行一些假設(shè)。另外,nodetool toppartitions命令可以幫助定位熱分區(qū)。
物化視圖、二級索引和其他
數(shù)據(jù)庫具有自動二級索引和物化視圖以及變更數(shù)據(jù)捕獲(CDC)功能。這些表本質(zhì)上是由數(shù)據(jù)庫本身維護(hù)的輔助表,只允許使用一個寫操作以多種形式寫入數(shù)據(jù)。
而在幕后,Scylla根據(jù)用戶提供的模式派生要寫入的新值,并將這些新值寫入不同的表。在這方面,Scylla和RDBMS之間的主要區(qū)別在于,派生數(shù)據(jù)是異步寫入的,并且作為索引和物化視圖跨網(wǎng)絡(luò)寫入,而不局限于單個分區(qū)。這是另一個需要考慮的寫入的來源。在Scylla中,它是可以預(yù)測的,并且在性能上與用戶生成的操作相似。對于寫入的每一項(xiàng),CDC和從寫入單元格派生的每個物化視圖或二級索引都將觸發(fā)一次寫入。在某些情況下,物化視圖和CDC可能需要額外的讀取,例如,如果啟用了CDC的“預(yù)映像”功能,就會發(fā)生這種情況。另外需要記住的是,一個CQL查詢可以觸發(fā)多個寫操作。
CDC、二級索引和物化視圖被實(shí)現(xiàn)為由Scylla本身管理的常規(guī)表,但這也意味著它們消耗的磁盤空間與用戶表相當(dāng),因此必須在容量計(jì)劃中考慮到這一點(diǎn)。
高峰和數(shù)據(jù)庫維護(hù)
所有數(shù)據(jù)庫都需要執(zhí)行各種維護(hù)操作。RDBMS需要清理重做日志(例如Postgre SQL VACUUM),轉(zhuǎn)儲快照到磁盤(查看Redis),或執(zhí)行內(nèi)存垃圾收集(Cassandra、Elastic、HBase)。使用LSM存儲的數(shù)據(jù)庫(如Cassandra、HBase、Scylla)也需要定期壓縮SSTables,并將memtable刷新到新的SSTables中。
如果數(shù)據(jù)庫足夠智能,可以將壓縮和修復(fù)等維護(hù)操作推遲到稍后、負(fù)載更少的時間,那么可以在短時間內(nèi)獲得最佳性能。然而,最終將不得不為這些維護(hù)操作預(yù)留資源。這對于大多數(shù)系統(tǒng)來說非常有用,因?yàn)橐惶靸?nèi)的負(fù)載的分配并不是均勻的。但是,企業(yè)的計(jì)劃應(yīng)該為數(shù)據(jù)庫的持續(xù)長期操作提供足夠的容量。
此外,僅根據(jù)吞吐量進(jìn)行規(guī)劃是不夠的。在某種程度上,可以使數(shù)據(jù)庫過載以獲得更高的吞吐量,但其代價是更高的延遲。
在這個意義上,基準(zhǔn)往往具有誤導(dǎo)性,通常時間太短而無法達(dá)到有意義的持續(xù)運(yùn)行。延遲/吞吐量的權(quán)衡通常更容易度量,甚至在較短的基準(zhǔn)測試中也可以觀察到。
另一個重要但經(jīng)常被忽視的問題是降級操作。作為一個本地冗余和高可用的數(shù)據(jù)庫,Scylla被設(shè)計(jì)為平滑地處理節(jié)點(diǎn)故障(根據(jù)用戶設(shè)置的一致性約束)。但是,雖然故障在語義上是一致的,但這并不意味著它們是動態(tài)透明的,而其容量的顯著損失將影響集群的性能,以及故障節(jié)點(diǎn)的恢復(fù)或替換。在調(diào)整集群規(guī)模時也需要考慮這些因素。
選擇適當(dāng)規(guī)模的節(jié)點(diǎn)
由于Scylla的容量可以通過增加節(jié)點(diǎn)或選擇更大的節(jié)點(diǎn)來增加,一個有趣的問題出現(xiàn)了:應(yīng)該選擇哪種擴(kuò)展策略?一方面,更大的節(jié)點(diǎn)效率更高,因?yàn)榭梢元?dú)立于服務(wù)Scylla的內(nèi)核分配CPU核來處理網(wǎng)絡(luò)和其他任務(wù),并減少節(jié)點(diǎn)協(xié)調(diào)的相對開銷。另一方面,擁有的節(jié)點(diǎn)越多,當(dāng)其中一個節(jié)點(diǎn)出現(xiàn)故障時,損失的部分容量就越少--盡管丟失節(jié)點(diǎn)的概率稍微高一些。
對于非常大的工作負(fù)載,解決這個問題是沒有意義的,因?yàn)榇笮凸?jié)點(diǎn)是不夠的。但是對于許多較小的工作負(fù)載來說,3個中大型節(jié)點(diǎn)就足夠了。這個決定與工作量相關(guān)。但是,對于可靠性降級操作,建議至少運(yùn)行6~9個節(jié)點(diǎn)(假設(shè)復(fù)制因子為3)。
結(jié)論
容量規(guī)劃和調(diào)整集群規(guī)??赡芊浅?fù)雜和具有挑戰(zhàn)性。本文已經(jīng)討論了如何考慮安全裕度、維護(hù)操作和使用模式。重要的是要記住,任何猜測都只是迭代的初始估計(jì)。一旦投入生產(chǎn)并有了真實(shí)的數(shù)據(jù),可以讓它指導(dǎo)實(shí)施容量計(jì)劃。