gpfdist外部表,是Greenplum數據庫最重要的數據批量加載技術,有着極高的性能。
先講個故事,我們經歷過很多的POC測試,雖然gpfdist外部表的加載性能一直領先友商,但是,在文件服務器帶寬有限的情況下,我們往往很難取得碾壓性的優勢,尤其是文件服務器只有一根光纖的時候,測試的結果,往往是大家的性能幾乎相同,所以,我在很長一段時間,都在考慮,如何可以突破性能的上限,大概一年前,我就跟我們的技術大牛同事聊這些思考,得到了一個大牛的共鳴,後來我就做了他的產品經理,他開始着手寫一個可以在一定程度上代替gpfdist的命令,後來他的成品命名爲lotus,本文後續的一些測試中,包括文件名和數據庫的名稱都是使用的lotus,就是源於這裏,大牛的命令就叫lotus,我們討論了gpfdist是單進程的服務,所以,大神的第一次入手就開始幹多線程切分文件和分發文件的實現,我們進行了測試,但測試發現,網卡是個更大的性能瓶頸,然後我就給大牛提需求,上ZSTD壓縮,我們還對ZSTD的各個壓縮級別做了測試,最終,我們發現,ZSTD壓縮,可以極大的節省網絡帶寬的開銷,在大神上ZSTD需求期間,我就開始琢磨,我是不是可以改造一下gpfdist的代碼,加入ZSTD壓縮,經過兩三週的學習和努力,最終有了ZSTD改造版的gpfdist,本文不會深入講解如何改造gpfdist的過程,因爲過程崎嶇坎坷,只是介紹大概的優化思想。
關於大牛的命令,可以參見lotus文檔瞭解大牛的神作。 本文的測試場景,大牛的命令可以做到3182 ms完成數據掃描,加載性能爲3320 MB/s。
本文主要探討如何來進行性能的調優。
首先,我們準備了一個測試數據文件:
[gpadmin@smdw ~]$ ll --block-size M /dev/shm/lotus.txt
-rw-r--r--. 1 root root 10567M Jul 27 15:07 /dev/shm/lotus.txt
文件尺寸爲10567MB,爲了確保整個測試過程不受磁盤性能的影響,我們將測試用的數據文件放到內存中,即/dev/shm目錄下。
首先,我們在一個有72個Primary的集羣,創建一個常規的gpfdist外部表,由於GP集羣資源有限,設備比較老,這裏主要是爲了gpfdist的極限性能,所以,只對外部表執行count(*)
的操作,不會真正的執行數據入庫的操作。
CREATE READABLE EXTERNAL TABLE ext_test
(
a text
)
LOCATION ('gpfdist://172.28.8.251:8080/dev/shm/lotus.txt')
FORMAT 'TEXT' (DELIMITER 'OFF');
啓動gpfdist服務,使用簡潔模式,指定了路徑和端口,其他參數保持缺省值:
[root@smdw ~]# . /usr/local/greenplum-db-6.9.0/greenplum_path.sh
[root@smdw ~]# gpfdist -p 8080 -d /../ -s
2020-09-19 12:45:47 7512 INFO Before opening listening sockets - following listening sockets are available:
2020-09-19 12:45:47 7512 INFO IPV6 socket: [::]:8080
2020-09-19 12:45:47 7512 INFO IPV4 socket: 0.0.0.0:8080
2020-09-19 12:45:47 7512 INFO Trying to open listening socket:
2020-09-19 12:45:47 7512 INFO IPV6 socket: [::]:8080
2020-09-19 12:45:47 7512 INFO Opening listening socket succeeded
2020-09-19 12:45:47 7512 INFO Trying to open listening socket:
2020-09-19 12:45:47 7512 INFO IPV4 socket: 0.0.0.0:8080
Serving HTTP on port 8080, directory /
執行外部表的count(*)查詢,爲了準確的評估,我們進行連續3次查詢:
lotus=# SELECT count(*) FROM ext_test;
NOTICE: External scan from gpfdist(s) server will utilize 64 out of 72 segment databases
count
----------
89000000
(1 row)
Time: 9655.308 ms
lotus=# SELECT count(*) FROM ext_test;
NOTICE: External scan from gpfdist(s) server will utilize 64 out of 72 segment databases
count
----------
89000000
(1 row)
Time: 9635.492 ms
lotus=# SELECT count(*) FROM ext_test;
NOTICE: External scan from gpfdist(s) server will utilize 64 out of 72 segment databases
count
----------
89000000
(1 row)
Time: 9633.638 ms
平均耗時爲9641 ms。
┌nmon─16g──────[H for help]───Hostname=smdw─────────Refresh= 2secs ───12:49.07────────────────────────────────────────────────────────────────────────────────────────┐
│ CPU Utilisation ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── │
│---------------------------+-------------------------------------------------+ │
│CPU User% Sys% Wait% Idle|0 |25 |50 |75 100| │
│ 1 0.0 0.0 0.0 100.0|> | │
│ 2 0.0 0.6 0.0 99.4| > | │
│ 3 0.0 1.0 0.0 99.0|> | │
│ 4 0.5 3.6 0.0 95.9|s> | │
│ 5 0.0 0.0 0.0 100.0|> | │
│ 6 0.0 0.5 0.0 99.5|> | │
│ 7 0.5 0.5 0.5 98.5|> | │
│ 8 1.0 12.1 0.0 86.9|ssssss> | │
│ 9 0.0 0.0 0.0 100.0|> | │
│ 10 1.0 6.1 0.0 92.9|sss> | │
│ 11 0.0 0.0 0.0 100.0|> | │
│ 12 1.0 9.5 0.0 89.4|ssss> | │
│ 13 0.5 3.5 0.0 96.0|s> | │
│ 14 0.5 4.5 0.0 94.9|ss > | │
│ 15 1.0 12.1 0.0 86.9|ssssss> | │
│ 16 1.0 14.1 0.0 84.9|sssssss > | │
│ 17 0.0 0.0 0.0 100.0|> | │
│ 18 0.0 3.5 0.0 96.5|s> | │
│ 19 0.0 0.0 0.0 100.0|> | │
│ 20 0.0 0.0 0.0 100.0|> | │
│ 21 0.5 5.5 0.5 93.5|ss> | │
│ 22 1.5 12.6 0.0 85.9|ssssss> | │
│ 23 0.5 4.1 0.0 95.3|ss > | │
│ 24 1.0 14.6 0.0 84.4|sssssss> | │
│---------------------------+-------------------------------------------------+ │
│Avg 0.4 4.6 0.0 95.0|ss> | │
│---------------------------+-------------------------------------------------+ │
│ Network I/O ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── │
│I/F Name Recv=KB/s Trans=KB/s packin packout insize outsize Peak->Recv Trans │
│ lo 1.2 1.2 4.0 4.0 307.5 307.5 16.1 16.1 │
│virbr0-ni 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 │
│ virbr0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 │
│ em3 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 │
│ em1 0.0 0.0 0.0 0.0 0.0 0.0 0.1 0.0 │
│ em2 0.0 0.2 0.5 1.0 64.0 230.0 0.4 0.9 │
│ em4 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 │
│ p1p1 1792.2 1130760.9 27804.925717.6 66.045023.6 1832.9 1130760.9 │
│ p1p2 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 │
│──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
從nmon的輸出來看,網卡的性能已經達到極限了。接下來,我們使用兩個網口進行數據獲取操作:
DROP EXTERNAL TABLE ext_test;
CREATE READABLE EXTERNAL TABLE ext_test
(
a text
)
LOCATION (
'gpfdist://172.28.8.251:8080/dev/shm/lotus.txt',
'gpfdist://172.28.12.251:8080/dev/shm/lotus.txt'
)
FORMAT 'TEXT' (DELIMITER 'OFF');
接下來,進行連續3次查詢:
lotus=# SELECT count(*) FROM ext_test;
count
----------
89000000
(1 row)
Time: 6529.515 ms
lotus=# SELECT count(*) FROM ext_test;
count
----------
89000000
(1 row)
Time: 6631.681 ms
lotus=# SELECT count(*) FROM ext_test;
count
----------
89000000
(1 row)
Time: 6335.189 ms
平均耗時爲6498 ms。 我們通過觀察nmon的輸出發現,有一個CPU Core已經100%繁忙了,根據我們的經驗,我們嘗試調整gpfdist的-m參數來增加gpfdist分割數據的效率:
[root@smdw ~]# gpfdist -p 8080 -d /../ -s -m 128000
2020-09-19 12:58:06 11018 INFO Before opening listening sockets - following listening sockets are available:
2020-09-19 12:58:06 11018 INFO IPV6 socket: [::]:8080
2020-09-19 12:58:06 11018 INFO IPV4 socket: 0.0.0.0:8080
2020-09-19 12:58:06 11018 INFO Trying to open listening socket:
2020-09-19 12:58:06 11018 INFO IPV6 socket: [::]:8080
2020-09-19 12:58:06 11018 INFO Opening listening socket succeeded
2020-09-19 12:58:06 11018 INFO Trying to open listening socket:
2020-09-19 12:58:06 11018 INFO IPV4 socket: 0.0.0.0:8080
Serving HTTP on port 8080, directory /
再次測試,平均耗時爲5475 ms。性能已經到了極限。
接下來,我們介紹gpfdist的深入優化,首先,我們嘗試修改gpfdist源碼,加入了異步數據傳輸和ZSTD壓縮傳輸特性,並使用execute外部表來進行數據獲取,我們並沒有修改Greenplum的Server端的代碼,所以,需要藉助execute外部表來完成這個優化測試。測試如下:
[root@smdw tmp]# ./gpfdist -p 8080 -d /../ -s -m 440000
2020-09-19 13:03:47 12544 INFO Before opening listening sockets - following listening sockets are available:
2020-09-19 13:03:47 12544 INFO IPV6 socket: [::]:8080
2020-09-19 13:03:47 12544 INFO IPV4 socket: 0.0.0.0:8080
2020-09-19 13:03:47 12544 INFO Trying to open listening socket:
2020-09-19 13:03:47 12544 INFO IPV6 socket: [::]:8080
2020-09-19 13:03:47 12544 INFO Opening listening socket succeeded
2020-09-19 13:03:47 12544 INFO Trying to open listening socket:
2020-09-19 13:03:47 12544 INFO IPV4 socket: 0.0.0.0:8080
Serving HTTP on port 8080, directory /
創建新的外部表:
CREATE READABLE EXTERNAL WEB TABLE ext_testexcu
(
a text
)
EXECUTE E'sh /tmp/gcurl.sh smdw:8080/dev/shm/lotus.txt'
FORMAT 'text' (delimiter 'off' null E'\\N' escape E'\\');
進行連續3次測試:
lotus=# SELECT count(*) FROM ext_testexcu;
count
----------
89000000
(1 row)
Time: 4189.400 ms
lotus=# SELECT count(*) FROM ext_testexcu;
count
----------
89000000
(1 row)
Time: 4213.136 ms
lotus=# SELECT count(*) FROM ext_testexcu;
count
----------
89000000
(1 row)
Time: 4179.077 ms
平均耗時爲4193 ms。
總結
gpfdist優化情況 | 平均耗時 | 平均加載性能 | 瓶頸點 | gpfdist服務器帶寬消耗 |
---|---|---|---|---|
無優化 | 9641 ms | 1096 MB/s | 網絡帶寬 | 1096 MB/s |
增加網絡帶寬 | 6498 ms | 1626 MB/s | CPU主頻 | 1626 MB/s |
-m爲128000 | 5475 ms | 1930 MB/s | 網絡帶寬/CPU主頻 | 1930 MB/s |
修改源碼加入ZSTD壓縮傳輸和異步數據傳輸 | 4193 ms | 2520 MB/s | CPU主頻 | 大約380 MB/s |
對gpfdist進行ZSTD壓縮傳輸和異步數據傳輸的優化,主要目的是,降低網絡帶寬的消耗,充分利用多CPU Core的計算能力。尤其是在網絡帶寬有限的環境,可以發揮極大的性能優勢。