本章將介紹一些 Greenplum 的高級特性,主要是與其他關係型數據庫有區別的地方。
當今的數據處理大致可以分成兩大類:聯機事務處理OLTP(On-Line Transaction Processing)、聯機分析處理OLAP(On-Line Analytical Processing)。OLTP是傳統的關係型數據庫的主要應用,主要是基本的、日常的事務處理。OLAP是數據倉庫系統的主要應用,支持複雜的分析操作,側重決策支持,並且提供直觀易懂的查詢結果。
表6-1列出OLTP與OLAP之間的比較
表6-1
表6-2
6.1 Appendonly 表與壓縮表
6.1.1 應用場景及語法介紹
壓縮表必須是Appendonly表,是隻能不斷追加的表,不能進行更新和刪除。
(1)壓縮表的應用場景
- 業務上不需要對錶進行更新和刪除操作,用truncate + insert 就可以實現業務邏輯
- 訪問表的時候基本上是全表掃描,不需要在表上建立索引
- 不能經常對錶進行加字段或修改字段類型,對Appendonly表加字段比普通錶慢很多
(2)語法介紹
建表的時候加上 with(appendonly=true) 就可以指定表是Appendonly表。如果需要建壓縮表,則加上 with(appendonly=true,compresslevel=5),其中compresslevel是壓縮率,取值爲1~9,一般選擇5就足夠。
6.1.2 壓縮表的性能差異
由於數據倉庫的大部分應用都是IO密集型的,每次的查詢基本上都是全表掃描,大量的順序讀寫。
一張簡單的商品表,總數據量500萬條,常見的數據類型有date、numeric、integer、varchar、text等,普通表與壓縮表性能比較結果如表6-3:
表6-3
6.1.3 Appendonly 表特性
對於每一張Appendonly表,創建的時候都附帶創建一張pg_aoseg模式下的額表,表名一般是 pg_aoseg_ + 表的oid,這張表中保存了這張Appendonly表的一些原數據信息。
這個表的每個字段信息如表6-4:
表6-4
在第四章中,介紹了一個函數 gp_dist_random,通過這個函數,可以查詢子節點的信息,可以知道gp 底層每個數據節點的數據量、數據文件大小的情況,這樣我們就能夠分析表的數據分佈情況、算出傾斜率等。
基於 pg_aoseg_ 這個表 :
- get_ao_compression_ratio:查詢壓縮表的壓縮率
- get_ao_distribution:查詢每個子節點的數據量
基於 Appendonly 表,開發了兩個 gp的函數:
- get_table_count:獲取Appendonly表的行數,Appendonly表有一個字段用於保存這些信息,支持分區表。
- get_table_info :用於獲取更加詳盡的表的信息,有表的行數、大小、數據傾斜率(最大節點數據量/平均節點數據量)、壓縮率。對於非分區表,就只有一行數據;對於分區表,但一行則是所有分區的彙總數據,其他是每一個分區的具體信息,效果如圖6-1:
圖6-1
6.1.4 相關數據字典
在gp3.* 中,aoseg表的信息記錄在 pg_class 中的relaosegrelid 字典中,可用以下sql進行查詢:
select relaosegrelid from pg_class where relname='test01';
在gp4.*中,pg_class的relaosegrelid 字段已經取消,aoseg表的信息保留在pg_appendonly 表中的segrelid字段中。
表6-5
6.2 列存儲
Appendonly表還有一種特殊的形式,就是列存儲。
6.2.1 應用場景
列存儲一般適用於寬表(即字段非常多的表)。在使用列存儲時,同一個字段的數據都連續保存在一個物理文件中,所以列存儲的壓縮率已普通壓縮表的壓縮率要高的多,另外在多數字段中篩選其中幾個字段時,需要掃描的數據量很小,掃描速度比較快。因此,列存儲尤其適合在寬表中部分字段進行篩選的場景。
6.2.2 數據文件存儲特性
列存儲在物理上存儲,一個列對應一個數據文件,文件名是原文件名加上 .n(n是一個數字)的形式來表示的,第一個字段,n=1,第二個字段n=129,以後每增加一個字段,n都會增加128.
爲什麼每增加一個字段,n不是遞增的而是中間要間隔128個數字呢?這大概是由於,在gp中,每一個數據文件最大是1GB,如果一個字段在一個Segment中的數據量超過1GB,就要重新生成一個新的文件,每個字段中間剩餘的這127個數字,就是預留給組隊內容很大,單個文件1GB放不下,拆分成多個文件時,可以使用預留的127個數字爲數據文件編號。
從這個物理的存儲特性就可以很明顯地看多列存儲的缺點,那就是會產生大量的小文件,假設一個集羣中有30個節點,我們建一張有100個字段寬表的,假設這張表是按照每天分區的一張分佈表,保留了一年的數據,那麼將會產生的文件數是:
即產生將近110萬的文件,這樣我們對一個表進行DDL操作就會比較慢,所以,列存儲應該在合適的場景下才能使用,而不能濫用列存儲,否則會得不償失
6.2.3 如何使用列存儲
列存儲的表必須是appendonly表,創建一個列存儲的表只需要在創建表的時候,在 with 子句中加入 appendonly=true,ORIENTATION=column 即可。一般 appendonly 表會結合壓縮一起使用,在 with 子句中增加 compresslevel=5 啓用壓縮.
6.2.4 性能比較
簡單比較一下列存儲和普通壓縮表在性能上的區別。由於列存儲主要應用在寬表上,故這次測試都是針對字段比較多的表。
測試結果:
表6-6
通過表6-6可以看出:
- 完全隨機的數據壓縮率比較低,使用兩種方式的壓縮結果差不多
- 當只查詢表的數據量的時候,普通壓縮表需要全表掃描,但是列存儲只需要查詢其中一個字段,速度非常快
- 隨着查詢字段的增加,普通壓縮表查詢的速度基本上變動不大,因爲掃描的數據量沒有變化。而列存儲則隨着查詢的字段增加,消耗時間增長明顯
- 在測試的時候瓶頸在於磁盤的IO,這是因爲測試集羣磁盤性能較差。在CPU消耗上,列存儲的消耗略大於普通表的消耗。
表6-7
採用這兩種數據方式加載數據時都使用外部表,加載時的瓶頸在於文件服務器的網卡,所以使用這兩種方式進行數據加載的速度都差不多
通過表6-7的結果可以看出,測試的結果與測試數據1的結果大致相同,區別主要是:
- 測試的數據是真實的數據,每個字段的內容都會有些相似,當每個字段的數據比較相似時,使用列存儲,這樣在壓縮文件時,可以獲得更高的壓縮率,節省磁盤的空間佔用。
- 瓶頸還是磁盤IO,由於列存儲有更好的壓縮率,所以在查詢全部字段的情況下,列存儲消耗的時間還是比普通壓縮表小很多的。
6.3 外部表高級應用
6.3.1 外部表實現原理
gpfdist 可以看做一個http服務,當我們啓動 gpfdist 時,可以用 wget 命令去下載 gpfdist 的文件,將創建外部表命令時使用的url地址中的gpfdist 換成 http 即可,如:
wget http://10.20.151.59:8081/inc/1.dat -o 2.dat
當查詢外部表時,所有的Segment都會連上 gpfdist,然後 gpfdist 將數據隨機分發給每個節點。
gpfdist 的架構如圖6-2 所示:
圖6-2
1、gpfdist 的工作流程
- 啓動 gpfdist,並在 Master 上建表。表建好之後並沒有任何的數據流動,只是定義好了外部表的原數據信息。
- 將外部表插入到一張 Gp 的物理表中,開始導入數據。
- Segment 根據建表時定義的 gpfdist url 個數,啓動相同的併發到 gpfdist 獲取數據,其中每個 Segment 節點都會連接到 gpfdist 上獲取數據。
- gpfdist 收到 Segment 的連接並要接收數據時開始讀取文件,順序讀取文件,然後將文件拆分成多個塊,隨機拋給 Segment。
- 由於 gpfdist 並不知道數據庫中有多少個 Segment,數據是按照哪個分佈鍵拆分的,因此數據是隨機發送到每個 Segment 上的,數據到達 Segment 的時間基本上是隨機的,所以外部表可以看成是一張隨機分佈的表,將數據插入到物理表的時候,需要進行一次重分佈。
- 爲了提高性能,數據讀取與重分佈是同時進行的,當數據重分佈完成後,整個數據導入流程結束。
2、gpfdist 最主要的功能
- 負載均衡:每個 Segment 分配到的數據都是隨機的,所以每個節點的負載都非常均衡。
- 併發讀取,性能高:每臺 Segment 都同時通過網卡到文件服務器獲取數據,併發讀取,從而獲取了比較高的性能。相對於 copy 命令,數據要通過 Master 流入,使用外部表就消除了 Master 這個單點問題。
3、如何提高 gpfdist 性能
想提高一個工具的性能,首先要了解這個工具使用的瓶頸在哪裏。對於數據導入,衡量性能最重要的就是數據分發時的吞吐,其中有兩個地方容易成爲瓶頸。
(1)文件服務器
一般文件服務器比較容易出現瓶頸,因爲所有的 Segment 都連接到這個節點上獲取數據,所以如果文件服務器是單機的,那麼就很容易在磁盤 IO 和網卡上出現瓶頸。
對於文件服務器,當磁盤 IO 出現瓶頸時:
- 使用磁盤陣列來提高磁盤的吞吐能力
- 採用分佈式文件系統來提高整體的性能
對於網卡出現瓶頸:
- 更換網卡爲萬兆網卡,需要各環節的網絡環節滿足條件,如交換機支持等
- 通過多網卡機制來保證。如圖6-3,將文件拆分成多個文件,並啓動多個 gpfdist 分別讀取不同的文件,然後將 gpfdist 綁定到不同的網卡上以提高性能。
圖6-3
在創建表的時候指定多個 gpfdist 的地址,例如:
CREATE EXTERNAL TABLE table_name
( column_name data_type [, ...] | LIKE other_table )
LOCATION ('gpfdist://filehostip1[:port1]/file_pattern1',
'gpfdist://filehostip2[:port2]/file_pattern2',
'gpfdist://filehostip3[:port3]/file_pattern3',
'gpfdist://filehostip4[:port4]/file_pattern4')
......
當然我們也可以只啓動一個 gpfdist,但是通過不同的 ip 連接到 gpfdist 上,這樣讀取文件的 gpfdist 只有一個,不能實現 IO 的併發,但是網卡卻可以使用多張,從而來消除單網卡的瓶頸。
6.3.2 可寫外部表
前面介紹的是隻讀外部表,在 gp 中,還有一種可寫外部表。
語法如下:
CREATE WRITABLE EXTERNAL TABLE table_name
( column_name data_type [, ...] | LIKE other_talbe )
LOCATION ('gpfdist://outputhost[:port]/fillename'[, ...]) | ('gpfdist://hdfs_host[:port]/path')
FORMAT 'TEXT'
[( [DELIMITER [AS] 'delimiter']
[NULL [AS] 'null string']
[ESCAPE [AS] 'escape' | 'OFF'] )]
| 'CSV'
[([QUETE [AS] 'quote']
[DELIMITER [AS] 'delimiter']
[NULL [AS] 'null string']
[FORCE QUOTE column [, ...]]
[ESCAPE [AS] 'escape'] )]
[ ENCODING 'write_encoding' ]
[DISTRIBUTED BY (column, [ ... ] ) | DISTRIBUTED RANDOMLY]
在語法上可寫外部表與普通外部表主要有兩個地方不一樣:
- 可寫外部表沒有錯誤表,不能指定允許有多少行數據錯誤。因爲外部表的數據一般是從數據庫的一張表中導出的,格式肯定是正確的,一般不會有異常數據。
- 可寫外部表在建表時可以指定分佈鍵,如果不指定分佈鍵,默認爲隨機分佈(distributed randomly)。儘量採用與原表一致的分佈鍵。
使用可寫外部表的注意事項:
- 可寫外部表不能中斷(truncate)。當我們將數據寫入可寫外部表時,如果可寫外部表中途斷開了要想重新運行必須手動將原有的文件刪除,否則那一部分數據會重複。
- 可寫外部表指定的分佈鍵應該與原表的分佈鍵一致,避免多餘的數據重分佈。
- gpfdist 必須使用 gp 4.x 之後的版本。可寫外部表是 gp 4.x 之後加入的新功能,在其他機器上啓動 gpfdist,只需要將 $GPHOME/bin/gpfdist 複製過去即可。
6.3.3 HDFS外部表
gp 可以直接讀取 HDFS 文件,這樣可以將 gp 和 HDFS 整合在一起,將 gp 作爲一個計算節點,計算後的數據可以直接寫入到 HDFS 中,提供對外的服務。HDFS 外部表的架構圖如圖6-4。
圖6-4
下面將對 gp4.2 以上版本的 HDFS外部表介紹。
在 gp 4.2 及以上版本安裝、配置 HDFS外部表的步驟如下:
- 在所有 Segment 上安裝 Java 1.6 或以上版本。
- gp 數據庫包括一下與 Hadoop 相關版本:
- Provatal HD 1.0(gphd-2.0):Hadoop 2.0 版本
- Greenplum HD(gphd-1.0,gphd-1.1 和 gphd-1.2):默認版本,使用其他組件可以通過設置 gp_hadoop_targetversion 這個參數進行切換。
- Greenplum MR 組件(gpmr-1.0 和 gpmr-1.2):Greenplum 版本的 Mapreduce。
- Cloudera Hadoop 版本連接(cdh3u2 和 cdh4.1):Cloudera 版本的 Hadoop。
- 需要讀者自己安裝 Hadoop(Gp 只支持以上所列的版本),安裝之後,需確保 gp 的系統用戶有讀和執行 Hadoop 相關 lib 庫的權限。
- 設置環境變量 JAVA_HOME、HADOOP_HOME。
- 設置 gp_hadoop_target_version 和 gp_hadoop_home 參數如表6-9:
- 重啓數據庫:在使用 HDFS外部表的時候
- 有兩層權限關係:
- 對 gp 的 HDFS 的使用用戶賦予 SELECT 和 INSERT 權限:GRENT INSERT ON PROTOCOL gphdfs TO gpadmin;
- 確保 gp 對應的 OS用戶有 Hadoop 上 HDFS 文件的讀寫權限。
- gp 在 HDFS 上支持三種文件格式:
- Text,文本格式,同時支持讀寫
- gphdfs_import,只支持只讀外部表。
- gphdfs_export,只支持可寫外部表。
- 有兩層權限關係:
表6-9
創建外部表進行讀取:
CREATE readable EXTERNAL TABLE hdfs_test_read (id int,name varchar(128))
LOCATION ('gphdfs://10.20.151.7:9000/data/gpext/hdfs_test.dat')
FORMAT 'TEXT' (DELIMITER ',');
select * from hdfs_test_read;
id | name
---------------
104 | john
101 | tom
103 | marry
102 | cat
在 $GPHOME/lib/hadoop 目錄下,gp 自帶了各種 Hadoop 版本的 jar 包,這些包中定義了 gphdfs_import 跟 gphdfs_export 的數據格式。gp 在讀寫這種格式的時候,需要自己編寫 MapReduce 任務。
6.3.4 可執行外部表
可執行外部表的語法如下:
CREATE [READABLE] EXTERNAL WEB TABLE table_name ( column_name data_type [, ...] | LIKE other_table )
LOCATION ('http://webhost[:port]/path/file' [, ...])
| EXECUTE 'command' [ON ALL
| MASTER
| number_of_segments
| HOST ['segment_hostname']
| SEGMENT segment_id ]
FORMAT 'TEXT'
[( [HERDER]
[DELIMITER [AS] 'delimiter' | 'OFF']
[NULL [AS] 'null string']
[ESCAPE [AS] 'escape' | 'OFF']
[NEWLINE [AS] 'LF' | 'CR' | 'CRLF']
[FILL MISSING FIELDS])]
| 'CSV'
[( [HEADER]
[QUOTE [AS] 'delimiter']
[NULL [AS] 'null string']
[FORCE NOT NULL column [, ...]]
[ESCAPE [AS] 'null string']
[NEWLINE [AS] 'LF' | 'CR' | 'CRLF']
[FILL MISSING FIELDS])]
[ ENCODING 'encoding' ]
[ [LOG ERRORS INTO error_talbe] SEGMENT REJECT LIMIT count
[ROWS | PERCEND] ]
在使用外部表時,每個 Segment 的參數可能會略有不同,或者是在腳本中需要獲取一些系統的參數。表6-10是 gp 自帶的一些常見參數,可以供腳本使用。
表6-10
表6-10-2
6.4 自定義函數——各個編程接口
6.4.1 pl/pgsql
相比起 Oracle 的 pl/sql ,pl/pgsql 主要有如下限制:
- 最大的子事務個數只能是100,每一個異常捕獲,都會造成一個子事務。
- 在函數執行的過程中不能執行 commit。
- 由於不能執行 commit,因此一個函數中不要處理太多的邏輯,否則容易導致程序出錯,這一點與 oracle 不一樣。如果有比較複雜的邏輯要以外部的 shell 調用來實現,不要在函數中實現。
- 所有的 pl/pgsql 都必須以函數的形式存在,沒有 oracle 中存儲過程的概念,所以每次使用都必須建立函數,然後再執行這個函數,使用起來不方便。
6.4.2 C 語言接口
6.4.2 plpython
6.5 Greenplum MapReduce
MapReduce 是 Google 提出的一種海量數據併發處理的一種編程思想,可以讓程序員在多態機器上快速的編寫出自己的代碼。MapReduce 的基本思想是將大數據量拆分成一行一行 (key,value) 對,然後分發給 Map 模塊,通過自定義的 Map 函數,生成新的 (key,value) 對,通過排序彙總,生成 (key,output_list) 發給自定義的 Reduce 函數處理,每一個 Reduce 函數處理一個唯一的 key 及這個 key 對應的所有 value 的列表,如果有需要,還可以將 Reduce 的結果作爲下一個 Map 的數據再進行計算。每一個機器都可以啓動多個 Map 和 Reduce 來計算結果。
gp MapReduce 允許用戶自己編寫 map 和 reduce 函數,然後提交給 gp 併發處理引擎處理,gp 在分佈式的環境下允許 MapReduce 代碼,並處理所有Segment 的報錯信息。
gp 中的 MapReduce 基本框架如圖:
圖6-5
MapReduce 的組成部分:
表6-11
在 gp 中,執行 MapReduce 函數需要編寫 yaml 文件。在 yaml 文件中,主要有以下幾個部分需要定義:
1、INPUT
INPUT 有多種類型:可以是一個外部文本數據文件、一個數據庫中的表或查詢等。
(1)外部文件
- INPUT:
NAME: my_file_input
FILE: seghostname:/var/data/gpfiles/employees.txt
COLUMNS
- first_name text
- last_name text
- dept text
FORMAT: TEXT
DELIMITER: |
(2)數據庫表
- INPUT:
NAME: my_table_input
TABLE: sales
(3)數據庫查詢
- INPUT:
NAME: my_query_input
QUERY: SELECT vendor, amt FROM sales WHERE region='usa';
(4)通過 gpfdist 導入外部文件
- INPUT:
NAME: my_distributed_input
# specifiles the host,port and the desired files served
# by gpfdist. /* denotes all files on the gpfdist server
GPFDIST:
- gpfdisthost:8080/*
COLUMNS
- first_name text
- last_name text
FORMAT: TEXT
DELIMITER: '|'
(5)操作系統命令
- INPUT:
NAME: my_query_input
EXEC: /var/load_scripts/get_log_data.sh
COLUMNS
- url text
- date timestamp
FORMAT: TEXT
DELIMITER: '|'
類型(4)和(5)也可以採用在數據庫中建成外部表的形式,就是可以當成數據庫中的表操作,跟(2)一樣,這兩個類型的功能跟外部表有點重複。
2、Map
Map 是輸入一行數據,有 0 個或多個數據輸出,可以用 python 或 perl 實現。
3、Reduce
也可以用 python 或 perl 實現。
gp 提供幾個預定義 Reduce 函數:
IDENTITY - 返回沒有標紅的 (key,value) 對
SUM - 計算數字類型的和
AVG - 計算數字類型的平均值
COUNT - 計算輸入數據的count數
MIN - 計算數字類型的最小值
MAX - 計算數字類型的最大值
(函數對 value 進行相應的操作)
4、OUTPUT
OUTPUT 可以是數據表也可以是文本文件:
(1)輸出到文本文件
- OUTPUT:
NAME: gpmr_output
FILE: /var/data/mapreduce/wordcount.out
(2)輸出到數據表
- OUTPUT
NAME: gpmr_output
TABLE: wordcount_out
KEYS:
- value
MODE: REPLACE(注:若是放此表並重建表,則用 REPLACE,若是想追加輸出的數據,則用 APPEND)
5、Task
Task 的描述是選擇性的,主要用在多級的 MapReduce 任務中,一個 Task 的輸出可以作爲下一個 Task 或 Map 的輸入。
6、EXECUTE
EXECUTE 就是將上面定義的步驟串聯起來
EXECUTE:
- RUN:
SOURCE: input_or_task_name
TARGET: output_name
MAP: map_function_name
REDUCE: reduce_function_name
6.6 小結
本章介紹了 gp 的一些高級的特性:
- 壓縮表:對數據採用壓縮存儲的方法,可以利用 CPU 來節省存儲空間,降低 IO 的消耗,對於數據庫應用性能有着重要的影響。
- 列存儲:一種特殊的壓縮表,將數據按列存儲,可以提升壓縮率,對於數據庫這種 IO 密集型的應用,可以大大提升性能。但是列存儲同時也帶來了大量的小文件,會對文件系統造成一定的壓力,故不能大規模使用。
- 外部表:gp 通過外部表可以非常方便地進行數據的導入導出,通過並行處理,性能非常高。可執行外部表表提供了一種可拓展的方法,使得數據導入導出更加靈活。同時 gp 還通過提供與 HDFS 快速交互的方式,使數據庫與 Hadoop 可以靈活地結合在一起使用。
- 自定義函數:要使數據庫使用起來更加方便,自定義函數在拓展 sql 上有着很重要的作用,通過自定義函數,再結合 sql,可以實現很多功能。
- MapReduce:當下非常流行的一種編程思想,它簡化了編程模型,通過這種編程思路,可以解決很多 sql 無法完成的功能。