刚把一套几十张表的业务库从旧集群迁到新集群,整个过程用了不到半天,数据量大概4TB,校验下来零误差。用的就是Greenplum官方自带的gpcopy,没花钱没折腾第三方ETL。这篇文章把装的过程、参数怎么配、坑都有哪些,按我实际操作的顺序写一遍,给后面要迁库的朋友参考。
先说重点:gpcopy是Greenplum官方维护的专用迁移工具,只干一件事——把表和数据的定义从源集群搬运到目标集群。它跟pg_dump、ETL工具最不一样的地方在于,gpcopy原生感知Greenplum的分布式架构,数据迁移时能按“分布键路由+Segment并行写入”的方式跑,速度和一致性都不是逻辑导出的方式能比的。适合的场景很清晰:同构集群升级、机房迁移、集群拆分合并、验证备份恢复结果。
1. 项目拆解:gpcopy到底解决什么问题
1.1 传统迁移方式的痛点
Greenplum迁移这件事,早些年大家常用三种土办法。
第一种是pg_dump导SQL再导入,对小库还行,一旦上了TB级,单进程导出加单点导入,跑几十个小时是常事。第二种是拿ETL工具从源端查出来再写进目标端,这个思路没问题,但ETL工具通常不感知Greenplum的分布键规则,写入时在Master做数据重分布,代价极高,而且字段类型映射容易出幺蛾子。第三种最野,直接同步文件系统,比如用存储层的复制功能把数据目录整体拷过去,这种方式要求源和目标集群配置一致,而且必须停写,生产环境基本没法用。
1.2 gpcopy的核心设计思路
gpcopy的设计思路很直接:既然目标是另一个Greenplum集群,那就让源端的Segment把数据并行读出来,然后通过网络直接发给目标端的Segment,中间不经过Master的集中处理。相当于每个Segment干自己那份活,一堆Segment同时动手,整体速度跟着Segment数量线性往上走。
这个设计还带来一个额外的好处:分布键一致的表,数据可以直接按照分布键路由到目标Segment上,不需要额外的重分布过程。源端和目标端的分布键如果定义一致,整个过程几乎可以做到无损直写。这也是gpcopy官方推荐用于集群升级场景的根本原因。
顺带说一句,gpcopy复制的不单是表和行数据,还包括序列、视图、函数、外部表定义这类元数据对象。类型覆盖上,常见的大对象、自定义类型、分区表都在支持范围内。也就是说,它做的不是“只搬数据”,而是“把库结构也搬过去再塞数据”,省了很多手工补DDL的活。
2. 安装过程记录
2.1 版本匹配是头等大事
装gpcopy之前,务必先确认两件事:源端Greenplum版本和目标端Greenplum版本。不同大版本对应不同gpcopy版本,用错版本多半会在运行时报语法错误或者连不上Master。
参考对应关系大致如下:
| Greenplum版本 | 推荐gpcopy版本 | 说明 |
|---|---|---|
| GP 4.3.x | gpcopy 1.x | 老版本,配置文件驱动 |
| GP 5.x | gpcopy 1.x | 同上 |
| GP 6.x | gpcopy 2.x | 命令行参数驱动 |
| GP 7.x | gpcopy 2.4+ | 新增部分对象类型支持 |
我这次迁移是GP 6.20到GP 6.23的小版本升级,直接选gpcopy 2.3.2。如果跨大版本,比如GP 5升GP 6,建议先看一下官方Release Note里是否有已知的元数据兼容问题。gpcopy的配置解析方式是跟着版本走的,很多老帖子里的配置文件写法在2.x里已经废了,别拿旧教程硬套。
注意:gpcopy 2.x开始不再读取gpcopy.conf,所有参数都在命令行里传。网上很多教程还在讲配置文件怎么配,那基本是1.x时代的内容,直接跳过。
2.2 依赖环境准备
gpcopy 2.x是Python实现的,依赖一些Python包和Greenplum客户端库。核心依赖有:
- Python 2.7或3.x
- PyGreSQL(访问PostgreSQL协议的驱动)
- psutil(读系统负载信息)
- Greenplum客户端工具(psql等,一般装在Greenplum安装目录里)
安装前先确认环境变量。在源端和目标端的Greenplum管理员用户下,检查GPHOME是否正确指向Greenplum安装目录,执行一下:
echo $GPHOME echo $MASTER_DATA_DIRECTORY如果环境变量是空的,先source一下Greenplum的环境变量文件,不同发行版的路径不一样,常见的是/usr/local/greenplum-db/greenplum_path.sh:
source /usr/local/greenplum-db/greenplum_path.sh2.3 三步完成安装
gpcopy没有复杂的configure和make流程,适合我这类不想折腾编译的人。我是在源端和目标端各装了一份,因为迁移时既要从源端发起,有时候也要在目标端回读校验,两端都装有备无患。
第一步,从官方渠道下载对应版本的tar包。gpcopy是开源项目,找一个能访问到的下载源,把tar包放到目标机器上,比如/home/gpadmin/soft目录。
第二步,解压并放到管理目录里。我习惯统一放到/usr/local/gpcopy下:
mkdir -p /usr/local/gpcopy tar -zxvf gpcopy-2.3.2.tar.gz -C /usr/local/gpcopy ln -s /usr/local/gpcopy/gpcopy-2.3.2 /usr/local/gpcopy/current第三步,把可执行命令路径加入PATH,同时给Python添加包路径:
export PATH=/usr/local/gpcopy/current/bin:$PATH export PYTHONPATH=/usr/local/gpcopy/current/lib/python:$PYTHONPATH为了方便每次登录都生效,可以把上面两行写进~/.bashrc。
安装完成之后验证一下:
gpcopy --version能正常输出版本号,就说明装好了。
如果系统是Python 3,可能还需要单独确认PyGreSQL装到位。我在一台干净机器上遇到过gpcopy报错说找不到pgdb模块,检查了一圈就是PyGreSQL没装。可以用如下命令快速验证:
python -c "import pg; print(pg.__version__)"有输出就说明驱动正常。缺了就补装一下,用pip安装即可。注意以实际环境的Python版本为准选对应驱动版本,否则会有兼容性问题。
3. 核心参数详解与选择逻辑
gpcopy 2.x的参数很多,但真正高频用到、且容易让人迷惑的就那几类。我按使用场景拆开讲,每个参数都标了干什么用的、什么时候必须配。
3.1 迁移范围控制参数
控制迁什么、迁多少,是每个DBA动手前必须想清楚的事。
--full:全库迁移。表、视图、函数、序列、外部表定义全带,加上数据。适合整个集群搬家的场景。--schema-only:只搬元数据,不搬数据。适合需要先建好结构再慢慢灌数的场景。--metadata-only:和schema-only效果接近,但在2.x某些版本中是独立实现,建议用--schema-only更保险。--schema-file:指定一个文件,里面列举要迁移的schema列表,一行一个。适合只迁部分schema的场景。--table-file:指定一个文件,里面列举要迁移的表清单,格式是schema.table,一行一个。适合迁特定业务表的场景。--schema-map:源schema到目标schema的映射关系,支持把一个schema的数据直接灌到另一个schema里,比如源库.业务a:目标库.业务a_归档。这个很实用,业务库重命名时可以少写很多DDL。
选型逻辑很简单:我如果只迁两张业务表,就用--table-file;迁一整个schema,就用--schema-file。不建议全库迁移时用--full之外的方式,因为后续你可能还得单独补视角和函数,绕圈子。
3.2 数据同步细粒度控制
数据怎么搬、遇到已存在的表怎么办,由这一类参数控制。
--truncate:目标表已存在时,先truncate再插入新数据。适合重复迁移同一批表的情况,保证结果干净。--drop:先drop目标表再重建。适合目标表结构跟源端不一致、且不需要保留目标端已有表的情况。--append:目标表已存在,不清空直接追加数据。适合这种场景:持续同步增量数据,或者目标端表里有保留价值的数据。--batch-size:每次从源端读取的数据行数,默认10000。小表不用管,大表建议调低到1000~2000,防止单次读取内存占用过高。--parallel:并发复制表的数量,默认4。这个值直接决定迁移速度,但也直接影响两端系统的负载。我常用4到8,具体看机器核数和IO能力。
参数优先级要注意:同时给了--truncate和--drop,以--drop为准,因为drop之后再重建是更彻底的操作。同时给了--drop和--append,会直接报错,这俩逻辑是互斥的。
3.3 连接与安全性参数
这类参数跟数据库连接有关,配置不对直接连不上,表现形式还很像网络故障。
--source-host:源端Master主机名或IP。--source-port:源端Master端口,默认5432。--source-user:源端数据库用户名,推荐用超级管理员。--dest-host:目标端Master主机名或IP。--dest-port:目标端Master端口。--dest-user:目标端数据库用户名。
两边的库名分别放在--source-db和--dest-db。要记住:gpcopy不会自动建目标库,如果目标库不存在,你得先在目标端手动创建数据库,再执行gpcopy。这个坑我踩过一次,用--dest-db指定的库不存在,命令一直报数据库不存在,还以为是网络问题。
还有几个好用的小参数:
--with-stats:复制完数据后顺便拷贝统计信息。这个对迁移后查询性能很重要,不搬统计信息的话,优化器可能因为没统计信息而选出很烂的执行计划,表现为“迁完之后部分查询反而更慢了”。--analyze:复制完数据后对目标表执行ANALYZE,重新收集统计信息。如果你不确信源端统计信息是最新的,用这个。--validate:迁移完成后对源和目标做行数和校验和比对。对零误差有要求时,这个参数一定要加。
3.4 环境兼容参数
因为源端和目标端版本可能不同,有一些参数是为了兼容性设计的。
--skip-generated-columns:跳过生成列。GP 7新增生成列特性,从GP 6迁移到GP 7时,如果不想生成列出幺蛾子,加上它。--drop其实也能当兼容性手段用:目标端版本不支持某种对象时,drop掉目标端已有定义再重建。
4. 实操全过程:从环境准备到数据校验
4.1 迁移目标场景假设
为了让过程更直观,我虚拟一个场景:某公司有一套Greenplum生产集群(GP 6.20),上面跑着业务核心这个库,里面有三个schema:交易中心、会员中心、临时分析。现在要迁移到一台新硬件集群(GP 6.23),库名不变,需要把前两个schema完整迁移,临时分析不要。表数量127张,最有分量的一张大表大概1.2TB,其余多数是几GB到几十GB的中型表。总数据量约4TB。
目标端已经建好库:
CREATE DATABASE 业务核心;要用超级管理员执行,普通用户建库需要相应权限。
4.2 阶段一:元数据先行
我的习惯是先把结构和对象定义搬过去,再搬数据。好处很明显:数据搬完后不用急着建索引和约束,减少高峰期锁冲突;而且如果元数据定义有兼容性问题,可以提前发现,避免搬了一堆数据后发现建表失败还得回滚。
执行如下命令,只搬元数据:
gpcopy \ --source-host 192.168.1.10 \ --source-port 5432 \ --source-user gpadmin \ --source-db 业务核心 \ --dest-host 192.168.1.20 \ --dest-port 5432 \ --dest-user gpadmin \ --dest-db 业务核心 \ --schema-only \ --schema-file /home/gpadmin/migrate/schema_list.txtschema_list.txt内容:
交易中心 会员中心跑完后,登录目标库确认一下:
\dn能看到目标端已经有了两个schema,且里面的表结构都在。看一眼行数,全是0,正常。
4.3 阶段二:分批并发数据迁移
元数据就位后,开始灌数据。我建议把表清单拆成多份,分批跑,避免一次性并发太多表导致两边IO都打满。大表和小表分开处理,效果更好。
大表用单独一个清单,小表用一个清单。大表跑的时候把--parallel调低,比如2,避免并行线程太多拖垮IO;小表可以用8左右的并行度,速度提上来。
小表清单执行:
gpcopy \ --source-host 192.168.1.10 \ --source-port 5432 \ --source-user gpadmin \ --source-db 业务核心 \ --dest-host 192.168.1.20 \ --dest-port 5432 \ --dest-user gpadmin \ --dest-db 业务核心 \ --table-file /home/gpadmin/migrate/tables_small.txt \ --parallel 8 \ --truncate \ --with-stats \ --analyzetables_small.txt格式:
交易中心.订单日汇总 交易中心.用户积分流水 会员中心.会员标签 ...大表清单执行:
gpcopy \ --source-host 192.168.1.10 \ --source-port 5432 \ --source-user gpadmin \ --source-db 业务核心 \ --dest-host 192.168.1.20 \ --dest-port 5432 \ --dest-user gpadmin \ --dest-db 业务核心 \ --table-file /home/gpadmin/migrate/tables_big.txt \ --parallel 2 \ --truncate \ --with-stats大表因为数据量大,单表复制时间可能几十分钟到几个小时,这时候如果执行中断了,重跑一遍时--truncate会先清空目标表再重新灌,不影响数据正确性。这是加--truncate的主要意义,断点续传做不到,但重跑很干净。
这点跟很多人的预期不一样:gpcopy本身没有原生断点续传功能,数据传到一半断了,目标表里会有部分数据,如果你不带--truncate直接重跑,数据就是坏的。所以只要不是首次灌入,重跑时必须带--truncate或--drop,切记。
4.4 阶段三:统计信息与数据校验
小表组里我加了--with-stats --analyze,目标表统计信息已经更新。大表组数据量大,我在全部跑完后统一对目标库做大表的ANALYZE,避免在数据搬完前就分析,浪费IO:
ANALYZE 交易中心.交易流水大表; ANALYZE 交易中心.历史订单表;如果表非常多,可以写个脚本批量ANALYZE。
数据校验我做双保险。第一重是gpcopy自带的行数校验,适合快速确认有没有整表漏掉。第二重用SQL抽查关键表:
-- 源端执行 SELECT count(*), sum(校验金额字段) FROM 交易中心.交易流水大表; -- 目标端执行 SELECT count(*), sum(校验金额字段) FROM 交易中心.交易流水大表;两边结果一致,迁移才算闭环。
4.5 迁移结果的业务侧验证
数据层校验过了还不够,我习惯让业务侧跑一遍冒烟用例。举例:登录核心会员查询接口、翻页查最近订单、跑一遍日常汇总报表SQL。这步看起来麻烦,但能发现统计信息不完整导致的慢查询、分布键不一致导致的查询倾斜、缺失函数导致的存储过程报错等gpcopy自身校验不覆盖的问题。
5. 常见报错与排查技巧
5.1 连接类问题
最经典的错误:could not connect to server。第一反应不是查网络,而是确认三件事。
第一,源端和目标端的pg_hba.conf是否允许gpcopy所在机器的IP访问。Greenplum集群安装时默认只允许本机或特定网段访问,跨机器跑gpcopy前必须手动放行。第二,两端Master端口是否被防火墙拦截。第三,密码认证方式是否正确。如果源端用gpadmin账户做了SSH免密,数据库层面未必有免密,要在命令里通过--source-user和--dest-user配合环境变量方式提供密码(PGPASSWORD),或者确保pg_hba允许trust。
还有一类是连接没断但握手超时,一般出现在大表迁移时读空闲连接被中间防火墙杀掉。可以把--batch-size调小,减少单次会话的驻留时间;或者干脆在两端Master和Segment节点上把TCP keepalive相关参数调一下。
5.2 权限与对象存在性错误
报Insufficient privilege之类的问题,90%是用户权限不足。gpcopy迁移过程中要读取数据、建表、建视图、跑ANALYZE,要求的权限不比应用迁移脚本低。我的建议是:源端用一个有对应库读权限的角色,目标端用一个具备建表和DDL权限的角色,如果想省事而且环境允许,直接用超级管理员。
报relation already exists,说明目标端已经有同名对象,且你没加--drop或--truncate。按业务需要决定:保留目标端已有数据就加--append;用源端数据覆盖就加--truncate;结构也要覆盖就加--drop。
报function does not exist,通常是目标端缺失某些自定义函数或扩展。gpcopy按顺序迁移对象,有些对象依赖前置对象,如果前置对象在目标端不存在,后置的就会失败。解决办法是优先用--schema-only把依赖对象搬一遍,再单独搬数据,能让这种失败明显减少。
5.3 数据一致性与倾斜问题
用了--validate之后如果报校验不一致,先别急着重跑。我遇到过一次,原因不是数据没搬全,而是目标端有触发器,在数据写入时改了行内容。这类情况下,先把目标端相关触发器临时禁用,再重跑数据迁移。
还有一类是迁移后某些表在Segment间数据分布严重不均匀,用select gp_segment_id, count(*) from 表 group by 1能看出来。多数情况是因为源端和目标端的分布键定义不一致,gpcopy按源端分布键路由到目标端,如果目标端的分布键不是同一组列,数据会全部落在一个Segment上。解决办法:迁移前核对建表语句,确认两边的DISTRIBUTED BY完全一致。
5.4 性能问题定位
迁移速度上不去,优先查两端Segment磁盘IO是不是打满了。如果Master节点的CPU很高而Segment很闲,可能是源端查询计划有问题,试试给源端大表提前收集统计信息。如果目标端Segment IO很高而源端Segment很闲,说明目标端写入或压缩开销大,可以考虑关闭目标端表的压缩或用更低压缩级别。
另一个影响性能的点是网络。gpcopy数据流是Segment间直接传,跨机房带宽不够时,迁移会跟业务抢带宽。建议在业务低峰期执行,或通过tc之类手段限制迁移流量,别让一个迁移任务把整条链路打瘫。
6. 避开我踩过的那些坑
文档上不会写但实际影响最大的几个细节,单独拎出来说。
6.1 源端负载和并行度设置
--parallel不是越大越好。我最初贪快设成16,结果源端Master负载直接飙到几十,业务侧开始出现慢查询报警。后来改成先跑一轮4并行度试水,观察两端负载再加,最终稳定在8。如果是白天迁移、还有业务在跑,并行度建议不超过4。
大表的单表搬迁没法用并行度调快,因为一张表的数据读取和写入走的是同一个流程。想加速只能从网络和磁盘层面想办法。
6.2 字符串编码和排序规则
Greenplum集群的编码和collation在迁移时必须保持一致。如果源库是UTF8,目标库建库时写成了GBK或者默认值,数据进去后中文会乱码,而且这种乱码很隐晦,数字字段不受影响,光看行数校验发现不了。目标库在建库阶段就得把encoding、locale这些刻好,别指望迁移后再改。
6.3 大对象和外部表检查
gpcopy对外部表只迁移定义,不迁移外部数据文件,因为外部表的数据本来就不在数据库里。迁移前梳理一下哪些是外部表,它们的文件路径在新环境是否仍然有效,如果文件系统路径变了,需要手工改外部表定义。
大对象(BLOB/CLOB类型)在大表和导出场景下容易出问题,建议迁移前先确认源端是否有大对象存储,如果有,提前规划专门的大对象迁移步骤,别依赖gpcopy的默认行为。
6.4 序列值的推进
gpcopy会迁移序列的当前值,但有个前提:序列必须已经被使用并推进过。如果序列在迁移时还是初始状态,迁移后目标端序列可能没有正确同步最新值,导致新插入数据主键冲突。迁完后挨个做主键序列表的setval检查,是个好习惯。
判断方法:在源端执行select * from 序列名;看last_value,目标端同样查询。不一致就手工setval到源端的值加上一个余量。
7. 一个可复用的快速checklist
最后把我每次迁移前必走的检查项整理出来,照着做能省很多事:
| 项目 | 检查内容 | 命令或说明 |
|---|---|---|
| 版本匹配 | gpcopy版本与GP版本对应 | gpcopy --version |
| 环境变量 | GPHOME和MASTER_DATA_DIRECTORY正确 | echo $GPHOME |
| 网络连通 | 两端Master 5432端口可达 | psql -h 目标IP -p 5432 -U gpadmin -d postgres -c "select 1" |
| 权限 | 两端用户具备迁移所需权限 | 超级管理员最省事 |
| 目标库 | 目标库已创建 | CREATE DATABASE ... |
| Schema | 目标schema已创建 | 可让gpcopy自动建,也可SQL建 |
| 分布键 | 源和目标分布键一致 | 对比\d+ 表名的分布键信息 |
| 表清单 | 明确要迁哪些表 | 生成table-file时要核对 |
| 存储空间 | 目标端有足够容量 | df -h看数据目录 |
| 统计信息 | 源端大表统计信息较新 | 必要时先ANALYZE源表 |
| 触发器 | 目标端业务触发器已确认 | 避免写入时被篡改数据 |
按这个清单走一遍,再执行gpcopy,基本能做到一次成功。
最后分享一个我用着最顺手的组合:日常迁移都用--truncate --with-stats --analyze,数据量小、业务催得急就用--parallel 8,大表集中迁移就用--parallel 2加--batch-size 2000。这套组合帮我扛过了好几次集群迁移和故障恢复演练,后续我还会用它做灾备环境的周期刷新,只需要把--table-file换成定时生成的清单即可,算是把gpcopy用出了持续同步的感觉。