在线分区功能(rds_online_migrate)

RDS PostgreSQLrds_online_migrate插件提供了在线分区功能,可以将普通表转换为分区表。

背景

随着业务发展,数据量会逐渐增多,在特定场景下,可能需要将普通表转换为分区表,但是原生PostgreSQL数据库并没有提供将普通表转换为分区表的在线分区功能。为了简化运维操作,降低分区行为对业务的影响,RDS PostgreSQL提供了在线分区功能。

适用范围

RDS PostgreSQL实例版本需要满足以下条件:

  • 实例大版本:13或以上版本。

  • 内核小版本:大于等于20251130。

注意事项

在执行在线分区操作前,请详细阅读并确认以下事项,以确保数据安全和操作成功。

  • 前置准备

    • 备份与测试:执行任何操作前,务必确认已有有效的全量备份集,并在非生产环境中完成充分的演练与验证。

    • 存储空间规划:在线分区过程中,系统会创建一张新表用于数据迁移。因此,需要预留至少为原表及其所有索引总大小两倍以上的存储空间。

    • 数据库参数配置:此功能依赖逻辑解码(Logical Decoding)实现,需将wal_level参数设置为logical。此外,若需并行执行多个分区任务,可能需要相应调高max_worker_processes参数的值。

  • 表结构与权限要求

    • 主键或唯一标识:由于功能内部基于PostgreSQL的原生逻辑复制,源表必须拥有主键(PRIMARY KEY)或已设置REPLICA IDENTITY

    • 用户权限:执行操作的用户账号必须具备以下权限:创建publicationsubscriptionreplication slot的权限,以及对源表和目标表执行RENAME操作的权限。

    • 权限一致性:为确保业务连续性,迁移前请授予业务账号对目标分区表的相应操作权限,确保其权限与源表一致。

  • 异常处理与风险

    • 非原子性操作:此功能为脚本化流程,不具备原子性。若执行过程中发生异常中断,可能导致内部对象残留。

    • 手动清理:发生异常中断后,可能需要手动清理报错日志中残留的rds_online_migrate.internal_map表记录、publicationsubscriptionreplication slot对象。请参考以下命令进行清理:

      -- 清理元数据记录
      DELETE FROM rds_online_migrate.internal_map WHERE src_relname = 'source_table' AND dst_relname = 'destination_table';
      
      -- 删除残留的发布
      DROP PUBLICATION publication_name;
      
      -- 删除残留的订阅
      DROP SUBSCRIPTION subscription_name;
      
      -- 删除残留的复制槽
      SELECT pg_drop_replication_slot('slot_name');
  • RENAME 操作的潜在影响

    • 关联对象重建RENAME操作完成后,依赖于原表的视图(View)、触发器(Trigger)和外键约束需要手动重建。

    • 逻辑复制影响:若源表已加入其他逻辑复制任务,RENAME操作可能会中断该复制链路。操作完成后,需将新表(原目标表)重新添加到相应的publication中。

  • 功能适用场景扩展

    • 该功能同样支持对已有分区表进行在线重分区。

使用示例

以下示例将演示如何将一个名为 public.test 的普通表在线转换为分区表。

1. 配置数据库参数

rds_online_migrate功能依赖逻辑解码(Logical Decoding)。在RDS控制台中将wal_level参数设置为logical

2. 准备源表和数据

创建一个普通表public.test作为转换的源表,并插入一百万行测试数据,以模拟业务真实场景。

-- 创建源表
CREATE TABLE public.test(id int4 PRIMARY KEY, info text);

-- 插入模拟数据
INSERT INTO public.test SELECT x, repeat(x::text, 2) FROM generate_series(1, 1000000) AS x;

3. 安装插件

在需要执行操作的数据库中创建rds_online_migrate插件。

CREATE EXTENSION rds_online_migrate;

4. 创建目标分区表

创建一个与源表结构相同的目标分区表public.test_p。该表必须与源表位于同一Schema下。本示例创建一个按ID哈希分区的表,包含四个子分区。

CREATE TABLE public.test_p (LIKE test INCLUDING ALL) PARTITION BY HASH(id);
CREATE TABLE public.test_p_0 PARTITION OF public.test_p FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE public.test_p_1 PARTITION OF public.test_p FOR VALUES WITH (MODULUS 4, REMAINDER 1);
CREATE TABLE public.test_p_2 PARTITION OF public.test_p FOR VALUES WITH (MODULUS 4, REMAINDER 2);
CREATE TABLE public.test_p_3 PARTITION OF public.test_p FOR VALUES WITH (MODULUS 4, REMAINDER 3);

5. 执行在线转换

调用rds_online_migrate.rewrite_table函数启动在线分区流程。函数的第一个参数是源表名称,第二个参数是目标分区表名称。函数返回t表示执行成功。

SELECT rds_online_migrate.rewrite_table('public.test', 'public.test_p');
-- 预期输出:
 rewrite_table
---------------
 t
(1 row)

6. 验证转换结果

操作成功后,函数会自动完成表名切换,以保证业务的无缝过渡:

  • 原表public.test被重命名为public.test_rds_bkp,作为数据备份,供后续验证,可自行删除。

  • 目标分区表public.test_p被重命名为public.test,作为新表使用。

接下来,通过以下步骤进行验证:

  • 检查表结构 使用\d命令查看关系列表,确认表名已按预期切换,并且新的test表类型为 partitioned table

    testdb=> \d
    -- 预期输出:
                       List of relations
     Schema |     Name     |       Type        |   Owner
    --------+--------------+-------------------+-----------
     public | test         | partitioned table | rds_super
     public | test_p_0     | table             | rds_super
     public | test_p_1     | table             | rds_super
     public | test_p_2     | table             | rds_super
     public | test_p_3     | table             | rds_super
     public | test_rds_bkp | table             | rds_super
    (6 rows)
  • 验证数据完整性 分别查询新表、备份表和各分区的数据行数,确保数据迁移完整且分布正确。

    1. 查询备份表总行数,应与迁移前一致。

      SELECT count(*) FROM public.test_rds_bkp;
      -- 预期输出:1000000
    2. 查询新分区表总行数,应与备份表一致。

      SELECT count(*) FROM public.test;
      -- 预期输出:1000000
    3. 查询各分区行数,验证数据已按哈希规则分布到各子表。

      SELECT count(*) FROM public.test_p_0;
      -- 预期输出:249589
      SELECT count(*) FROM public.test_p_1;
      -- 预期输出:250376
      SELECT count(*) FROM public.test_p_2;
      -- 预期输出:249786
      SELECT count(*) FROM public.test_p_3;
      -- 预期输出:250249