一、存儲格式介紹
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~