PG-Strom讓PostgreSQL查詢飛起來 –基於OpenShift-3.10的GPU加速
- 2019 年 11 月 21 日
- 筆記
作者簡介
Zvonko Kosic,發表多篇基於openshift的GPU加速系列文章,IBM工程師。
譯者簡介
朱君鵬,華東師範大學博士研究生,個人興趣主要集中在:新型硬件(GPU、RDMA、FPGA等)在數據庫中的應用,數據庫系統,分佈式系統,架構設計與並行計算。
Qinghui.Guo, PG粉絲,DBA,負責公司Cloud DB的維護,痴迷於開源架構解決方案,現致力於PG分享和推廣。
介紹
在OpenShift 3.9 GPU博客中,我們利用OpenShift上的機器學習框架進行圖像識別。在OpenShift 3.10博客中的如何使用帶有DevicePlugin的GPU中,我們安裝並配置了支持GPU的OpenShift集群。在本部分中,我們將在集群上創建更複雜的工作負載-使用GPU加速數據庫查詢。
任何機器學習算法的關鍵部分之一是數據(通常稱為數據湖/倉庫,存儲為結構化,半結構化或非結構化數據)。
機器學習管道的主要部分是準備,清理和探索這些數據。特別是刪除NAs(缺失值),轉換,規範化,子集化,排序和大量繪圖。
本博客將重點關注數據準備階段,展示如何使用GPU加速PostgreSQL(數據倉庫)中的查詢。接着使用R繪製數據。
環境概述
l Red Hat Enterprise Linux 7.5,CentOS PostgreSQL 10鏡像
l OpenShift Container Platform 3.10 Cluster running on AWS
l Container Runtime: crio-1.10.5
l 容器工具:podman-0.6.1,buildah-1.1,skopeo-0.1.30
l 主節點:m4.xlarge
l Infra節點:m4.xlarge
l 計算節點:p3.2xlarge(一個NVIDIA Tesla V100 GPU,8vCPU和61GB RAM)
本文中使用的yaml和配置文件在https://github.com/redhat-performance/openshift-psap/tree/master/blog/gpu/pg-strom 中可以找到。
我們創建了一個git倉庫,其中包含要在整個博客中使用的工具。從現在開始,我們將它們稱為pg-strom / <file>。
# git clone https://github.com/redhat-performance/openshift-psap
我們將使用centos / postgresql-10-centos7作為基礎鏡像,並使用PG-Strom啟用GPU加速查詢。PG-Strom是PostgreSQL的擴展模塊,可以加速SQL工作負載進行數據分析或批處理。
使用buildah構建自定義圖像
新鏡像的構建將使用buildah完成,這有助於構建OCI容器鏡像,並且是「docker build」的替代方案。Skopeo是一種便於檢查,拉取容器鏡像並將其推送到註冊表的工具。現在安裝buildah和skopeo。
# yum -y install buildah skopeo
Buildah使用簡單的bash腳本進行構建,示例演示可在此處訪問:Buildah演示。可以在pg-strom / buildah-pg.sh中找到此博客中使用的buildah腳本。該腳本安裝容器,安裝所需的軟件並提交容器供以後使用。只需調用該腳本即可生成支持GPU的PostgreSQL容器。
# <dir>/pg-strom/buildah-pg.sh
讓我們檢查一下我們是否在本地提交了完整的鏡像:
# podman images | grep pgstrom
localhost/pgstrom latest 7ca1854e4176 5 days ago 517MB
可以選擇將容器推送到註冊表。Skopeo可以在不從註冊表中提取鏡像的情況下檢查鏡像:
# skopeo inspect <registry>/<repo>/pgstrom
{
"Name": "<registry>/<repo>/pgstrom",
"Tag": "latest",
"Digest": "sha256:b71e9bca91e23a21e7579c01d38522b76dc05f83c59",
"RepoTags": [
"latest"
],
"Created": "2018-07-13T10:26:49.604378011Z",
…
準備PostgreSQL服務器
OpenShift 3.10的另一個新功能是支持HugePages。HugePages是一種常用於數據庫的內存訪問性能優化技術(如PostgreSQL,C和Java應用程序)。我們將使用HugePages作為數據庫內存。讓我們安裝一些工具以便於管理並創建所需的安裝。
# yum -y install libhugetlbfs-utils
# hugeadm –create-global-mounts
讓我們在節點上分配相當數量的大頁面,並檢查節點是否接受了更改。
為此,我們將使用tuned,它是用於監視和自適應調整系統設備的守護進程。我們可以創建一個配置文件,該配置文件是最簡單的配置文件,包含幾個部分來調整系統控件和參數。除此之外,還可以提供bash腳本來為系統實現更複雜的調整。
以下配置文件pg-strom / pgstrom.conf可以放在/ etc / tuned / pgstrom中,以便可以識別為要調整的配置文件。Tuned具有配置文件繼承的概念。在這裡,我們將「include = openshift-node」設置為我們的自定義pgstrom(子)配置文件的父配置文件。
# tuned
[main]
summary=Configuration for pgstrom
include=openshift-node
[vm]
transparent_hugepages=never
[sysctl]
vm.nr_hugepages=16384
現在,根據openshift-node配置文件加載新的調優配置文件。默認情況下,每個OpenShift節點(計算,控制平面……)都有自己的調整配置文件,具體取決於它的職責。
# tuned-adm profile pgstrom
檢查節點是否有HugePages。
# oc describe node | grep Capacity -A13
Capacity:
cpu: 8
hugepages-1Gi: 0
hugepages-2Mi: 32Gi
memory: 62710868Ki
nvidia.com/gpu: 1
pods: 250
Allocatable:
cpu: 8
hugepages-1Gi: 0
hugepages-2Mi: 32Gi
memory: 29054036Ki
nvidia.com/gpu: 1
pods: 250
持久化存儲
我們希望數據被持久保存(而不是存儲在ephemerally,在容器中,這將不會持久)。為此,我們將在OpenShift 3.10中使用一個名為Local Volumes的新功能。這裡的關鍵部分是新的本地存儲配置器,它監視主機上的特定路徑,並為此路徑中找到的每個安裝點創建PV(持久卷)。
這些PV可以分組為命名組PV,如SSD或HDD,並且可以在命名空間中聲明。然後,該聲明可以被pod用作卷。
如何設置具有本地卷的PV可在此處閱讀:配置本地卷以及Pod如何使用PersistentVolumeClaim消耗此PV可在此處閱讀:使用持久卷。
對於這個博客,我創建了一個HDD PV組,其中包含一個保存PostgreSQL數據的磁盤。
# ls /mnt/local-storage/hdd/
disk1
OpenShift將為disk1創建一個PV,我們將聲明這個PV並將其安裝到pod中。查看pg-strom / pgstrom.yml以了解如何安裝聲明。
設置數據存儲後,我們可以使用以下pg-strom / pgstrom.yml部署pgstrom pod。啟動pod並檢查日誌,容器將初始化PostgreSQL數據存儲(initdb)。
# oc create -f pgstrom.yml
# oc logs pgstrom
—————————- snip —————————————
Starting server…
UTC [1] LOG: listening on IPv4 address "0.0.0.0", port 5432
UTC [1] LOG: listening on IPv6 address "::", port 5432
UTC [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
UTC [1] LOG: listening on Unix socket "/tmp/.s.PGSQL.5432"
UTC [1] LOG: redirecting log output to logging collector process
UTC [1] HINT: Future log output will appear in directory "log".
要啟用擴展,我們必須更改postgresql.conf(存儲在安裝到pod中的PV中)並添加一些用於GPU加速的調整。在pod中編輯/var/lib/pgsql/data/userdata/postgresql.conf並設置:
# oc rsh pgstrom /bin/bash
$ vi /var/lib/pgsql/data/userdata/postgresql.conf
————————- snip —————————————–
## postgresql.conf
huge_pages = on
# Initial buffers and mem too small, increase it to work in mem
# and not in storage
shared_buffers = 30GB
work_mem = 30GB
# PG-Strom internally uses several background workers,
# Default of 8 is too small, increase it
max_worker_processes = 100
max_parallel_workers = 100
# PG-Strom module must be loaded on startup
shared_preload_libraries = '/usr/pgsql-10/lib/pg_strom.so,pg_prewarm'
————————- snip ——————————–
編輯配置文件後,重新啟動pod以啟用新功能。
# oc replace –force -f pgstrom.yml
在pod中,我們可以檢查服務器是否在監聽
# oc exec pgstrom pg_isready
/var/run/postgresql:5432 – accepting connections
buildah腳本還安裝了postgresql-odbc,用於從我們的工作站連接到數據庫服務器。但首先讓我們創建一個測試數據庫,我們將在其上進行一些初始測試查詢。
# oc exec -it pgstrom /bin/bash
$ cd /var/lib/pgsql/pg-strom/test
$ make init_regression_testdb
# Grab some coffee, this takes some time
R連接數據庫
對於下一部分,我們將使用R和RStudio創建一個類似於Jupyter的筆記本來訪問數據庫並從我們的查詢中繪製一些數字。
根據您在工作站上的版本安裝R,RStudio和R軟件包RPostgreSQL。
我添加了一個簡單的筆記本pg-strom / pgstrom.Rmd作為參考。這是RStudio中運行的筆記本運行針對PostgreSQL服務器的SQL查詢。

第一步是通過db驅動程序包連接到數據庫。
require(RPostgreSQL)
drv <- dbDriver("PostgreSQL")
con <- dbConnect(drv,
user="postgres",
password="postgres",
host=".com",
# This is the database we created in the step before
dbname="contrib_regression_pg_strom")
現在我們可以對數據庫發出SQL查詢。第一步是啟用PG-Strom擴展。
dbGetQuery(con, "SET pg_strom.enabled=on")
我們現在可以對數據庫運行加速查詢,讓我們從兩個表上的一個簡單的NATURAL JOIN開始,讓數據庫EXPLAIN和ANALYZE在這個特定的查詢中做了什麼。
dbGetQuery(con, "EXPLAIN ANALYZE SELECT cat, count(*), avg(ax) FROM t0 NATURAL JOIN t1 GROUP BY CAT")
QUERY PLAN
—————————————————————————
GroupAggregate (cost=18097.45..18130.73 rows=1024 width=24) (actual time=299.065..300.648 rows=1025 loops=1)
Group Key: t0.cat
-> Sort (cost=18097.45..18100.01 rows=1024 width=48) (actual time=299.053..299.480 rows=1025 loops=1)
Sort Key: t0.cat
Sort Method: quicksort Memory: 193kB
-> Custom Scan (GpuPreAgg) (cost=18025.77..18046.25 rows=1024 width=48) (actual time=295.002..295.546 rows=1025 loops=1)
Reduction: Local
Combined GpuJoin: enabled
-> Custom Scan (GpuJoin) on t0 (cost=19089.39..26894.61 rows=995000 width=16) (never executed)
Outer Scan: t0 (cost=0.00..20310.00 rows=1000000 width=12) (actual time=62.944..106.492 rows=1000000 loops=1)
Depth 1: GpuHashJoin (plan nrows: 1000000…995000, actual nrows: 1000000…994991)
HashKeys: t0.aid
JoinQuals: (t0.aid = t1.aid)
KDS-Hash (size plan: 11.54MB, exec: 7125.12KB)
-> Seq Scan on t1 (cost=0.00..2031.00 rows=100000 width=12) (actual time=0.012..49.225 rows=100000 loops=1)
Planning time: 0.424 ms
Execution time: 476.807 ms
PostgreSQL構建計劃節點的樹結構,表示所採取的不同操作。我們可以看到GPU(GpuPreAgg,GpuJon,GpuHashJoin)正在加速執行樹的各個部分,因此我們得到了計劃和執行時間。
讓我們關閉擴展並重新運行SQL語句。我們的筆記本再次執行以下內容。
dbGetQuery(con, "SET pg_strom.enabled=off")
dbGetQuery(con, "EXPLAIN ANALYZE SELECT cat, count(*), avg(ax) FROM t0 NATURAL JOIN t1 GROUP BY CAT")
QUERY PLAN
—————————————————-
Finalize GroupAggregate (cost=27455.10..27488.38 rows=1024 width=24) (actual time=2819.862..2823.624 rows=1025 loops=1)
Group Key: t0.cat
-> Sort (cost=27455.10..27460.22 rows=2048 width=48) (actual time=2819.847..2821.153 rows=3075 loops=1)
Sort Key: t0.cat
Sort Method: quicksort Memory: 529kB
-> Gather (cost=27127.42..27342.46 rows=2048 width=48) (actual time=2806.990..2809.907 rows=3075 loops=1)
Workers Planned: 2
Workers Launched: 2
-> Partial HashAggregate (cost=26127.42..26137.66 rows=1024 width=48) (actual time=2804.084..2804.723 rows=1025 loops=3)
Group Key: t0.cat
-> Hash Join (cost=3281.00..23018.05 rows=414583 width=16) (actual time=306.307..2281.754 rows=331664 loops=3)
Hash Cond: (t0.aid = t1.aid)
-> Parallel Seq Scan on t0 (cost=0.00..14476.67 rows=416667 width=12) (actual time=0.027..622.406 rows=333333 loops=3)
-> Hash (cost=2031.00..2031.00 rows=100000 width=12) (actual time=305.785. -> Seq Scan on t1 (cost=0.00..2031.00 rows=100000 width=12) (actual time=0.012..182.335 rows=100000 loops=3)
Planning time: 0.201 ms
Execution time: 2824.216 ms
我們可以看到沒有GPU方法出現,查詢在CPU上運行。執行時間從~477 ms增加到2824 ms -幾乎是6倍的加速。
讓我們以此為起點,為10個表(t0-t9)創建自然連接的基準,在CPU和GPU上一次添加一個表。我已經實現了一個小函數來從輸出中提取執行時間並將結果保存在R數據框中,以便於處理和繪圖(有關詳細信息,請參閱筆記本)。
現在針對CPU和GPU運行查詢。
dbGetQuery(con, "SET pg_strom.enabled=on")
ton <- list()
ton <- c(ton, get_exec_time(dbGetQuery(con, "EXPLAIN ANALYZE SELECT cat, count(*), avg(ax) FROM t0 NATURAL JOIN t1 GROUP BY CAT")))
ton <- c(ton, get_exec_time(dbGetQuery(con, "EXPLAIN ANALYZE SELECT cat, count(*), avg(ax) FROM t0 NATURAL JOIN t1 NATURAL JOIN t2 GROUP BY CAT")))
ton <- c(ton, get_exec_time(dbGetQuery(con, "EXPLAIN ANALYZE SELECT cat, count(*), avg(ax) FROM t0 NATURAL JOIN t1 NATURAL JOIN t2 NATURAL JOIN t3 GROUP BY CAT")))
ton <- c(ton, get_exec_time(dbGetQuery(con, "EXPLAIN ANALYZE SELECT cat, count(*), avg(ax) FROM t0 NATURAL JOIN t1 NATURAL JOIN t2 NATURAL JOIN t3 NATURAL JOIN t4 GROUP BY CAT")))
…
在提取執行時間之後,我們現在能夠在R中繪製數據,我們在這裡使用ggplot2,這是R中最常用的圖形包。

GPU加速查詢在~2.5秒內保持平穩,其中CPU查詢因添加的每個新表而增加。我們可以進一步添加更多表來顯示GPU執行時間何時會增加,但這超出了此博客的範圍。
PostgreSQL 上使用PG-Strom運行DBT-3基準測試
DBT-3基準測試是決策支持基準測試的開源實現,其中數據和工作負載基於特定的數據庫規範。基準測試將在PG-Strom啟用和禁用的情況下運行,類似於上面的測試。

大多數查詢都會在沒有任何SQL查詢更改的情況下加速(某些查詢未在CPU或GPU上運行,因此報告為0)。
結論
PG-Strom是一個在數據庫SQL級別即時加速的優秀插件。查詢(avg,cnt,sqrt,…)中的數字運算次數越多,GPU的利益就越大。
此外,還有一些努力正在加速用GPU加速R.在不久的將來,人們可能擁有從數據庫到分析,繪圖和統計計算的完整GPU加速管道。
https://blog.openshift.com/gpu-accelerated-sql-queries-with-postgresql-pg-strom-in-openshift-3-10/