Greenplum 性能優化之路 --(二)存儲格式


一、存儲格式介紹

Greenplum(以下簡稱 GP)有2種存儲格式,Heap 表和 AO 表(AORO 表,AOCO 表)。

  • Heap 表:這種存儲格式是從 PostgreSQL 繼承而來的,目前是 GP 默認的表存儲格式,只支持行存儲。

  • AO 表:  AO 表最初設計是只支持 append 的(就是只能 insert ),因此全稱是Append-Only,在4.3之后進行了優化,目前已經可以 update 和 delete 了,全稱也改為 Append-Optimized。AO 支持行存儲(AORO)和列存儲(AOCO)。

 

二、Heap表

Heap 表是從 PostgreSQL 繼承而來,使用 MVCC 來實現一致性。如果你在創建表的時候沒有指定任何存儲格式,那么 GP 就會使用 Heap 表。

Heap 表支持分區表,只支持行存,不支持列存和壓縮。需要注意的是在處理 update 和 delete 的時候,Heap 表並沒有真正刪除數據,而只是依靠 version 信息屏蔽老的數據,因此如果你的表有大量的 update 或者 delete,表占用的物理空間會不斷增大,這個時候需要依靠 vacuum 來清理老數據。

Heap 表不支持邏輯增量備份,因此如果要對 Heap 表做快照,每次都需要導出全量數據。

建表語句

CREATE TABLE heap(
	a int,
	b varchar(32)
) DISTRIBUTED BY (a);

  

最佳實踐:

  • 如果該表是一張小表,比如數倉中的維度表,或者數據量在百萬以下,推薦使用 Heap 表。

  • 如果該表的使用場景是 OLTP 的,比如有較多的 update 和 delete,查詢多是帶索引的點查詢等,推薦使用 Heap 表。

  •  

三、AO表

 

AO 表是 GP 特有的,設計的目的就是為了數倉中大型的事實表。AO 表支持行存和列存,並且也支持對數據進行壓縮。

AO 表無論是在表的邏輯結構還是物理結構上,都與 Heap 表有很大的不同。比如上文所述 Heap 表使用 MVCC 控制 update 和 delete 之后數據的可見性,而 AO 表則使用一個附加的 bitmap 表來實現,這個表的的內容就是表示 AO 表中哪些數據是可見的。

對於有大量 update 和 delete 的 AO 表,同樣需要 vacuum 進行維護,不過在 AO 表中, vacuum 需要對 bitmap 進行重置並壓縮物理文件,因此通常比 Heap 的 vacuum 要慢。

 

AORO表

AORO 就是行存的 AO 表,同時行存也是 AO 表的默認存儲方式。

AORO 支持表級別的壓縮,不支持列級別的壓縮。

建表語句如下,重點是 with 后的 appendonly=true,由於 AO 表默認是行存,因此 orientation=row 也可以不要,后面的 compresstype=zlib, compresslevel=4 都是壓縮相關選項。

CREATE TABLE aoro(
	a int,
	b int,
	c varchar(32),
	d varchar(32)
)
WITH (appendonly=true, orientation=row, compresstype=zlib, compresslevel=4)
DISTRIBUTED BY (a)

  

壓縮選項:

  • compresstype :壓縮格式,開源版本的 AORO 表只支持 zlib。

  • compresslevel :壓縮級別,從1-9,簡單說來,級別越低(1最低),壓縮比越低,但是壓縮與解壓消耗的 cpu 資源就越少。默認壓縮級別是1。

 

最佳實踐____________________:

  • AO 表主要是針對大表,比如數倉中的事實表。

  • AO 表支持邏輯增量備份,對於比較大的表,如果需要定期做快照,建議使用 AO 表,否則每次都要導出全量數據。

  • 如果該表是大表,使用場景偏 OLTP 並且 update 和 delete 頻率不高,可以考慮使用 AORO 表。

  • 如果該表是大表,並且查詢通常都需要掃描大多數列比如查詢明細(最典型的就是 SELECT * FROM ),可以考慮使用 AORO 表。

  • 在設置壓縮級別的時候,通常對於數據倉庫用戶,設置到 4 或者 5 是比較折中的一個選擇。

AOCO表

AOCO 表就是列存的 AO 表。

AOCO 不僅支持表級別的壓縮,同時也支持列級別的壓縮。

建表語句如下,這里還加入了分區特性,關於分區可以參見上一篇:Greenplum 性能優化之路 --(一)分區表

CREATE TABLE aoco(
	a int  ENCODING (compresstype=zlib, compresslevel=5),
	b int  ENCODING (compresstype=none),
	c varchar(32) ENCODING (compresstype=RLE_TYPE, blocksize=32768),
	d varchar(32),
	fdate date
)
WITH (appendonly=true, orientation=column, compresstype=zlib, compresslevel=6, blocksize=65536)
DISTRIBUTED BY (a)
PARTITION BY RANGE(fdate) 
(
	PARTITION pn START ('2018-11-01'::date) END ('2018-11-10'::date) EVERY ('1 day'::interval),
	DEFAULT PARTITION pdefault
);

  

壓縮選項:

  • compresstype:支持2種壓縮格式,zlib 和 RLE_TYPE,其中 RLE_TYPE(Run-length Encoding)對於有較多重復值的列壓縮比很高,因為它會將多個重復值存儲為一個值,從而大大降低存儲量,比如日期,性別,年齡等字段。

  • compresslevel:compresstype 如果是 zlib,compresslevel 在1-9,compresstype 如果是 RLE_TYPE,compresslevel 在1-4。

  • 列壓縮與表壓縮:AOCO 表除了支持表級別的壓縮外,還支持列級別的壓縮,列級別的壓縮配置會覆蓋表級別的壓縮配置,比如上述語法中4個字段,每個字段都采用了不用的壓縮方式,d 列沒有定義,則會默認使用表級別的壓縮方式。

  • 分區壓縮:在使用分區表的時候,每個分區表也可以設置不同的壓縮配置,這個常用於對數據進行冷熱分離,比如對於非常老的數據,由於訪問頻率較低,可以考慮采用較大的壓縮比,減少存儲量。

 

BLOCKSIZE:

  • 表的存儲塊大小,通常表數據對應的物理文件就是按 blocksize 的粒度增加,也就是初始就是 blocksize 大,並且保持 blocksize 的倍數。

  • blocksize 大小在8192和2097152之間,必須是 8192 的倍數,默認是 32768。

  • 在 AOCO 表中,每一列也可以設置自己的 blocksize,列的配置會覆蓋表的配置。

 

物理文件:

  • AOCO 表之所以能夠按照列來設置壓縮等參數,本質原因在於 AOCO 表中每一列的數據都會單獨存儲在一個文件中。因此不同文件之間可以按不同的參數進行存儲,互不影響。

  • 對於 AOCO 表,如果使用了分區,那么對於每一個分區的每一列都會有一個文件,如果一個表的分區很多,又是一張大寬表,那么產生的文件就會很多,也會對性能有一些影響。

 

最佳實踐:

  • AOCO 表通常用於數倉中的核心事實表,這種表字段多,數據量大,主要是用於 OLAP 場景,也就是查詢的過程不會 SELECT * FROM,而是對其中部分字段進行讀取和聚合。

  • 由於 AOCO 表一般用於大表,因此經常搭配壓縮和分區,以減少表的實際存儲量來提升性能。

  • 一般情況下,壓縮格式選擇 zlib,壓縮級別可以采用折中的 4 或者 5,但是對於有大量重復值的字段,記得要采用 RLE_TYPE 壓縮格式。

  • blocksize 不要設置過大,特別是對於分區表,GP 對於每個分區的每個字段都會維護一個 buffer,blocksize 過大,會導致消耗的內存過大,通常就采用默認值 32768 即可。

  •  

四、修改表結構

 

單獨列出這一節是因為 Heap,AORO 和 AOCO 這3種表在修改表結構時表現是不一樣的,這也是大家容易忽視的地方。

對於不同的表類型,同樣的修改語法耗時可能會差異很多,主要原因在於對於有些修改操作會導致表重寫,而表重寫的時間就取決於表本身的數據量。

以下列出了不同的表結構,在不同的 ALTER 語法下的行為,其中 YES 代表需要重寫表,NO代表不需要重寫表。

操作      

Heap        

AORO

AOCO

ADD COLUMN    

NO

YES

NO

DROP COLUMN    

NO

NO

NO

ALTER COLUMN TYPE    

YES

YES

YES

ADD COLUMN DEFAULT NULL    

YES

YES

NO

ADD COLUMN DEFAULT VALUE    

YES

YES

NO

可以看出 AOCO 表由於每個列都是單獨一個文件,因此在修改列結構時影響最小,這也是 AOCO 表的一個優勢。

 

五、混合存儲

 

一張表是否可以同時使用多種存儲方式呢?對於分區表,是可以的。

混合存儲一般用於這樣的場景,對於一張按時間分區的表,通常對於不同時間點的數據行為是不一樣的,比如對於最近的數據,會有較多的明細查詢,而對於比較老的數據,則是以分析為主。同時由於業務可能要保存較長時間的數據,為了節約成本,較老的數據會考慮使用壓縮比較大的存儲方式。

混合存儲的關鍵就是使用到了 GP 的交換分區語法,也就是將一張獨立的表與自己的一個分區表進行交換,當然這里前提是新表的結構是一樣,並且交換的過程沒有新數據進入。

 

流程如下:

1.創建一張分區表(1到5月份,每月一張表),采用 Heap 存儲

CREATE TABLE hyper_storage (
	a int,
	b varchar(32),
	fdate date
) DISTRIBUTED BY (a)
PARTITION BY RANGE(fdate) 
(
	PARTITION pn START ('2018-01-01'::date) END ('2018-06-01'::date) EVERY ('1 month'::interval),
	DEFAULT PARTITION pdefault
);

storage=# \d
                           List of relations
 Schema |             Name             | Type  |    Owner     | Storage
--------+------------------------------+-------+--------------+---------
 public | hyper_storage                | table | test | heap
 public | hyper_storage_1_prt_pdefault | table | test | heap
 public | hyper_storage_1_prt_pn_1     | table | test | heap
 public | hyper_storage_1_prt_pn_2     | table | test | heap
 public | hyper_storage_1_prt_pn_3     | table | test | heap
 public | hyper_storage_1_prt_pn_4     | table | test | heap
 public | hyper_storage_1_prt_pn_5     | table | test | heap

  

2.現在要對1月份的表修改存儲格式,因此創建一張新的 AOCO 表

CREATE TABLE exchange_table(
	a int,
	b varchar(32),
	fdate date
) WITH (appendonly=true, ORIENTATION=column, compresstype=zlib, compresslevel=6) 
DISTRIBUTED BY (a)

  

3.將1月份的數據導入新表

INSERT INTO exchange_table SELECT * FROM hyper_storage_1_prt_pn_1;

  

4.交換分區

ALTER TABLE hyper_storage EXCHANGE PARTITION pn_1 WITH TABLE exchange_table;

  

注:pn_1是1月份的分區表的 partitionname,可以從 pg_partitions 中查詢得到

5.查看結果

storage=# \d
                                  List of relations
 Schema |             Name             | Type  |    Owner     |       Storage
--------+------------------------------+-------+--------------+----------------------
 public | hyper_storage                | table | test | heap
 public | hyper_storage_1_prt_pdefault | table | test | heap
 public | hyper_storage_1_prt_pn_1     | table | test | append only columnar
 public | hyper_storage_1_prt_pn_2     | table | test | heap
 public | hyper_storage_1_prt_pn_3     | table | test | heap
 public | hyper_storage_1_prt_pn_4     | table | test | heap
 public | hyper_storage_1_prt_pn_5     | table | test | heap
public | exchange_table  | table | test | heap

  

這樣1月份的分區表就變成了 AOCO 表,而其他分區表仍然是 Heap 表

 

六、對比測試

 

各類型表占用空間比較

選取 Heap,AORO,AOCO 三種表,分別采用壓縮和不壓縮2種方式(Heap表不支持壓縮,AO 表壓縮采用zlib格式,壓縮級別設置為6),插入5億條隨機數據,然后使用

select pg_size_pretty(pg_relation_size('{tablename}'));

  

查看表所占大小,結果如下:

各類型表大小比較.png

 

說明:可以看出 Heap 表占用空間更大,即使 AO 表不采用壓縮。AOCO 表由於是按列進行存儲,所以相比行存的 AORO 表壓縮比更大。當然這三者的差距取決於數據的實際情況,一般生產環境中 Heap 表不會和 AO 表有如此大的差距。

各級別壓縮率比較

使用 AOCO 表,zlib 壓縮格式,選取不同的壓縮級別,比較數據寫入時間和表所占大小,由於 zlib 支持9個級別,這里選取1,6,9 三個級別進行比較,體現出趨勢即可,結果如下:

各壓縮級別比較.png

 

說明:實際生產環境中不同壓縮級別的數據,壓縮比的差距可能會更大。但可以看出,越高的壓縮級別,在插入的時候越耗時,其它 SQL,類似 SELECT,UPDATE 等也都是一樣。

寫在最后

切記,從其它系統遷移數據到 GP 上來,第一件事情就是給每張表選擇合適的存儲格式,特別是核心表。

 


 

 

關注“騰訊雲大數據”公眾號,技術交流、最新活動、服務專享一站Get~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM