Greenplum數據庫是一種大規模并行處理(MPP)數據庫服務器,其架構特別針對管理大規模分析型數據倉庫以及商業智能工作負載而設計。
MPP(也被稱為shared nothing架構)指有兩個或者更多個處理器協同執行一個操作的系統,每一個處理器都有其自己的內存、操作系統和磁盤。 Greenplum使用這種高性能系統架構來分布數T字節數據倉庫的負載并且能夠使用系統的所有資源并行處理一個查詢。
Greenplum數據庫是基于PostgreSQL開源技術的。它本質上是多個PostgreSQL面向磁盤的數據庫實例一起工作形成的一個緊密結合的數據庫管理系統(DBMS)。 它基于PostgreSQL 9.4開發,其SQL支持、特性、配置選項和最終用戶功能在大部分情況下和PostgreSQL非常相似。 與Greenplum數據庫交互的數據庫用戶會感覺在使用一個常規的PostgreSQL DBMS。
Greenplum數據庫可以使用追加優化(append-optimized,AO)的存儲格式來批量裝載和讀取數據,并且能提供HEAP表上的性能優勢。 追加優化的存儲為數據保護、壓縮和行/列方向提供了校驗和。行式或者列式追加優化的表都可以被壓縮。
Greenplum數據庫和PostgreSQL的主要區別在于:
- 在基于Postgres查詢規劃器的常規查詢規劃器之外,可以利用GPORCA進行查詢規劃。
- Greenplum數據庫可以使用追加優化的存儲。
- Greenplum數據庫可以選用列式存儲,數據在邏輯上還是組織成一個表,但其中的行和列在物理上是存儲在一種面向列的格式中,而不是存儲成行。 列式存儲只能和追加優化表一起使用。列式存儲是可壓縮的。當用戶只需要返回感興趣的列時,列式存儲可以提供更好的性能。 所有的壓縮算法都可以用在行式或者列式存儲的表上,但是行程編碼(RLE)壓縮只能用于列式存儲的表。Greenplum數據庫在所有使用列式存儲的追加優化表上都提供了壓縮。
為了支持Greenplum數據庫的并行結構,PostgreSQL的內部已經被修改或者增補。 例如,系統目錄、優化器、查詢執行器以及事務管理器組件都已經被修改或者增強,以便能夠在所有的并行PostgreSQL數據庫實例之上同時執行查詢。 Greenplum的interconnect(網絡層)允許在不同的PostgreSQL實例之間通訊,讓系統表現為一個邏輯數據庫。
Greenplum數據庫也可以使用聲明式分區和子分區來隱式地生成分區約束。
Greenplum數據庫也包括為針對商業智能(BI)負載優化PostgreSQL而設計的特性。 例如,Greenplum增加了并行數據裝載(外部表)、資源管理、查詢優化以及存儲增強,這些在PostgreSQL中都是無法找到的。 很多Greenplum開發的特性和優化都在PostgreSQL社區中找到了一席之地。例如,表分區最初是由Greenplum開發的一個特性,現在已經出現在了標準的PostgreSQL中。
Greenplum數據庫的查詢使用一種火山式查詢引擎模型,其中的執行引擎拿到一個執行計劃并且用它產生一棵物理操作符樹,然后通過物理操作符計算表,最后返回結果作為查詢響應。
Greenplum數據庫通過將數據和處理負載分布在多個服務器或者主機上來存儲和處理大量的數據。 Greenplum數據庫是一個由基于PostgreSQL 8.3的數據庫組成的陣列,陣列中的數據庫工作在一起呈現了一個單一數據庫的景象。 Master是Greenplum數據庫系統的入口。客戶端會連接到這個數據庫實例并且提交SQL語句。 Master會協調與系統中其他稱為Segment的數據庫實例一起工作,Segment負責存儲和處理數據。
Figure 1. 高層的Greenplum數據庫架構
下面的主題描述了組成一個Greenplum數據庫系統的組件以及它們如何一起工作。
Parent topic: Greenplum數據庫概念
關于Greenplum的Master
Greenplum數據庫的Master是整個Greenplum數據庫系統的入口,它接受連接和SQL查詢并且把工作分布到Segment實例上。
Greenplum數據庫的最終用戶與Greenplum數據庫(通過Master)交互時,會覺得他們是在與一個典型的PostgreSQL數據庫交互。 他們使用諸如<samp class="ph codeph" style="-webkit-font-smoothing: antialiased; box-sizing: border-box; font-family: monospace, monospace; font-size: 1em;">psql</samp>之類的客戶端或者JDBC、ODBC、 libpq(PostgreSQL的C語言API)等應用編程接口(API)連接到數據庫。
Master是全局系統目錄的所在地。全局系統目錄是一組包含了有關Greenplum數據庫系統本身的元數據的系統表。 Master上不包含任何用戶數據,數據只存在于Segment之上。 Master會認證客戶端連接、處理到來的SQL命令、在Segment之間分布工作負載、協調每一個Segment返回的結果以及把最終結果呈現給客戶端程序。
Greenplum數據庫使用預寫式日志(WAL)來實現主/備鏡像。 在基于WAL的日志中,所有的修改都會在應用之前被寫入日志,以確保對于任何正在處理的操作的數據完整性。
Note: Segment鏡像還不能使用WAL日志。
關于Greenplum的Segment
Greenplum數據庫的Segment實例是獨立的PostgreSQL數據庫,每一個都存儲了數據的一部分并且執行查詢處理的主要部分。
當一個用戶通過Greenplum的Master連接到數據庫并且發出一個查詢時,在每一個Segment數據庫上都會創建一些進程來處理該查詢的工作。 更多有關查詢處理的內容,請見關于Greenplum的查詢處理。
用戶定義的表及其索引會分布在Greenplum數據庫系統中可用的Segment上,每一個Segment都包含數據的不同部分。 服務于Segment數據的數據庫服務器進程運行在相應的Segment實例之下。 用戶通過Master與一個Greenplum數據庫系統中的Segment交互。
Segment運行在被稱作Segment主機的服務器上。 一臺Segment主機通常運行2至8個Greenplum的Segment,這取決于CPU核數、RAM、存儲、網絡接口和工作負載。 Segment主機預期都以相同的方式配置。 從Greenplum數據庫獲得最佳性能的關鍵在于在大量能力相同的Segment之間平均地分布數據和工作負載,這樣所有的Segment可以同時開始為一個任務工作并且同時完成它們的工作。
關于Greenplum的Interconnect
Interconect是Greenplum數據庫架構中的網絡層。
Interconnect指的是Segment之間的進程間通信以及這種通信所依賴的網絡基礎設施。 Greenplum的Interconnect采用了一種標準的以太交換網絡。出于性能原因,推薦使用萬兆網或者更快的系統。
B默認情況下,Interconnect使用帶流控制的用戶數據包協議(UDPIFC)在網絡上發送消息。 Greenplum軟件在UDP之上執行包驗證。這意味著其可靠性等效于傳輸控制協議(TCP)且性能和可擴展性要超過TCP。 如果Interconnect被改為TCP,Greenplum數據庫會有1000個Segment實例的可擴展性限制。對于Interconnect的默認協議UDPIFC則不存在這種限制。
Greenplum 的查詢處理
這個主題給出了Greenplum數據庫如何處理查詢的概述。理解這一處理有助于編寫和調優查詢。
用戶像對任何數據庫管理系統那樣將查詢發送到Greenplum數據庫。它們使用psql之類的客戶端應用連接到Greenplum的Master主機上的數據庫實例并且提交SQL語句。
Master接收、解析并且優化查詢。作為結果的查詢計劃可能是并行的或者定向的。如圖 Figure 1所示,Master會把并行查詢計劃分發到所有的Segment。而如圖 2Figure 2所示,Master會把定向查詢計劃分發到單一的一個segment實例。每個segment實例負責在其自己的數據集上執行本地數據庫操作。
大部分的數據庫操作(例如表掃描、連接、聚集和排序)都會以并行的方式在所有segment實例上執行。在一個segment實例的數據庫上執行的每個操作都獨立于存儲在其他segment實例數據庫中的數據。
Figure 1. 分發并行查詢計劃
某些查詢可能只訪問單個Segment上的數據,例如單行的INSERT, UPDATE, DELETE, 或者 SELECT操作或者以表分布鍵列過濾的查詢。在這些查詢中,segment實例,而是定向給到包含受影響或者相關行的segment實例。
Figure 2. 分發定向查詢計劃
理解Greenplum的查詢計劃
查詢計劃是Greenplum數據庫將要執行以產生查詢答案的操作集合。計劃中的每個節點或者步驟表示一個數據庫操作,例如表掃描、連接、聚集或者排序。計劃的讀取和執行按照從底向上的順序進行。
除通常的數據庫操作(例如表掃描、連接等等)之外,Greenplum數據庫還有一種額外的被稱為移動的操作類型。移動操作涉及到在查詢處理期間在segment實例之間移動元組。注意并非每一個查詢都需要移動操作。例如,定向查詢計劃就不需要通過Interconnect移動數據。
為了在查詢執行期間達到最大并行度,Greenplum將查詢計劃的工作劃分成切片。切片是Segment能夠在其上獨立工作的計劃片段。只要有一個移動操作出現在計劃中,該查詢計劃就會被切片,在移動的兩端分別有一個切片。
例如,下面涉及兩個表之間連接的簡單查詢:
SELECT customer, amount
FROM sales JOIN customer USING (cust_id)
WHERE dateCol = '04-30-2016';
Figure 3 展示了這個查詢計劃。每個segment實例接收一份查詢計劃的拷貝并且并行地根據計劃工作。
這個例子的查詢計劃有一個重分布移動,它在segment實例之間移動元組以完成連接。重分布移動是必要的,因為customer表在Segment上按照cust_id分布,而sales表是按照sale_id分布。為了執行該連接,sales元組必須按照cust_id重新分布。該計劃在重分布移動操作的兩邊被切換,形成了slice 1和slice 2。
這個查詢計劃由另一種稱為收集移動的移動操作。收集操作表示segment實例何時將結果發回給Master,Master再將結果呈現給客戶端。由于只要有移動產生查詢計劃就會被切片,這個計劃在其最頂層也有一個隱式的切片(slice 3)。不是所有的查詢計劃都涉及收集移動。例如,一個CREATE TABLE x AS SELECT...語句不會有收集移動,因為元組都被發送到新創建的表而不是發給Master。
Figure 3. 查詢切片計劃
理解并行查詢執行
術語說明:
查詢分發器(QD)
查詢執行器(QE)
Greenplum會創建若干數據庫進程來處理查詢的工作。在Master上,查詢工作者進程被稱作查詢分發器(QD)。QD負責創建并且分發查詢計劃。它也收集并且表達最終的結果。在Segment上,查詢工作者進程被稱為查詢執行器(QE)。
QE負責完成它那一部分的工作并且與其他工作者進程交流它的中間結果。
對查詢計劃的每一個切片至少要分配一個工作者進程。工作者進程獨立地工作在分配給它的那部分查詢計劃上。在查詢執行期間,每個Segment將有若干進程并行地為該查詢工作。
為查詢計劃的同一個切片工作但位于不同Segment上的相關進程被稱作團伙。隨著部分工作的完成,元組會從一個進程團伙流向查詢計劃中的下一個團伙。這種Segment之間的進程間通信被稱作Greenplum數據庫的Interconnect組件。
Figure 4 所示查詢計劃在Master和兩個Segment實例上的查詢工作者進行。
Figure 4. 查詢工作者進程
Tips: hash join
hash join是一種數據庫在進行多表連接時的處理算法,對于多表連接還有兩種比較常用的方式:sort merge-join 和 nested loop。
多表連接的查詢方式又分為以下幾種:內連接,外連接和交叉連接。外連接又分為:左外連接,右外連接和全外連接。對于不同的查詢方式,使用相同的join算法也會有不同的代價產生,這個是跟其實現方式緊密相關的,需要考慮不同的查詢方式如何實現,對于具體使用哪一種連接方式是由優化器通過代價的衡量來決定的,后面會簡單介紹一下幾種連接方式代價的計算。 hashjoin其實還有很多需要考慮和實現的地方,比如數據傾斜嚴重如何處理、內存放不下怎木辦,hash如何處理沖突等。
nested loop join
嵌套循環連接,是比較通用的連接方式,分為內外表,每掃描外表的一行數據都要在內表中查找與之相匹配的行,沒有索引的復雜度是O(N*M),這樣的復雜度對于大數據集是非常劣勢的,一般來講會通過索引來提升性能。
sort merge-join
merge join需要首先對兩個表按照關聯的字段進行排序,分別從兩個表中取出一行數據進行匹配,如果合適放入結果集;不匹配將較小的那行丟掉繼續匹配另一個表的下一行,依次處理直到將兩表的數據取完。merge join的很大一部分開銷花在排序上,也是同等條件下差于hash join的一個主要原因。
原理和實現
簡單的對于兩個表來講,hash-join就算講兩表中的小表(稱S)作為hash表,然后去掃描另一個表(稱M)的每一行數據,用得出來的行數據根據連接條件去映射建立的hash表,hash表是放在內存中的,這樣可以很快的得到對應的S表與M表相匹配的行。
對于結果集很大的情況,merge-join需要對其排序效率并不會很高,而nested loop join是一種嵌套循環的查詢方式無疑更不適合大數據集的連接,而hash-join正是為處理這種棘手的查詢方式而生,尤其是對于一個大表和一個小表的情況,基本上只需要將大小表掃描一遍就可以得出最終的結果集。
不過hash-join只適用于等值連接,對于>, <, <=, >=這樣的查詢連接還是需要nested loop這種通用的連接算法來處理。如果連接key本來就是有序的或者需要排序,那么可能用merge-join的代價會比hash-join更小,此時merge-join會更有優勢。
好了,廢話說了不少了,來講講實現,拿一條簡單的多表sql查詢語句來舉個栗子:select * from t1 join t2 on t1.c1 = t2.c1 where t1.c2 > t2.c2 and t1.c1 > 1。這樣一條sql進入數據庫系統中,它是如何被處理和解剖的呢?sql:鬼知道我都經歷了些什么。。。
1.背景知識
1.第一步呢,它需要經歷詞法以及語法的解析,這部分的輸出是一顆帶有token結點的語法樹。
語法分析,顧名思義這部分只是語法層面的剖析,將一個string的sql語句處理成為一顆有著雛形結構的node tree,每個結點有它們自身的特殊標識,但是并沒有分析和處理這個結點的具體含義和值。
2. 第二步是語義分析和重寫處理。
重寫的過程不同的數據庫可能有不同的處理,有些可能是跟邏輯執行過程放在一起,有的則分開。
這一步做完樹的形狀大體上是與語法分析樹保持一致的,但是此時的結點都攜帶了一些具體的信息,以where后面的表達式為例,這顆中綴表達式每一個結點都有了自身的類型和特定的信息,并不關心值是什么,這步做完后進入改寫過程,改寫是一種邏輯優化方式,使得一些復雜的sql語句變得更簡單或者更符合數據庫的處理流程。
3.優化器處理
優化器的處理是比較復雜的,也是sql模塊最難的地方,優化無止境,所以優化器沒有最優只有更優。優化器需要考慮方方面面的因素既要做的通用型很強又要保證很強的優化能力和正確性。
優化器最重要的作用莫過于路徑選擇了,對于多表連接如何確定表連接的順序和連接方式,不同的數據庫有著不同的處理方式,pg支持動態規劃算法,表數量過多的時候使用遺傳算法。路徑的確定又依賴于代價模型的實現,代價模型會維護一些統計信息,像列的最大值、最小值、NDV和DISTINCT值等,通過這些信息可以計算選擇率從而進一步計算代價。
回歸到正文,使用哪一種連接方式就是在這里決定的,hash join 對大小表是有要求的,所以這里形成的計劃是t1-t2還是t2-t1是不一樣的,每種連接方式有著自身的代價計算方式。
hash join的代價估算:
COST = BUILD_COST + M_SCAN_COST + JOIN_CONDITION_COST + FILTER_COST
簡單來看,hash join的代價主要在于建立hash表、掃描M表、join條件連接和filter過濾,對于S表和M表都是只需要掃描一次即可,filter過濾是指t1.c2>t2.c2這樣的條件過濾,對于t1.c1>1這樣只涉及單表的條件會被下壓,在做連接之前就被過濾了。
優化器處理過后,會生成一顆執行計劃樹,真正的實現過程根據執行計劃的流程操作數據,由低向上地遞歸處理并返回數據。
2.hash join的實現
hash join的實現分為build table也就是被用來建立hash map的小表和probe table,首先依次讀取小表的數據,對于每一行數據根據連接條件生成一個hash map中的一個元組,數據緩存在內存中,如果內存放不下需要dump到外存。依次掃描探測表拿到每一行數據根據join condition生成hash key映射hash map中對應的元組,元組對應的行和探測表的這一行有著同樣的hash key, 這時并不能確定這兩行就是滿足條件的數據,需要再次過一遍join condition和filter,滿足條件的數據集返回需要的投影列。
hash join實現的幾個細節
1.hash join本身的實現不要去判斷哪個是小表,優化器生成執行計劃時就已經確定了表的連接順序,以左表為小表建立hash table,那對應的代價模型就會以左表作為小表來得出代價,這樣根據代價生成的路徑就是符合實現要求的。
2.hash table的大小、需要分配多少個桶這個是需要在一開始就做好的,那分配多少是一個問題,分配太大會造成內存浪費,分配太小會導致桶數過小開鏈過長性能變差,一旦超過這里的內存限制,會考慮dump到外存,不同數據庫有它們自身的實現方式。
3.如何對數據hash,不同數據庫有著自己的方式,不同的哈希方法也會對性能造成一定的影響。
參考資料
https://www.cnblogs.com/shangyu/p/6055181.html
http://docs-cn.greenplum.org/v6/admin_guide/query/topics/parallel-proc.html#topic1