Greenplum 编译、安装、和调试

<code>本⽂先介绍如何从源代码编译安装Greenplum、初始化Greenplum集群。然后介绍SQL在Greenplum中的典型执⾏路径,最后介绍⼀些调试技巧。/<code>

源 代 码 使 ⽤ Greenplum 开 源 社 区 最 新 源 代 码 6X_STABLE 分 ⽀ :ghttps://github.com/greenplum-db/gpdb%EF%BC%8C 内核代码基于 PostgreSQL9.4。⽬前(2019/04/23) 主⼲分⽀的代码基于 PostgreSQL 9.4 。合并到 PostgreSQL 9.5 的⼯作也已经开始, 有关最新⼯作进展请参⻅:https://github.com/greenplum-db/gpdb-postgres-merge%E3%80%82

1 从源代码编译 Greenplum

Greenplum ⽬前官⽅⽀持 Redhat/Centos/SuSE/Ubuntu 等Linux系统。⼤量开发⼈员包括我⾃⼰使⽤Mac系统,但是不在官⽅⽀持列表中。

1.1 在 Mac系统上编译

⾸先需要关闭苹果操作系统的 SIP 特性,否则⽆法初始化集群。

重启操作系统重启过程中按下 command+R进⼊恢复模式从 Utilities菜单选择 Terminal执 ⾏ csrutildisable重启操作系统

其次,安装Greenplum管理脚本依赖的 Python 包

<code>$ wget

https:

/

/bootstrap.pypa.io/get

-pip.py $ sudo python get-pip.py $ sudo pip install psutil lockfile paramiko setuptools epydoc/<code>

然后,需要安装 openssl,否则⽆法编译

<code>$ brew

install

zstd openssl && brew

link

openssl $ CPPFLAGS=

"-I/usr/local/include/ -I/usr/local/opt/openssl/include"

\ LDFLAGS=

"-L/usr/local/lib -L/usr/local/opt/openssl/lib"

\ CFLAGS=

"-O0 -g3 -ggdb3"

\ ./configure $ make [-j4] $ make

install

/<code>

最后,在苹果系统上初始化Greenplum单节点集群时,需要做些准备⼯作:

添加export PGHOST=localhost⾄~/.bash_profile将本机的hostname与127.0.0.1的map写到/etc/hosts中。例如

<code> 127

.0

.0

.1

yydzero

yydzero

.local

/<code>修改/etc/sysctl.conf⽂件,并重启:

<code>kern.sysv.shmmax=2147483648 kern.sysv.shmmin=1 kern.sysv.shmmni=64 kern.sysv.shmseg=16 kern.sysv.shmall=524288 kern.maxfiles=65535 kern.maxfilesperproc=65535 net.inet.tcp.msl=60

cd

gpAux/gpdemo

source

$HOME

/gpdb.master/greenplum_path.sh

export

PGHOST=`hostname`

make

source

gpdemo-env.sh

psql postgres

SELECT version()

/<code>

有关更详细的信息请参考 README.macOS.md。

1.2 在 Redhat/Centos系统上编译

本⼩节以 RHEL7 为例介绍如何编译Greenplum。

⾸先,下载 Greenplum 源代码

<code>$ git

clone

https:/<code>

其次,Greenplum Database 编译和运⾏依赖于各种系统库和Python库。需要先安装这些依赖:

<code>$ sudo yum groupinstall

'Development Tools'

$ sudo yum install curl-devel bzip2-devel python-devel openssl-devel readline-devel libzstd-devel $ sudo yum install perl-ExtUtils-Embed $ sudo yum install libxml2-devel $ sudo yum install openldap-devel $ sudo yum install pam pam-devel $ sudo yum install perl-devel $ wget

https:

/

/bootstrap.pypa.io/get

-pip.py $ sudo python get-pip.py $ sudo pip install psutil lockfile paramiko setuptools epydoc/<code>

再次,编译 Greenplum Database 源代码,假定安装到 $HOME/gpdb.master ⽬录下

<code>$ CFLAGS=

"-O0 -g3 -ggdb3"

\ ./configure --with-perl --with-python --with-libxml --

enable

-debug --

enable

-cassert \ --

disable

-orca --

disable

-gpcloud --

disable

-gpfdist \ --prefix=/home/gpadmin/gpdb.master $ make $ make install/<code>

<code>

cd

gpAux/gpdemo

source

/home/gpadmin/gpdb.master/greenplum_path.sh

export

PGHOST=`hostname`

make

source

gpdemo-env.sh

/<code>

<code>

$

psql

postgres

postgres#

SELECT

version()

version

-----------------------------------------------------------------

PostgreSQL

8.3

.23

(Greenplum

Database

5.4

.1

+dev.56.gcdfadd9

build

dev)

...

/<code>

<code>postgres= dbid

| content |

role

| preferred_role |

mode

| status |

port

| hostname |

address

| replication_port ------+---------+------+----------------+------+--------+-------+----------+---------+------------------ 1 |

-

1

| p |

p

| s |

u

| 15432 |

g2

0

| g20 |

2

| 0 |

p

| p |

s

| u |

25432

| g20 |

g2

0

| 25438 3 |

1

| p |

p

| s |

u

| 25433 |

g2

0

| g20 |

25439

4

| 2 |

p

| p |

s

| u |

25434

| g20 |

g2

0

| 25440 5 |

0

| m |

m

| s |

u

| 25435 |

g2

0

| g20 |

25441

6

| 1 |

m

| m |

s

| u |

25436

| g20 |

g2

0

| 25442 7 |

2

| m |

m

| s |

u

| 25437 |

g2

0

| g20 |

25443

/<code>

2 初始化 Greenplum 集群

前⾯编译部分介绍了如何使⽤ Greenplum 源代码中的 demo 集群脚本创建集群。这种⽅法简单快捷,让⽽屏蔽了很多细节。

2.1 ⼿⼯集群初始化

下⾯介绍如何⼿⼯部署⼀个单机集群:在⼀台笔记本上安装⼀个Greenplum的集群,包括⼀个master,两个segments。

step 0. 系统环境配置

<code>

$

/etc/sysctl.conf

kernel.shmmax

=

500000000

kernel.shmmni

=

4096

kernel.shmall

=

4000000000

kernel.sem

=

250

512000

100

2048

kernel.sysrq

=

1

kernel.core_uses_pid

=

1

kernel.msgmnb

=

65536

kernel.msgmax

=

65536

kernel.msgmni

=

2048

net.ipv4.tcp_syncookies

=

1

net.ipv4.conf.default.accept_source_route

=

0

net.ipv4.tcp_tw_recycle

=

1

net.ipv4.tcp_max_syn_backlog

=

4096

net.ipv4.conf.all.arp_filter

=

1

net.ipv4.ip_local_port_range

=

10000

65535

net.core.netdev_max_backlog

=

10000

net.core.rmem_max

=

2097152

net.core.wmem_max

=

2097152

vm.overcommit_memory

=

2

$

cat

/etc/security/limits.conf

*

soft

nofile

65536

*

hard

nofile

65536

*

soft

nproc

131072

*

hard

nproc

131072

$

sudo

reboot

/<code>step 1. source⼀些环境变量, 例如PATH

<code>

source

$HOME

/gpdb.master/greenplum_path.sh

/<code>step 2. 交换集群中所有机器的ssh密钥, 我们这⾥只有⼀台机器

<code>$ gpssh-exkeys -h

`hostname`

/<code>step 3. ⽣成三个配置⽂件:env.sh, hostfile, gpinitsystem_config

<code>$ cat env.sh

source

$HOME

/gpdb.master/greenplum_path.sh

export

PGPORT=5432

export

MASTER_DATA_DIRECTORY=

$HOME

/data/master/gpseg-1/<code>

# hostfile 包括集群中所有机器的hostname, 我们这⾥只有⼀台

<code>

cat hostfile

cat gpinitsystem_config

ARRAY_NAME

=

"Open Source Greenplum"

SEG_PREFIX

=

gpseg

PORT_BASE

=

40000

/<code>

# 根据需要,修改下⾯的路径和主机名

# 有⼏个DATA_DIRECTORY, 每个节点上便会启动⼏个segments

<code>

declare

-a DATA_DIRECTORY=(/

path

/

to

/your/

data

/

path

/

to

/your/

data

)/<code>

# master的主机名, 路径和端⼝

<code>

MASTER_HOSTNAME

=your_hostname

MASTER_DIRECTORY

=/path/to/your/data/master

MASTER_PORT

=

5432

TRUSTED_SHELL

=ssh

CHECK_POINT_SEGMENTS

=

8

ENCODING

=UNICODE

MACHINE_LIST_FILE

=hostfile/<code>step 4. 初始化Greenplum 集群

<code>

source

env.sh

gpinitsystem -c gpinitsystem_config -a

/<code>step 5. 初始化成功后,运⾏下⾯命令验证系统状态

<code>$ psql -l $ gpstate/<code>step 6. 简单测试

<code>$ createdb

test

$ psql

test

test

test

gp_segment_id | count ---------------+--------- 0 | 501 1 | 499/<code>

有关如何安装多节点集群,请参考Greenplum官方安装文档。

2.2 集群初始化问题调试

有时候 gpinitsystem 会失败,但是不清楚失败原因是什么。下⾯提供⼀些思路来 RCA:

2.2.1 使⽤ gpinitsystem调试模式

gpinitsystem有⼀个 -D选项,使⽤这个选项可以看到更多的输出信息,根据这些额外的输出信息可以发现并解决⼤部分问题。

2.2.2 查看⽇志

常⽤的⽇志⽂件有两类,⼀种是 gpinitsystem 的⽇志,⼀种是数据库的⽇志。它们分别保存在不同的⽬录下:

gpinitsystem 的⽇志⽂件。默认路径为 ~/gpAdmin/gpinitsystem_***数据库的⽇志⽂件:进⼊ master ( segment 的⽇志类似) 的⽇志⽬录 ( 例如/data/master/gpseg-1/pg_log/)查看⽇志。 这⾥⾯有2种类型的⽇志:startup.loggpdb-.csv

2.2.3 初始化 master数据库失败

⼿动执⾏initdb查看详细错误信息,然后分析具体错误信息采取相应错误。不同的版本可能参数不同,可以通过在 gpinitsystem 脚本中找到完整的命令。

<code>$ initdb -E UNICODE -D /data/master/gpseg

-1

/<code>

2.2.4 master起不来

使⽤下⾯命令,⼿动启动master观看⽇志是否有问题。下⾯使⽤ Utility 模式启动master, 仅仅允许utility 模式连接。

<code>

$

postgres

-D

/data/master/gpseg-1

-i

-p

5432

-c

gp_role=utility

-M

master

-b

1

-C

-1

-z

0

-m

/<code>

2.2.5 启动Segment出错

如果启动 segment 时出错,并且看不到具体错误信息(通常由于错误信息被重定向到/dev/null 了),则可以尝试⼿动启动 segment。

⼿动启动segment的命令参考下⾯,需要根据⾃⼰的环境修改某些路径或者参数:

<code>export LD_LIBRARY_PATH=

/home/gpadmin

/build/gpdb

.master/

lib:

/

lib:

;export PGPORT=

40006

;

/home/gpadmin

/build/gpdb

.master/bin/pg_ctl -w -l /data2/primary/gpseg18/pg_log/startup.log -D /data2/primary/gpseg18 -o

"-i -p 40006 -M mirrorless -b 20 -C 18 -z 0"

start/<code>

有时候单独执⾏各种命令没有问题,但是使⽤ SSH 执⾏时报错。

这通常是由于 ssh 改变了环境变量造成的,查看 .bash_profile, .bashrc, 发现 .bashrc 设置了不同的默认 PGHOST,删除这个配置后就可以了。

2.2.6 不能连接到server:找不到domain socket

<code>○ → PGOPTIONS=

'-c gp_session_role=utility'

/Users/yydzero/work/build/master/bin/psql postgres psql: could

not

connect

to server: No such file

or

directory Is the server running locally

and

accepting connections on Unix domain

socket

"/var/pgsql_socket/.s.PGSQL.5432"

?/<code>

这个通常是由于不同的 psqlbinary造成的,也就是说⾃⼰编译的 psql调⽤了系统的 libpq 库。可以通过 ldd或者 otool-L查看。

解决⽅法:

<code>

export

LD_LIBRARY_PATH=

/path/

to/your/psql/lib/<code>

2.2.7 gpstart失败,并且原因不明

$gpstart-v//使⽤ verbose模式,显示每个执⾏的命令以及其结果。遇到的⼀个问题报错如下:

<code>unable to

import

module

: No

module

named psutil/<code>

原因是 psutil 这个python包没有安装,但是使⽤ python 验证,发现已经安装了。⽽使⽤ssh 验证发现使⽤了不同路径的 python。

2.2.8 关闭 IPv6

如果遇到下⾯错误,则关闭 IPv6:

<code>

ping

cmdStr='/bin/ping6 -c 1 gp1′ had result: cmd had rc=2 completed=True halted=False

stdout

=

stderr

=

'connect: Invalid argument'

/<code>

如何关闭 IPv6:

加⼊以下两⾏到⽂件:/etc/sysctl.conf

<code>

net.ipv6.conf.all.disable_ipv6

=

1

net.ipv6.conf.default.disable_ipv6

=

1

/<code>

然后 $ sysctl -p

2.2.9 小技巧

Greenplum使⽤ Bash和 Python脚本初始化集群和管理集群。可以通过在合适的地⽅设置⽇志或者调试信息可以帮助分析某些难以解决的问题。

集群初始化⼯具 gpinitsystem 是Bash脚本⼯具,有些时候它的报错信息很不清楚。这个时候可以使⽤ -D 选项gp_bash_functions.sh 是内部⼀个被频繁调⽤执⾏系统命令的函数,可以通过set -x 可以打印出所有执⾏的命令的详细信息。对调试 hang 问题很有效。在合适的代码处启⽤ Python 调试器,如果不知道什么地⽅合适,则在⼊⼝处。

3 Greenplum SQL执⾏流程概要

下⾯介绍下 Greenplum 中 SQL 执⾏的简单过程。例⼦中集群⼀个 Master 两个Segments。

准备简单的数据:

<code>

CREATE

TABLE

students (

id

int

,

name

text

)

DISTRIBUTED

BY

(

id

);

CREATE

TABLE

classes(

id

int

, classname

text

, student_id

int

)

DISTRIBUTED

BY

(

id

);

INSERT

INTO

students

VALUES

(

1

,

'steven'

), (

2

,

'changchang'

), (

3

,

'guoguo'

);

INSERT

INTO

classes

VALUES

(

1

,

'math'

,

1

), (

2

,

'math'

,

2

), (

3

,

'physics'

,

3

);/<code>

以下⾯的SQL为例⼦,了解 SQL 在 Greenplum 中的执⾏过程:

<code>

SELECT

s.name student_name, c.classname

FROM

students s, classes c

WHERE

s.id=c.student_id;/<code>

3.1 查询计划

其对应的查询计划如下所示:

<code>

test=#

explain

SELECT

s.name

student_name,

c.classname

test-#

FROM

students

s,

classes

c

test-#

WHERE

s.id=c.student_id;

QUERY

PLAN

-----------------------------------------------------------------------------------------------

Gather

Motion

2

:1

(slice2;

segments:

2

)

(cost=2.07..4.21

rows=4

width=14)

->

Hash

Join

(cost=2.07..4.21

rows=2

width=14)

Hash Cond:

c.student_id

=

s.id

->

Redistribute

Motion

2

:2

(slice1;

segments:

2

)

(cost=0.00..2.09

rows=2

width=10)

Hash Key:

c.student_id

->

Seq

Scan

on

classes

c

(cost=0.00..2.03

rows=2

width=10)

->

Hash

(cost=2.03..2.03

rows=2

width=12)

->

Seq

Scan

on

students

s

(cost=0.00..2.03

rows=2

width=12)

Optimizer status:

legacy

query

optimizer

/<code>

使用 explain.pl 可以生成如下的查询计划图:(把上面的explain结果保存到一个名为 a.plainplan 的文件中)

<code> $ explain.pl -opt jpg

/tmp/a

.plainplan >

/tmp/a

.jpg/<code>

从上图可以很明显看出该计划包含两个 slice,slice 使⽤的motion为重分布。

hashjoin的两个表为students和classes, 它们的分布键都是其 id, ⽽ join的键值是student.id=classes.student_id其中 student的join键是其主键, 因⽽不需要数据移动(motion);⽽classes的关联键是 student_id,和其分布键不同,因⽽需要数据移动(motion),以保证相同关联键的数据都在同⼀个 segment 上。

感兴趣的读者可以尝试把 stendent 的分布键改成其他字段,看看计划有什么变化。

3.2 查询执行

QD(Query Dispatcher) 将上⾯的并⾏计划分发到每个 segment 上执⾏。这个例⼦中⼀共有2个segments。

查询计划包含2个slices,所以每个 segment 会启动 2 个 QE(Query Executor),⼀个QE 负责执⾏⼀个 slice 对应的任务。

同⼀个 slice 在每个 segment 的 QE形成⼀个 Gang,它们在不同的segment上执⾏相同的任务。

HashJoin需要相同关联键的所有数据都在⼀个 segment上,因⽽如果关联键不是分布键, 则需要数据移动。在这个例⼦中classes的分布键(id)和关联键(student_id) 不同,所以需要数据重分布。

数据重分布由 Motion 操作符节点处理,它分成2个部分,⼀部分负责发送数据,⼀部分负责接收数据。发送数据者可以根据不同的策略将数据发送给接收⽅,现在⽀持的策略有1)重分布(redistribution);2)⼴播(broadcast)。

最后每个segment执⾏结束后,将结果发送给 Master。Master对最终的数据整合(Gather Motion),返回给客户端。

4 调试Greenplum MPP 数据库

4.1 调试 Master节点Backend进程

调试 Master 的Backend进程(也称为 QD)和调试单节点的PostgreSQL ⾮常类似。通常遇到解析、优化、调度相关问题时,需要调试QD。

下⾯以⼀个例⼦介绍如何调试 GreenplumQD进程。启动两个窗⼝,⼀个运⾏psql,⼀个运⾏ lldb

使⽤ lldb 的 gui 命令可以使⽤⼀个简单的源代码浏览器查看当前正在执⾏的代码区域,以及执⾏函数的相关变量。

通过简单的断点和单步执⾏,可以快速了解SQL的执⾏过程。譬如上⾯例⼦中可以看到cdbdisp_dispatchToGang 在 ExecutorStart 之后、ExecutorRun 之前运⾏,⽤途是将 QD 优化好的计划分发给 segments 执⾏。

4.2 调试 Segment节点Backend进程 (QE)

调试 segment 进程(通常是 QE)和调试master上的进程⼀样,唯⼀的区别是如何获得进程的id?

此时不能通过 pg_backend_pid() 获得,因为该pid是 QD 的进程号。常⽤的⽅法是通过执⾏2次 SQL,获得 QE 的进程号。

Greenplum为了提⾼效率, 降低创建 Gang/QEs的代价, 通常会重⽤已经创建的Gang/QEs。利⽤这⼀特性,可以⽅便的找到每个 segment上 QE 的pid。

先执⾏⼀次想要调试的 SQL。然后使⽤下⾯的命令找出感兴趣的 QE 的pid。

这个例⼦中进程38965 是 QD进程, 41210是 segment0上的 QE进程, 41211是segment 1 上的 QE 进程。

<code>○ →

ps

-ef

|

grep

postgres

|

grep

idle

503

38965

38387

0

9

:35PM

0

:00.46

postgres

:

5432

,

yydzero

test

::1(51161)

con9

cmd65

idle

503

41210

38354

0

10

:39PM

0

:00.10

postgres

:

40000

,

yydzero

test

**(

51490

)

con9

seg0

idle

503

41211

38355

0

10

:39PM

0

:00.11

postgres

:

40001

,

yydzero

test

**(

51491

)

con9

seg1

idle

/<code>

知道了 QE的进程号,使⽤ lldbattach到该进程,重新执⾏ SQL就可以进⾏调试了。Gang/QEs的重⽤时间由 GUCgp_vmem_idle_resource_timeout控制。

4.3 使⽤ IDE调试

常⽤的调试器gdb/lldb虽然简单易⽤、 功能也很强⼤,但是不直观。很多集成开发环境(IDE)提供了⾮常直观、强⼤、易⽤的调试环境,包括 clion、eclipse、xcode 等。IDE 对于学习 Greenplum 代码也⾮常有帮助,可以⼤⼤提⾼效率。

下⾯简单介绍如何使⽤ clion 图形化⽤户界⾯调试 Greenplum 代码。( Eclipse、VisualCode具有类似功能)

Greenplum进程都是 daemon进程,很难通过启动⽅式进⼊调试器。因⽽通常使⽤的⽅法是 attach到已经运⾏的进程。

⾸先启动 clion,导⼊ Greenplum源代码项⽬。clion需要 CMakeLists.txt⽂件构建⼯程项⽬。将下⾯的 CMakeLists.txt放到 Greenplum源代码⽬录的顶层⽬录中,再启动 clion既可建⽴合适的⼯程项⽬。

<code>$ cat CMakeLists.txt cmake_minimum_required(VERSION

3.8

) project(gpdb) set(CMAKE_CXX_STANDARD

11

) include_directories(src/

include

src/backend/gp_libpq_fe) file(GLOB_RECURSE SOURCE_FILES

"src"

"*.c"

"*.h"

) add_executable(gpdb ${SOURCE_FILES})/<code>

然后选择 Run → Attach to Local Process… 出现下面 “Attach with LLDB to” 窗口。选择需要调试的进程id即可。(如果确定进程id请见前面小节)

如果 clion调试器console显示类似 “Debugger attached to process 38965” 的消息,则表示进程attach成功,可以使⽤ clion进⾏调试了。

通过图像化窗⼝定位到 “ExecProcNode” 函数,通过单击下图的⼩红圈处,即可设置断点在 ExecAgg() 调⽤处。

执⾏ SELECT count(*) FROM students 语句,可以使⽤各种调试命令(例如单步执⾏、断点、跳出函数等)⽅便的调试代码。

如上图所示,可以通过 IDE很直观的看到正在执⾏的代码⽚段,以及函数中变量的值。对于学习和调试Greenplum⾮常有帮助。

来源:Greenplum中文社区