Greenplum 实时数据仓库实践——Greenplum监控与运维

Posted wzy0623

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Greenplum 实时数据仓库实践——Greenplum监控与运维相关的知识,希望对你有一定的参考价值。

目录

9.1 权限与角色管理

9.1.1 Greenplum中的角色与权限

9.1.2 管理角色及其成员

9.1.3 管理对象权限

9.1.4 口令加密

9.2 数据导入导出

9.2.1 file协议及其外部表

9.2.2 gpfdist协议及其外部表

9.2.3 基于Web的外部表

9.2.4 外部表错误处理

9.2.5 使用gpload导入数据

9.2.6 使用COPY互拷数据

9.2.7 导出数据

9.2.8 格式化数据文件

9.3 性能优化

9.3.1 常用优化手段

9.3.2 控制溢出文件

9.3.3 查询剖析

9.4 例行监控

9.4.1 检查系统状态

9.4.2 检查磁盘空间使用

9.4.3 检查数据分布倾斜

9.4.4 查看数据库对象的元数据信息

9.4.5 查看会话的内存使用

9.4.6 查看工作文件使用信息

9.4.7 查看服务器日志文件

9.5 例行维护

9.5.1 定期vacuum

9.5.2 定期维护系统目录

9.5.3 加强的系统目录维护

9.5.4 为查询优化执行vacuum与analyze

9.5.5 统计信息自动收集

9.5.6 重建索引

9.5.7 管理数据库日志文件

9.6 推荐的监控与维护任务

9.6.1 数据库活动监控

9.6.2 硬件和操作系统监控

9.6.3 系统目录监控

9.6.4 数据库维护

9.6.5 补丁与升级

小结


        想要一个数据库长久健康的运行,离不开完备的运维工作,切忌只运而不维。针对Greenplum分布式数据库,集群由大量服务器组成,对运维人员或DBA,不仅要关注数据库本身,还要注意集群中各硬件的状况,及时发现并处理问题。本篇介绍权限与角色管理、数据导入导出、性能优化、例行监控、例行维护、推荐的监控与维护任务六方面常规工作内容,目标是满足Greenplum系统维护、使用等方面的要求,保证提供稳定高效的数据库服务。

9.1 权限与角色管理

        从“4.6 允许客户端连接”一节中已知,pg_hba.conf文件限定了允许连接Greenplum的客户端主机、用户名、访问的数据库,认证方式等。而用户名、口令,以及用户对数据库对象的使用权限保存在Greenplum的pg_authid、pg_roles、pg_class等元数据表中。

9.1.1 Greenplum中的角色与权限

        Greenplum采用基于角色的访问控制机制。通过角色机制,简化了用户和权限的关联性。Greenplum系统中的权限分为两种:系统权限和对象权限。系统权限是指系统规定用户使用数据库的权限,如连接数据库、创建数据库、创建用户等。对象权限是指在表、序列、函数等数据库对象上执行特殊动作的权限,其权限类型有select、insert、update、delete、references、create、connect、temporary、execute和usage等。

        Greenplum的角色与Oracle、SQL Server等数据库中的角色概念有所不同。这些系统中的所谓角色,是权限的组合和抽象,创建角色最主要的目的是简化对用户的授权。举一个简单的例子,假设需要给五个用户每个授予相同的五种权限,如果没有角色,需要授权二十五次,而如果把五种权限定义成一种角色,只需要先进行一次角色定义,再授权五次即可。

        然而Greenplum中的角色既可以代表一个数据库用户,又可以代表一组权限。角色所拥有的预定义的系统权限是通过角色属性实现的。角色可以是数据库对象的属主,也可以给其他角色赋予访问对象的权限。角色可以是其他角色的成员,成员角色可以从父角色继承对象权限。

        Greenplum系统可能包含多个数据库角色(用户或组),这些角色并不是运行服务器上操作系统的用户和组。为方便起见,可能希望维护操作系统用户名和Greenplum角色名的关系,因为很多客户端应用程序,如psql,使用当前操作系统用户名作为缺省的角色,gpadmin就以最典型的例子。

        用户通过Master实例连接Greenplum,Master使用pg_hba.conf文件里的条目验证用户的角色和访问权限。之后Master以当前登录的角色,从后台向Segment实例发布SQL命令。系统级定义的角色对所有数据库都是有效的。为了创建更多角色,首先需要使用超级用户gpadmin连接Greenplum。

        配置角色与权限时,应该注意以下问题:

  • 保证gpadmin系统用户安全。Greenplum需要一个UNIX用户ID安装和初始化Greenplum系统,这个系统用户ID就是gpadmin。gpadmin用户是Greenplum中缺省的数据库超级用户,也是Greenplum安装及其底层数据文件的文件系统属主。这个缺省的管理员账号是Greenplum的基础设计,缺少这个用户系统无法运行,并且没有方法能够限制gpadmin用户对数据库的访问。应该只使用gpadmin账号执行诸如扩容和升级之类的系统维护任务。任何以这个用户登录Greenplum主机的人,都可以读取、修改和删除任何数据,尤其是系统目录相关的数据库访问权力。因此,gpadmin用户的安全非常重要,仅应该提供给关键的系统管理员使用。应用的数据库用户应该永不作为gpadmin登录。 
  • 赋予每个登录用户不同的角色。出于记录和审核目的,每个登录Greenplum的用户都应该被赋予相应的数据库角色。对于应用程序或者Web服务,最好为每个应用或服务创建不同的角色。
  • 使用组管理访问权限。
  • 限制具有超级用户角色属性的用户。超级用户角色绕过Greenplum中所有的访问权限检查和资源队列,所以只应该将超级用户权限授予系统管理员。

9.1.2 管理角色及其成员

        这里的角色指的是一个可以登录到数据库,并开启一个数据库会话的用户。建议在创建角色时为其指定资源队列,否则缺省使用pg_default。CREATE ROLE命令用于创建一个角色,例如:

create role jsmith with login;

        一个数据库角色有很多属性,用以定义该角色可以在数据库中执行的任务,或者具有的系统权限。表9-1描述了有效的角色属性。

属性

描述

SUPERUSER | NOSUPERUSER

确定一个角色是否是超级用户。只有超级用户才能创建新的超级用户。缺省值为NOSUPERUSER

CREATEDB | NOCREATEDB

确定角色是否被允许创建数据库。缺省值为NOCREATEDB

CREATEROLE | NOCREATEROLE

确定角色是否被允许创建和管理其他角色。缺省值为NOCREATEROLE

INHERIT | NOINHERIT

确定角色是否从其所在的组继承权限。具有INHERIT属性的角色可以自动使用所属组已经被授予的数据库权限,无论角色是组的直接成员还是间接成员。缺省值为INHERIT

LOGIN | NOLOGIN

确定角色是否可以登录。具有LOGIN属性的角色可以将角色作为用户登录。没有此属性的角色被用于管理数据库权限(即用户组)。缺省值为NOLOGIN

CONNECTION LIMIT connlimit

如果角色能够登录,此属性指定角色可以建立多少个并发连接。缺省值为-1,表示没有限制

PASSWORD password

设置角色的口令。如果不使用口令认证,可以忽略此选项。如果没有指定口令,口令将被设置为null,此时该用户的口令认证总是失败。一个null口令也可以显示的写成PASSWORD NULL

ENCRYPTED | UNENCRYPTED

控制口令是否加密存储在系统目录中。缺省行为由password_encryption配置参数所决定,当前设置是MD5,如果要改为SHA-256加密,设置此参数为password。如果给出的口令字符串已经是加密格式,那么它被原样存储,而不管指定ENCRYPTED还是UNENCRYPTED。这种设计允许在dump/restore时重新导入加密的口令

VALID UNTIL ‘timestamp

设置一个日期和时间,在该时间点后角色的口令失效。如果忽略此选项,口令将永久有效

RESOURCE QUEUE queue_name

赋予角色一个命名的资源队列用于负载管理。角色发出的任何语句都受到该资源队列的限制。注意,这个RESOURCE QUEUE属性不会被继承,必须在每个用户级(登录)角色设置

DENY deny_interval | deny_point

在此时间区间内禁止访问

表9-1 角色属性

        可以在创建角色时,或者创建角色后使用ALTER ROLE命令指定这些属性:

alter role jsmith with password 'passwd123';  
alter role jsmith valid until 'infinity';  
alter role jsmith login;  
alter role jsmith resource queue adhoc;  
alter role jsmith deny day 'sunday';

        使用drop role或drop user命令删除角色(用户)。在删除角色前,先要收回角色所拥有的全部权限,或者先删除与角色相关联的所有对象,否则删除角色时会提示“cannot be dropped because some objects depend on it”错误。

        通常将多个权限合成一组,能够简化对权限的管理。使用这种方法,对于一个组中的用户,其权限可以被整体授予和回收。在Greenplum中的实现方式为,创建一个表示组的角色,然后将用户角色授予组角色的成员。下面的SQL命令使用CREATE ROLE创建一个名为admin组角色,该组角色具有CREATEROLE和CREATEDB系统权限。

create role admin createrole createdb;

        一旦组角色存在,就可以使用GRANT和REVOKE命令添加或删除组成员(用户角色): 

grant admin to john, sally;  
revoke admin from bob;

        为简化对象权限的管理,应当只为组级别的角色授予适当的权限。成员用户角色继承组角色的对象权限:

grant all on table mytable to admin;  
grant all on schema myschema to admin;  
grant all on database mydb to admin;

        角色属性LOGIN、SUPERUSER、CREATEDB和CREATEROLE不会当做普通的数据库对象权限被继承。为了让用户成员使用这些属性,必须执行SET ROLE设置一个角色具有这些属性。在上面的例子中,我们已经为admin指定了CREATEDB和CREATEROLE属性。sally是admin的成员,当以sally连接到数据库后,执行以下命令,使sally可以拥有父角色的CREATEDB和CREATEROLE属性。

set role admin;

        有关角色属性信息可以在系统表pg_authid中找到,pg_roles是基于系统表pg_authid的视图。系统表pg_auth_members存储了角色与其成员的关系。

9.1.3 管理对象权限

        当一个对象(表、视图、序列、数据库、函数、语言、模式或表空间)被创建,它的权限被赋予属主。属主通常是执行CREATE语句的角色。对于大多数类型的对象,其初始状态是只允许属主或超级用户在对象上做任何操作。为了允许其他角色使用对象,必须授予适当的权限。Greenplum对每种对象类型支持的权限如表9-2所示。

对象类型

权限

Tables、Views、Sequences

SELECT、INSERT、RULE、ALL

External Tables

SELECT、RULE、ALL

Databases

CONNECT、CREATE、TEMPORARY | TEMP、ALL

Functions

EXECUTE

Procedural Languages

USAGE

Schemas

CREATE、USAGE、ALL

Custom Protocol

SELECT、INSERT、RULE、ALL

表9-2 对象权限

        必须为每个对象单独授权。例如,授予数据库上的ALL权限,并不会授予数据库中全部对象的访问权限,而只是授予了该数据库自身的数据库级别的全部权限(CONNECT、CREATE、TEMPORARY等)。

        使用标准的GRANT和REVOKE SQL语句为角色授予或回收一个对象权限:

grant insert on mytable to jsmith;  
revoke all privileges on mytable from jsmith;

        可以使用DROP OWNED和REASSIGN OWNED命令为一个角色删除或重新赋予对象属主权限。只有对象的属主或超级用户能够执行此操作:

reassign owned by sally to bob;  
drop owned by visitor;

        Greenplum不支持行级和列级的访问控制,但是可以通过视图来模拟,限制查询的行或列。此时角色被授予对视图而不是基表的访问权限。对象权限存储在pg_class.relacl列中。Relacl是PostgreSQL支持的数组属性,该数组成员是抽象的数据类型aclitem。每个ACL实际上是一个由多个aclitem构成的链表。

9.1.4 口令加密

        Greenplum缺省使用MD5为用户口令加密,通过适当配置服务器参数,也能实现口令的SHA-256加密存储。为了使用SHA-256加密,客户端认证方法必须设置为PASSWORD而不是缺省的MD5。口令虽然以加密形式存储在系统表中,但仍然以明文在网络间传递。为了避免这种情况,应该建立客户端与服务器之间的SSL加密通道。

1. 系统级启用SHA-256加密

# 设置缺省的口令加密算法
gpconfig -c password_hash_algorithm -v 'SHA-256'
# 重载参数使之动态生效
gpstop -u
# 查看
gpconfig -s password_hash_algorithm

2. 会话级启用SHA-256加密

-- 用gpadmin登录Greenplum后执行
set password_hash_algorithm = 'SHA-256';
-- 查看
show password_hash_algorithm;

3. 验证口令加密方式生效
(1)建立一个具有login权限的新角色,并设置口令。

create role testdb with password 'testdb12345#' login;

(2)修改客户端认证方法,允许存储SHA-256加密的口令。
        下面的shell命令将在pg_hba.conf文件的第一行添加一条记录。注意pg_hba.conf文件中记录的匹配顺序。

sed -i '1ihost all testdb 0.0.0.0/0 password' /data/master/gpseg-1/pg_hba.conf

(3)重载pg_hba.conf配置

gpstop -u

(4)以刚创建的testdb用户登录数据库,在提示时输入正确的口令。

psql -d postgres -h mdw -U testdb

        验证口令被以SHA-256哈希方式存储,加密后的口令存储在pg_authid.rolpasswod字段中。作为超级用户登录,执行下面的查询:

postgres=# select rolpassword from pg_authid where rolname = 'testdb';
                              rolpassword                               
------------------------------------------------------------------------
 sha25650c2445bab257f4ea94ee12e5a6bf1400b00a2c317fc06b6ff9b57975bd1cde1
(1 row)

9.2 数据导入导出

        本节介绍Greenplum的各种数据导入导出方法。所选择的方法依赖于数据源的特性,如位置、数据量、格式、需要的转换等。最简单的情况下,一条COPY命令就可将Greenplum主实例上的文本文件导入表中。对于少量数据,这种方式不需要更多步骤,并提供了良好的性能。COPY命令在Master主机上的单个文件与数据库表之间拷贝数据。这种方式拷贝的数据量受限于文件所在系统所允许的单一文件最大字节数。对于大数据集,更为有效的数据装载方式是利用多个Segments并行导入数据。该方式允许同时从多个文件系统导入数据,实现很高的数据传输速率。用gpfdist创建的外部表会使用所有Segment导入或导出数据,并且完全并行操作。

        无论使用哪种方法,导入完数据都应运行ANALYZE。ANALYZE或VACUUM ANALYZE(只对系统目录表)为查询优化器更新表的统计信息,以做出最好的查询计划,避免由于数据增长或缺失统计信息导致性能问题。

9.2.1 file协议及其外部表

        file://协议用于指定操作系统文件位置的URI中。URI包括主机名、端口和文件路径。每个文件必须位于Greenplum数据库超级用户(gpadmin)可访问的Segment主机上。URI中使用的主机名必须与gp_segment_configuration系统目录表中注册的段主机名匹配。LOCATION子句可以有多个URI。

        通过定义file协议的外部表,可以很容易地将外部数据导入普通表中,如下例所示。

# 分隔符的16进制
select to_hex(ascii('|'));
# 创建外部表
\\c dw gpadmin
create external table files_zz_ext (
   fid varchar(128), 
   server varchar(45), 
   ffid varchar(255), 
   flen bigint, 
   filemd5 varchar(64), 
   ttime integer, 
   lvtime integer, 
   vtimes integer, 
   stat smallint ) 
location ('file://mdw:5432/data/zz/files_000', 
          'file://mdw:5432/data/zz/files_001', 
          'file://mdw:5432/data/zz/files_002',
          'file://mdw:5432/data/zz/files_003',
          'file://smdw:5432/data/zz/files_004', 
          'file://smdw:5432/data/zz/files_005',
          'file://smdw:5432/data/zz/files_006',
          'file://sdw3:5432/data/zz/files_007', 
          'file://sdw3:5432/data/zz/files_008',
          'file://sdw3:5432/data/zz/files_009',
format 'text' (delimiter E'\\x7c' null ''); 

-- 修改外部表属主
alter external table files_zz_ext owner to dwtest;

# 导入数据
set gp_autostats_mode=none;
insert into files_zz1 select * from files_zz_ext;
# 分析表
vacuum freeze analyze files_zz1;

        在LOCATION子句中指定的URI数是将并行工作以访问外部表的Segment实例数。对于每个URI,Greenplum将指定主机上的一个Segment分配给文件。为了在导入数据时获得最大的并行性,最好将数据分散到与Segment数量相同的多个文件中,这可确保所有Segment都参与工作。每个Segment主机上的外部文件数不能超过该主机上的Segment实例数。例如,如果集群中每个Segment主机有四个实例,则可以在每个Segment主机上放置四个外部文件。基于file://协议的表只能是可读外部表。

        系统视图pg_max_external_files显示每个外部表允许的最大外部表文件数,该视图仅适用于file://协议。

postgres=# SELECT * FROM pg_max_external_files;
 hostname | maxfiles 
----------+----------
 smdw     |        6
 mdw      |        6
 sdw3     |        6
(3 rows)

9.2.2 gpfdist协议及其外部表

1. gpfdist
        gpfdist是一个并行文件分布程序,用于对本地文件的并行访问。它是一个操作外部表的HTTP服务器,使Segment可以从多个文件系统的外部表并行装载数据。可以在多个不同的主机上运行gpfdist实例,并能够并行使用它们。

        可以选择在Master以外的其他机器上运行gpfdist,例如一个专门用于ETL处理的主机。使用gpfdist命令启动gpfdist,该命令位于Master主机和每个Segment主机的$GPHOME/bin目录中。可以在当前目录位置或者指定任意目录启动gpfdist,缺省的端口是8080。下面是一些启动gpfdist的例子。

# 处理当前目录中的文件,使用缺省的8080端口
gpfdist &

# 指定要导入的文件目录、HTTP端口号、消息与错误日志文件,进程在后台运行
gpfdist -d /home/gpadmin/load_data/ -p 8081 -l /home/gpadmin/log &

# 在同一个ETL主机上运行多个gpfdist实例,每个实例使用不同的目录和端口
gpfdist -d /home/gpadmin/load_data1/ -p 8081 -l /home/gpadmin/log1 &  
gpfdist -d /home/gpadmin/load_data2/ -p 8082 -l /home/gpadmin/log2 &

# gpfdist不允许在根目录上启动服务,可通过/../的方式间接实现
nohup gpfdist -p 8080 -d /../ &

        Greenplum没有提供停止gpfdist的命令,要直接使用操作系统的kill命令停止gpfdist进程。

ps -ef | grep gpfdist | grep -v grep | awk 'print $2' | xargs kill -9

2. gpfdist外部表
        在外部数据文件所在的主机上运行gpfdist命令,外部表定义中使用gpfdist://协议引用一个运行的gpfdist实例。gpfdist自动解压缩gzip(.gz)和bzip2(.bz2)文件。可以使用通配符(*)或其它其他C语言风格的模式匹配多个需要读取的文件。指定的文件应该位于启动gpfdist实例时指定的目录下。

        为了创建一个gpfdist外部表,需要指定输入文件的格式和外部数据源的位置。使用gpfdist或gpfdists协议(gpfdist的安全版本)之一访问外部表数据源。一条CREATE EXTERNAL TABLE语句中使用的协议必须唯一。

        使用gpfdist外部表的步骤如下:

  1. 启动gpfdist文件服务器。
  2. 定义外部表。
  3. 将数据文件放置于外部表定义中指定的位置。
  4. 使用SQL命令查询外部表。

        Greenplum提供可读与可写两种gpfdist外部表,但一个外部表不能既可读又可写。一个gpfdist可读外部表的例子如下所示。

# 分别在两个主机mdw、smdw上启动gpfdist服务
nohup gpfdist -p 8081 -d /../ &

-- 创建外部表
set search_path to ext;
create external table test_ext1 (
  userid bigint,
  avid bigint,
  playcount bigint,
  praisecount bigint,
  commentcount bigint,
  sharecount bigint,
  updatetime timestamp
) 
location 
('gpfdist://mdw:8081/data/*.txt', 'gpfdist://smdw:8081/data/*.txt')
format 'text' (delimiter '|');

-- 向普通表中装载数据
insert into space.work_heat_user_operate select * from ext.test_ext;

        下面的SQL语句建立可写外部表并插入数据。

create writable external table example1 (name text, date date, amount float4, category text, desc1 text)  
       location ('gpfdist://mdw:8081/data/sales.out') format 'text' ( delimiter '|' null ' ')  
       distributed by (name);  

insert into example1 values ('aaa','2022-01-01',100.1,'aaa','aaa');  
insert into example1 values ('bbb','2022-01-02',200.1,'bbb','bbb');  

        结果是在mdw上建立了如下内容的/data/sales.out文件:

aaa|2022-01-01|100.1|aaa|aaa
bbb|2022-01-02|200.1|bbb|bbb

        可以使用insert into target_table select ... from external_table或create table target_table as select ... from external_table命令从外部表向普通表导入数据。同样也可以使用insert into external_table select ... from normal_table向可写外部表导出数据。drop external table命令只删除外部表定义,并不会删除外部文件的任何内容。

9.2.3 基于Web的外部表

        外部表可以是基于文件的或基于Web的。基于文件的外部表访问静态平面文件。在查询运行时数据是静态的,数据可重复读。基于Web的外部表通过Web服务器的http协议或通过执行操作系统命令或脚本,访问动态数据源。数据不可重复读,因为在查询运行时数据可能改变。

        CREATE EXTERNAL WEB TABLE语句创建一个web外部表。web外部表允许Greenplum将动态数据源视作一个常规数据库表。可以定义基于命令或基于URL的web外部表,但不能在一条建表命令中混用两种定义。

1. 基于命令的web外部表
        用一个shell命令或脚本的输出定义基于命令的web表数据。在CREATE EXTERNAL WEB TABLE语句的EXECUTE子句指定需要执行的命令。外部表中的数据是命令运行时的数据。EXECUTE子句在特定Master或Segment上运行shell命令或脚本。脚本必须是gpadmin用户可执行的,并且位于所有Master和Segment主机的相同位置上,Segment并行运行命令。

        外部表定义中指定的命令从数据库执行,数据库不能从.bashrc或.profile获取环境变量,因此需要在EXECUTE子句中设置环境变量。下面的外部表运行一个Greenplum Master主机上的命令。

create external web table output (output text)  
execute 'PATH=/home/gpadmin/programs; export PATH; myprogram.sh'  
    on master   
format 'text';

        下面的命令定义一个web表,在五个段上运行一个名为get_log_data.sh脚本文件。

create external web table log_output (linenum int, message text)   
execute '/home/gpadmin/get_log_data.sh' ON 5   
format 'text' (delimiter '|');  

        Greenplum集群中每台主机的相同位置上都必须有同一个可执行的脚本,否则查询会报错:

dw=# select * from log_output;
ERROR:  external table log_output command ended with error. sh: /home/gpadmin/get_log_data.sh: No such file or directory  (seg7 slice1 140.210.73.66:6001 pid=10461)
DETAIL:  Command: execute:/home/gpadmin/get_log_data.sh

        对该外部表的查询会返回每个Segment输出的并集,如get_log_data.sh脚本内容如下:

#!/bin/bash  
echo "1|aaa"  
echo "2|bbb"

        则该表将返回10条(每个段两条)数据:

dw=# select * from log_output;
 linenum | message 
---------+---------
       1 | aaa
       2 | bbb
       1 | aaa
       2 | bbb
       1 | aaa
       2 | bbb
       1 | aaa
       2 | bbb
       1 | aaa
       2 | bbb
(10 rows)

        下面创建一个执行脚本的可写web外部表。

create writable external web table example2  
      (name text, date date, amount float4, category text, desc1 text) 
execute 'PATH=/home/gpadmin/programs; export PATH; myprogram1.sh' 
format 'text' (delimiter '|')  
distributed randomly;

        可写外部表不能使用on子句,否则报错:

ERROR:  ON clause may not be used with a writable external table

        myprogram1.sh的内容如下:

#!/bin/bash  
while read line  
do  
    echo "File:$line" >> /home/gpadmin/programs/a.txt  
done

        下面将脚本设置为可执行,并复制到集群所有主机的相同目录下:

chmod 755 ./programs/myprogram1.sh
scp -r programs 210.73.209.102:/home/gpadmin/
scp -r programs 140.210.73.66:/home/gpadmin/

        现在向外部表中插入数据:

insert into example2 values 
('aaa','2022-01-01',100.1,'aaa','aaa'), 
('bbb','2022-01-02',200.1,'bbb','bbb'),
('ccc','2022-01-03',300.1,'ccc','ccc');  

        插入的数据通过管道输出给myprogram1.sh并执行,输出到a.txt文件。这里插入了三条数据,在我的环境中,构成集群的三台主机上都生成了一个a.txt文件,每个文件中保存了一条数据,可见是三个不同主机上的Segment并行向外部文件写入了数据。

2. 基于URL的web外部表
        基于URL的web表使用HTTP协议从Web服务器访问数据,web表数据是动态的。在LOCATION子句中使用http://指定文件在Web服务器上的位置。web数据文件必须在所有Segment主机能够访问的Web服务器上。URL的数量对应访问该web表时并行的最少Segment数量。下面的例子定义了一个从多个URL获取数据的web表。

create external web table ext_expenses (  
    name text, date date, amount float4, category text, description text)   
location ('http://mdw/sales/file.csv',  
          'http://mdw/exec/file.csv',  
          'http://mdw/finance/file.csv',  
          'http://mdw/ops/file.csv',  
          'http://mdw/marketing/file.csv',  
          'http://mdw/eng/file.csv'   
      )  
format 'csv';

9.2.4 外部表错误处理

        使用CREATE TABLE AS SELECT或INSERT INTO命令查询外部表数据时,如果数据包含错误,缺省行为是整条命令失败,没有数据被导入到目标数据库表中。SEGMENT REJECT LIMIT子句允许隔离外部表中格式错误的数据,并继续导入格式正确的行。使用SEGMENT REJECT LIMIT设置一个错误阈值,指定拒绝的数据行数(缺省)或一个占总行数的百分比(1 -~ 100)。如果错误行数达到了SEGMENT REJECT LIMIT的值,整个外部表操作失败,没有数据行被处理。限制的错误行数是相对于一个段而不是整个操作的。如果错误行数没有达到SEGMENT REJECT LIMIT值,操作处理所有正确的行,丢弃错误行,或者可选地将格式错误的行写入日志表。LOG ERRORS子句允许保存错误行以备后续检查。

        设置SEGMENT REJECT LIMIT会使Greenplum以单行错误隔离模式扫描外部数据。当外部数据行出现多余属性、缺少属性、数据类型错误、无效的客户端编码序列等格式错误时,单行错误隔离模式将错误行丢弃或写入日志表。Greenplum不检查约束错误,但可以在查询外部表时过滤约束错误。例如,消除重复键值错误:

insert into table_with_pkeys select distinct * from external_table;

        下面的例子记录错误信息,并设置错误行阈值为10。错误数据通过Greenplum的内部函数gp_read_error_log('external_table_name')访问。

create external table ext_expenses ( name text, date date, amount float4, category text, desc1 text )  
       location ('gpfdist://mdw:8081/data/*', 'gpfdist://smdw:8081/data/*')  
       format 'text' (delimiter '|')  
       log errors segment reject limit 10 rows; 

9.2.5 使用gpload导入数据

        Greenplum的gpload应用程序使用可读外部表和并行文件系统gpfdist或gpfdists导入数据。它并行处理基于文件创建的外部表,允许用户在单一配置文件中配置数据格式、外部表定义,以及gpfdist或gpfdists的设置。

        gpload需要依赖某些Greenplum安装中的文件,如gpfdist和Python,还要能通过网络访问所有Segment主机。gpload的控制文件是一个YAML(Yet Another Markup Language)格式的文件,在其中指定Greenplum连接信息、gpfdist配置信息、外部表选项、数据格式等。下面是一个名为my_load.yml的控制文件内容:

---
VERSION: 1.0.0.1
DATABASE: dw
USER: gpadmin
HOST: mdw
PORT: 5432
GPLOAD:
   INPUT:
    - SOURCE:
         LOCAL_HOSTNAME:
           - smdw
         PORT: 8081
         FILE:
           - /home/gpadmin/staging/*.txt
    - COLUMNS:
           - name: text
           - date: date
           - amount: float4
           - category: text
           - desc1: text
    - FORMAT: text
    - DELIMITER: '|'
    - ERROR_LIMIT: 25
   OUTPUT:
    - TABLE: t1
    - MODE: INSERT
   SQL:
    - BEFORE: "INSERT INTO audit VALUES('start', current_timestamp)"
    - AFTER: "INSERT INTO audit VALUES('end', current_timestamp)"

        gpload控制文件使用YAML 1.1文档格式,为了定义数据装载的各种步骤,它定义了自己的schema。控制文件必须是一个有效的YAML文档。gpload程序按顺序处理控制文件文档,并使用空格识别文档中各段之间的层次关系,因此空格的使用非常重要。不要使用TAB符代替空格,YAML文档中不要出现TAB符。

        LOCAL_HOSTNAME指定运行gpload的本地主机名或IP地址。如果机器配置了多块网卡,可以为每块网卡指定一个主机名,允许同时使用多块网卡传输数据。比如smdw上配置了两块网卡,可以如下配置LOCAL_HOSTNAME:

LOCAL_HOSTNAME:  
 - smdw-1  
 - smdw-2 

        下面看一个gpload示例。我们先在smdw上准备本地文件数据如下:

[gpadmin@vvml-z2-greenplum~/staging]$cat a.txt 
aaa|2022-01-01|100.1|aaa|aaa
bbb|2022-01-02|100.2|bbb|bbb
[gpadmin@vvml-z2-greenplum~/staging]$cat b.txt 
aaa|2022-01-03|200.1|aaa|aaa  
bbb|2022-01-04|200.2|bbb|bbb

        然后建立目标表和audit表:

\\c dw gpadmin
create table t1 ( name text, date date, amount float4, category text, desc1 text );  
create table audit(flag varchar(10),st timestamp);  

        最后执行gpload:

[gpadmin@vvml-z2-greenplum~/staging]$gpload -f my_load.yml
2022-01-11 09:29:31|INFO|gpload session started 2022-01-11 09:29:31
2022-01-11 09:29:32|INFO|setting schema 'rds' for table 't1'
2022-01-11 09:29:32|INFO|started gpfdist -p 8081 -P 8082 -f "/home/gpadmin/staging/*.txt" -t 30
2022-01-11 09:29:32|INFO|running time: 0.45 seconds
2022-01-11 09:29:32|INFO|rows Inserted          = 4
2022-01-11 09:29:32|INFO|rows Updated           = 0
2022-01-11 09:29:32|INFO|data formatting errors = 0
2022-01-11 09:29:32|INFO|gpload succeeded
[gpadmin@vvml-z2-greenplum~/staging]$

        查询目标表和audit表确认执行结果:

dw=# select * from t1;
 name |    date    | amount | category | desc1 
------+------------+--------+----------+-------
 bbb  | 2022-01-02 |  100.2 | bbb      | bbb
 bbb  | 2022-01-04 |  200.2 | bbb      | bbb
 aaa  | 2022-01-01 |  100.1 | aaa      | aaa
 aaa  | 2022-01-03 |  200.1 | aaa      | aaa  
(4 rows)

dw=# select * from audit;
 flag  |             st             
-------+----------------------------
 start | 2022-01-11 09:29:32.053015
 end   | 2022-01-11 09:29:32.246904
(2 rows)

9.2.6 使用COPY互拷数据

        COPY是Greenplum的SQL命令,它在外部文件和表之间互拷数据。COPY FROM命令将本地文件追加到数据表中,而COPY TO命令将数据表中的数据覆盖写入本地文件。COPY命令是非并行的,数据在Master实例上以单进程处理,因此只推荐对非常小的数据文件使用COPY命令。本地文件必须在Master主机上,缺省的文件格式是逗号分隔的CSV文本文件。下面是一个用copy导入数据的例子。

[gpadmin@vvml-z2-greenplum~/staging]$scp -r /home/gpadmin/staging/ 114.112.77.198:/home/gpadmin/
[gpadmin@vvml-z2-greenplum~/staging]$psql -h mdw -d dw
psql (9.4.24)
Type "help" for help.

dw=# create table t2 (like t1);  
NOTICE:  table doesn't have 'DISTRIBUTED BY' clause, defaulting to distribution columns from LIKE table
CREATE TABLE
dw=# copy t2 from '/home/gpadmin/staging/a.txt' with delimiter '|';
COPY 2
dw=# select * from t2;
 name |    date    | amount | category | desc1 
------+------------+--------+----------+-------
 bbb  | 2022-01-02 |  100.2 | bbb      | bbb
 aaa  | 2022-01-01 |  100.1 | aaa      | aaa
(2 rows)

        copy命令中可以指定文件格式、分隔符、字符集等属性:

copy test from '/tmp/file0' with (format csv, delimiter '|', encoding 'latin1');

        下面的例子将表数据导出到Master的本地文件中。如果文件不存在则建立文件,否则会用导出数据覆盖文件原来的内容。

dw=# copy (select * from t2) to '/home/gpadmin/staging/c.txt' with delimiter '|';
COPY 2

[gpadmin@vvml-z2-greenplum~/staging]$ssh 114.112.77.198 cat /home/gpadmin/staging/c.txt
bbb|2022-01-02|100.2|bbb|bbb
aaa|2022-01-01|100.1|aaa|aaa
[gpadmin@vvml-z2-greenplum~/staging]$

        Greenplum利用客户端与Master服务器之间的连接,能从STDIN或STDOUT拷贝数据,通过管道可以实现类似于流复制的功能,例如:

psql -h src -d srcdb -c 'copy test to stdout' | psql -h des -d desdb -c 'copy test from stdin'

        缺省时,COPY在遇到第一个错误就会停止运行。如果数据含有错误,操作失败,没有数据被装载。如果以单行错误隔离模式运行COPY,将跳过含有错误格式的行,装载具有正确格式的行。如果数据违反了NOT NULL或CHECK等约束条件,操作仍然是‘all-or-nothing’输入模式,整个操作失败,没有数据被装载。

        修改a.txt文件,制造一行格式错误的数据。

[gpadmin@vvml-z2-greenplum~/staging]$cat a.txt 
aaa,2022-01-01,100.1,aaa,aaa
bbb|2022-01-02|100.2|bbb|bbb

        执行copy命令。与导出不同,导入会向表中追加数据。

dw=# copy t2 from '/home/gpadmin/staging/a.txt'  
dw-# with delimiter '|' log errors segment reject limit 5 rows;  
NOTICE:  found 1 data formatting errors (1 or more input rows), rejected related input data
COPY 2
dw=# select * from t2;
 name |    date    | amount | category | desc1 
------+------------+--------+----------+-------
 bbb  | 2022-01-02 |  100.2 | bbb      | bbb
 bbb  | 2022-01-02 |  100.2 | bbb      | bbb
 aaa  | 2022-01-01 |  100.1 | aaa      | aaa
(3 rows)

dw=# select gp_read_error_log('t2');
                                                 gp_read_error_log                                                  
--------------------------------------------------------------------------------------------------------------------
 ("2022-01-11 09:47:30.923811+08",t2,<stdin>,1,,"missing data for column ""date""","aaa,2022-01-01,100.1,aaa,aaa",)
(1 row)

        再次修改文件,将name字段对应的数据置空,因为该字段定义为NOT NULL,所以违反约束,没有数据被拷贝,也不会更新错误日志。

[gpadmin@vvml-z2-greenplum~/staging]$cat a.txt 
|2022-01-01|100.1|aaa|aaa
bbb|2022-01-02|100.2|bbb|bbb

dw=# truncate table t2; 
TRUNCATE TABLE
dw=# alter table t2 alter column name set not null;
ALTER TABLE
dw=# copy t2 from '/home/gpadmin/staging/a.txt'  
dw-# with (FORMAT CSV, delimiter E'|', FORCE_NULL(name)) 
dw-# log errors segment reject limit 5 rows;
ERROR:  null value in column "name" violates not-null constraint
DETAIL:  Failing row contains (null, 2022-01-01, 100.1, aaa, aaa).
CONTEXT:  COPY t2, line 1: "|2022-01-01|100.1|aaa|aaa"
dw=# select * from t2;
 name | date | amount | category | desc1 
------+------+--------+----------+-------
(0 rows)

dw=# select gp_read_error_log('t2');
                                                 gp_read_error_log                                                  
--------------------------------------------------------------------------------------------------------------------
 ("2022-01-11 09:47:30.923811+08",t2,<stdin>,1,,"missing data for column ""date""","aaa,2022-01-01,100.1,aaa,aaa",)
(1 row)

        copy时可能出现如下错误:

ERROR:  invalid byte sequence for encoding "UTF8": 0x00

        这是一个已知错误,解决方法是先替换掉文件中的\\0字符串再执行copy:

# cat命令
cat audit_obj_detail_article.txt | sed 's/\\\\0//g' > audit_obj_detail_article.txt.1
# 或者perl命令,更快的方法
perl -p -i -e "s/\\x5c0//g" audit_obj_detail_article.txt

        与SQL命令copy读取Master上的文件不同,psql的命令\\copy从客户端本地读取文件:

\\copy test from '/tmp/file0' delimiter '|';

9.2.7 导出数据

        一个可写外部表允许用户从其他数据库表选择数据行并输出到文件、命名管道或应用。如前面的example1和example2所示,可以定义基于gpfdist或Web的可写外部表。对于使用gpfdist协议的外部表,Segment将它们的数据发送给gpfdist,gpfdist将数据写入命名文件中。gpfdist必须运行在Segment能够在网络上访问的主机上。gpfdist指向一个输出主机上的文件位置,将从Segment接收到的数据写入文件。一个可写web外部表的数据作为数据流发送给应用,例如,从Greenplum导出数据并发送给一个连接其他数据库的应用或向别处装载数据的ETL工具。可写web外部表使用EXECUTE子句指定一个运行在Segment主机上的shell命令、脚本或应用,接收输入数据流。

        可以选择为可写外部表声明分布策略。缺省时,可写外部表使用随机分布。如果要导出的源表是哈希分布的,为外部表定义相同的分布键列会提升数据导出性能,因为这消除了数据行在内部互联网络上的移动。如果导出一个特定表的数据,可以使用LIKE子句拷贝源表的列定义与分布策略。

dw=# create writable external table unload_expenses ( like t1 )  
dw-# location ('gpfdist://mdw:8081/data/expenses1.out', 'gpfdist://smdw:8081/data/expenses2.out')  
dw-# format 'text' (delimiter ',');  
NOTICE:  table doesn't have 'DISTRIBUTED BY' clause, defaulting to distribution columns from LIKE table
CREATE EXTERNAL TABLE

        可写外部表只允许INSERT操作。如果执行导出的用户不是外部表的属主或超级用户,必须授予对外部表的INSERT权限。

grant insert on unload_expenses TO admin;

        导出数据并查看输出文件。

dw=# insert into unload_expenses select * from t1; 
INSERT 0 4

# mdw上的输出文件
[gpadmin@vvml-z2-greenplum~]$cat /data/expenses1.out
bbb,2022-01-02,100.2,bbb,bbb
bbb,2022-01-04,200.2,bbb,bbb
[gpadmin@vvml-z2-greenplum~/staging]$

# smdw上的输出文件
[gpadmin@vvml-z2-greenplum~]$cat /data/expenses2.out
aaa,2022-01-01,100.1,aaa,aaa
aaa,2022-01-03,200.1,aaa,aaa  
[gpadmin@vvml-z2-greenplum~]$

        如example2所示,也可以定义一个可写的外部web表,发送数据行到脚本或应用。脚本文件必须接收输入流,而且必须存在于所有Segment的主机的相同位置上,并可以被gpadmin用户执行。Greenplum集群中的所有Segment都执行脚本,无论Segment是否有需要处理的输出行。

        允许外部表执行操作系统命令或脚本会带来相应的安全风险。为了在可写外部web表定义中禁用EXECUTE,可在Master的postgresql.conf文件中设置gp_external_enable_exec服务器配置参数为off:

gp_external_enable_exec = off

        正如前面说明COPY命令时所看到的,COPY TO命令也可以用来导出数据。它使用Master主机上的单一进程,将表中数据拷贝到Master主机上的一个文件(或标准输入)中。COPY TO命令重写整个文件,而不是追加记录。

9.2.8 格式化数据文件

        使用Greenplum工具导入或导出数据时,必须指定数据的格式。CREATE EXTERNAL TABLE、gpload和COPY都包含指定数据格式的子句。数据可以是固定分隔符的文本或逗号分隔值(CSV)格式。外部数据必须是Greenplum可以正确读取的格式。

1. 行分隔符
        Greenplum需要数据行以换行符(LF,Line feed,ASCII值0x0A)、回车符(CR,Carriage return,ASCII值0x0D)或回车换行符(CR+LF,0x0D 0x0A)作为行分隔符。LF是类UNIX操作系统中标准的换行符。而Windows或Mac OS X使用CR或CR+LF。所有这些表示一个新行的特殊符号都被Greenplum作为行分隔符所支持。

2. 列分隔符
        文本文件和CSV文件缺省的列分隔符分别是TAB(ASCII值为0x09)和逗号(ASCII值为0x2C)。在定义数据格式时,可以在CREATE EXTERNAL TABLE或COPY命令的DELIMITER子句,或者gpload的控制文件中,声明一个单字符作为列分隔符。分隔符必须出现在字段值之间,不要在一行的开头或结尾放置分隔符。如使用管道符(|)作为列分隔符:

data value 1|data value 2|data value 3 

        下面的建表命令显示以管道符作为列分隔符:

create external table ext_table (name text, date date)  
location ('gpfdist://host:port/filename.txt)  
format 'text' (delimiter '|');

3. 表示空值
        空值(NULL)表示一列中的未知数据。可以指定数据文件中的一个字符串表示空值。文本文件中表示空值的缺省字符串为\\N,CSV文件中表示空值的缺省字符串为不带引号的空串(两个连续的逗号)。定义数据格式时,可以在CREATE EXTERNAL TABLE、COPY命令的NULL子句,或者gpload的控制文件中,声明其他字符串表示空值。例如,若不想区分空值与空串,就可以指定空串表示NULL。使用Greenplum导出工具时,任何与声明代表NULL的字符串相匹配的数据项都被认为是空值。

4. 转义
        列分隔符与行分隔符在数据文件中具有特殊含义。如果实际数据中也含有这个符号,必须对这些符号进行转义,以使Greenplum将它们作为普通数据而不是列或行的分隔符。文本文件缺省的转义符为一个反斜杠(\\),CSV文件缺省的转义符为一个双引号(")。

(1)文本文件转义
        可以在CREATE EXTERNAL TABLE、COPY的ESCAPE子句,或者gpload的控制文件中指定转义符。假设有以下三个字段的数据:

backslash = \\
vertical bar = |
exclamation point = !

        指定管道符(|)为列分隔符,反斜杠(\\)为转义符。则对应的数据行格式如下:

backslash = \\\\ | vertical bar = \\| | exclamation point = !

        可以对八进制或十六进制序列应用转义符。在装载进Greenplum时,转义后的值就是八进制或十六进制的ASCII码所表示的字符。例如,取址符(&)可以使用十六进制的(\\0x26)或八进制的(\\046)表示。

        如果要在CREATE EXTERNAL TABLE、COPY命令的ESCAPE子句,或者gpload的控制文件中禁用转义,可如下设置:

ESCAPE 'OFF'

        该设置常用于输入数据中包含很多反斜杠(如Web日志数据)的情况。

(2)CSV文件转义
        可以在CREATE EXTERNAL TABLE、COPY的ESCAPE子句,或者gpload的控制文件中指定转义符。假设有以下三个字段的数据:

Free trip to A,B
5.89
Special rate "1.79"

        指定逗号(,)为列分隔符,一个双引号(")为转义符。则数据行格式如下:

"Free trip to A,B","5.89","Special rate ""1.79"""

        将字段值置于双引号中能保留字符串中头尾的空格。

5. 字符编码
        在将一个Windows操作系统上生成的数据文件装载到Greenplum前,先使用dos2unix系统命令去除只有Windows使用的字符,如删除文件中的CR('\\x0D')。

9.3 性能优化

        Greenplum为查询动态分配资源,数据所在的位置、查询所使用的段数量、集群的总体健康状况等因素都会影响查询性能。

9.3.1 常用优化手段

        当进行了适当的服务器参数设置后,Greenplum内部系统会自动实施某些优化,理解它们对于开发高性能应用大有裨益。对用户来说,表设计与SQL语句的写法对性能的影响很大,然而这些技术对大部分数据库系统来说是通用的,如规范化设计、索引设计、连接时驱动表的选择、利用提示影响优化器等等。有很多这方面的资料,本篇不展开讨论这些内容。

        Greenplum数据库会动态消除不相关的分区,

以上是关于Greenplum 实时数据仓库实践——Greenplum监控与运维的主要内容,如果未能解决你的问题,请参考以下文章

Greenplum 实时数据仓库实践——实时数据装载

Greenplum 实时数据仓库实践——实时数据装载

Greenplum 实时数据仓库实践——实时数据装载

Greenplum 实时数据仓库实践——维度表技术

Greenplum 实时数据仓库实践——数据仓库简介

Greenplum 实时数据仓库实践——数据仓库简介