pgcopydb: 功能亮点

三月 29, 2024

摘要:本部分文档向您介绍pgcopydb程序中的主要功能。

本文包含以下部分:

  1. 绕过表数据的中间文件
  2. 大对象支持
  3. 并发
  4. 数据变更捕获
  5. 内部目录(SQLite)

之所以开发pgcopydb的原因,主要是想实现两个方面,这两方面是不可能直接用pg_dumppg_restore实现的,这需要足够的工作,并没有多少现成的脚本可用于自动化。

绕过表数据的中间文件

第一个方面是,为了实现pg_dumppg_restore的并发,它们需要首先写入中间文件。

pg_dump 文档--jobs参数提了以下一点:

来自 PostgreSQL 文档

此选项只能用于目录输出格式,因为这是多个进程可以同时写入其数据的唯一输出格式。

pg_restore 文档--jobs参数提了以下一点:

来自 PostgreSQL 文档

此选项仅支持自定义和目录归档格式。输入必须是一个常规文件或目录(而不是,比如,一个管道或标准输入)。

因此,pgcopydb的第一个想法是要提供--jobs支持并发处理,并完全绕过中间文件(和目录),至少就实际的表数据集而言。

实现此目的的诀窍是,pgcopydb必须能够在整个操作期间连接到源数据库,而pg_restore可以使用导出到磁盘上的数据,而不必仍然要连接源数据库。在pgcopydb的环境中,是要可以访问源数据库的。在pg_restore的环境中,这是不可接受的。

大对象支持

PostgreSQL 大对象 API 少有人喜欢用,尽管许多应用程序开发人员发现,它实现的权衡非常有用。在转储和还原的业务环境中,Postgres 将大对象元数据与大对象内容分开了。

具体而言,元数据由大对象 OID 和访问控制列表组成,作为 PostgreSQL 转储的数据前面部分的一部分。

这意味着 pgcopydb 依赖于pg_dump将大对象元数据从源端导入到目标 PostgreSQL 服务器,但随后实现了自己的代码,来迁移大对象内容,并会根据命令行选项--large-objects-jobs的设置,使用多个工作进程。

并发

pgcopydb 的一个主要特性是如何实现并发的,并提供选项以获得同表 COPY 的并发。有关并发能力的更多信息,请参阅文档中“关于并发的说明”一章。

数据变更捕获

pgcopydb 基于底层的 PostgreSQL 逻辑解码 API,实现了完整的 PostgreSQL 复制解决方案。这允许 pgcopydb 与旧版本的 PostgreSQL 兼容,从版本 9.4 开始。

总要先进行一次不带--follow选项的迁移测试,以了解在您业务场景下所需的停机时长。这可以给您对是否使用数据更改捕获模式的决定提供参考信息,该模式会让迁移过程变得更加复杂。

PostgreSQL 逻辑解码客户端

pgcopydb 的复制客户端被设计为,能够在从源端 PostgreSQL 实例获取数据的初始副本时,同时获取数据的变更。它会创建三个工作进程,来实现逻辑解码客户端的处理:

  • 流处理进程使用 Postgres 复制协议从 Postgres 复制槽中获取数据。
  • 转换进程将获取的数据从中间的 JSON 格式转换为一种 SQL 派生语言。在预取模式下,这是以批处理操作来实现的,在重放模式下,这是以数据流方式完成的,一次一行,从一个 Unix 管道读取。
  • 然后,应用进程将 SQL 脚本应用于目标 PostgreSQL 数据库系统,并使用 PostgreSQL API 进行复制进度跟踪

在操作过程的初始副本阶段,pgcopydb follow 在预取模式下运行,并且还未应用更改。初始副本完成后,pgcopy 复制系统会进入一个循环,在以下两种操作模式之间切换:

  1. 预取模式下,更改会存储到磁盘上的 JSON 文件,转换进程在发生切换时对文件进行操作,应用进程通过一次应用一个文件来追赶磁盘上的更改。

    当下一个要应用的文件还不存在时,3 个转换工作进程会停止,然后主进程跟随主管进程切换到重放模式

  2. 重放模式下,使用 Unix 管道机制将更改从流处理工作进程流式传输到转换工作进程,并使用另一个 Unix 管道将获取的 SQL 语句发送到重放工作进程。

    然后,更改以端到端的数据流方式重放,并采用事务粒度进行。

内部采用类似 SQL 脚本的格式

Postgres 逻辑解码 API 不提供 CDC 格式,而是允许 Postgres 扩展的开发者实现逻辑解码输出插件。Postgres 核心发行版实现了这样一个名为 test_decoding 的输出插件。另一个常用的输出插件名为 wal2json

pgcopydb 与test_decodingwal2json两者都兼容,并且作为用户,可以使用--plugin命令行选项选择输出插件。

输出插件的兼容性意味着,pgcopydb 必须实现代码来解析输出插件语法并处理它。在内部,来自输出插件的消息由 pgcopydb 存储在一个 JSON 行格式文件中,其中每行都是一个 JSON 记录,按原样记录了有关更改和输出插件消息的解码元数据。

该 JSON 行格式会转换为 SQL 脚本。起初,pgcopydb 只会使用 SQL 作为中间格式,但后来作为优化,添加了对预备语句的支持。这意味着我们的 SQL 脚本会使用如下面示例中的命令:

PREPARE d33a643f AS INSERT INTO public.rental ("rental_id", "rental_date", "inventory_id", "customer_id", "return_date", "staff_id", "last_update") overriding system value VALUES ($1, $2, $3, $4, $5, $6, $7), ($8, $9, $10, $11, $12, $13, $14);
EXECUTE d33a643f["16050","2022-06-01 00:00:00+00","371","291",null,"1","2022-06-01 00:00:00+00","16051","2022-06-01 00:00:00+00","373","293",null,"2","2022-06-01 00:00:00+00"];

正如您在示例中看到的,pgcopydb 现在能够使用带有多个 VALUES 的单个 INSERT 语句,这是一个巨大的性能提升。pgcopydb 为了简化对 SQL 语法的解析,选择了将 EXECUTE 参数列表格式化为一个 JSON 数组,这并不符合实际的 SQL 语法,但处理起来简单快捷。

最后,转换进程无法预测应用进程的实际会话管理,因此 SQL 语句始终包含在 PREPARE 和 EXECUTE 步骤中。当然,pgcopydb 应用代码知道如何跳过重复的预备过程。

不幸的是,这意味着我们的 SQL 文件实际上并没有使用 SQL 语法,因而不能使用任何 SQL 客户端软件来直接处理。目前,需要使用 pgcopydb stream apply,或者编写自己的处理代码。

内部目录(SQLite)

为了能够实现 pgcopydb 操作,内部需要一个 SQL 对象列表,包括如表、索引、约束和序列。虽然 pgcopydb 过去常常以内存中的数组来处理这样的列表,并且还有一个用于直接查找的哈希表(按 oid 和还原列表的名称),但在某些情况下,源端数据库包含的对象太多,以至于这些数组不适合用内存来处理。

由于 pgcopydb 是用 C 语言编写的,因此要能够将数组溢出到磁盘并支持直接查找,当前处理对象数组的最佳方法实际上是 SQLite 库,它是一种采用文件格式的嵌入式数据库引擎。

这就是为什么当前版本的 pgcopydb 要使用 SQLite 来处理其对象目录的原因。

在内部,pgcopydb 将元数据信息存储在三个不同的目录中,默认情况下,所有目录都位于${TMPDIR}/pgcopydb/schema/ 目录中,除非使用了推荐的--dir选项。

  • 源端目录登记了有关源端数据库的元数据,以及 pgcopydb 上下文、一致性和进度相关的一些元数据。

  • 筛选器目录仅在使用--filters选项才使用,它登记了有关源端数据库中将要跳过的对象的元数据。

    这是必需的,因为筛选是使用pg_restore --listpg_restore --use-list选项实现的。Postgres 归档目录的格式包含了对象 OID 及其还原列表的名称,pgcopydb 需要能够在其筛选器目录中查找 OID 或名称。

  • 目标目录登记了有关目标数据库的元数据,例如角色列表、模式列表,或者在目标数据库上找到的现有约束列表。

了解更多

pgcopydb: 复制 PostgreSQL 数据库到目标服务器