使用 pg_partman 自动管理 PostgreSQL 数据库的分区表

[ 博客首页 ]
黄杰, 2022-02-27
root[a]linuxsand.info

前两篇文章((一)(二))写了手动管理 PostgreSQL 的分区表。

本文记录了使用 pg_partman 这个 PostgreSQL 拓展(extension)进行分区的创建和自动维护。 pg_partman 支持继承式分区表和声明式(原生)分区表,本文使用原生分区表。

1 构建 pg_partman

对于 Windows 用户,需要自己克隆代码(git clone https://github.com/pgpartman/pg_partman/)并构建(make install)出 pg_partman。

构建后,预期得到的文件有:

但是阅读 Makefile 可以知道,在 Windows 下没法直接、方便地进行构建。

通过搜索 github issues,找到了一个应变方法; 我修改了这个 make.sh 文件的 Shebang 、添加了注释如下。

#! /usr/bin/bash
# huangjie comments: execute in git bash
# then copy to C:\Program Files\PostgreSQL\14\share\extension
# then in psql, execute `create extension pg_partman`
# finally, check if it displayed in extensions list
# end huangjie comments
EXTENSION=pg_partman
VERSION=$(grep default_version $EXTENSION.control | \
          sed -e "s/default_version[[:space:]]*=[[:space:]]*'\([^']*\)'/\1/")

cat sql/types/*.sql > "${EXTENSION}--${VERSION}.sql"
cat sql/tables/*.sql >> "${EXTENSION}--${VERSION}.sql"
cat sql/functions/*.sql >> "${EXTENSION}--${VERSION}.sql"

直接在 git bash 内执行 ./make.sh,得到了 pg_partman.controlpg_partman--4.6.0.sql 文件;

2 安装 pg_partman

把上一步得到的 pg_partman.controlpg_partman--4.6.0.sql 拷贝到 C:\Program Files\PostgreSQL\14\share\extension。然后在 psql 内执行如下代码安装该拓展:

-- 切到 postgres 这个 db 下(以下所有示例,均在 postgres 这个数据库下演示)
\c postgres;
CREATE SCHEMA IF NOT EXISTS partman;
create extension IF NOT EXISTS pg_partman SCHEMA partman;

验证如下。

postgres=# select * from pg_available_extensions where name='pg_partman';
    name    | default_version | installed_version |                       comment
------------+-----------------+-------------------+------------------------------------------------------
 pg_partman | 4.6.0           | 4.6.0             | Extension to manage partitioned tables by time or ID
(1 row)

3 使用 pg_partman

3.1 创建分区表

我们直接使用 pg_partman.create_parent 按月来创建分区表:

create table dbo.test3
(
    "Id" bigserial,
    -- ...other columns
    "DetectedTime" timestamp without time zone
) PARTITION BY Range("DetectedTime");

-- 略过创建索引
-- create index on dbo.test3 ("DetectedTime");
-- create index on dbo.test3 ("Id");

-- 'native' 即原生分区表(声明式分区表)
select partman.create_parent('dbo.test3', 'DetectedTime', 'native', 'monthly');

查看 create_parent 创建的子表,我们可以看到一共创建了 10 张表(p_premake参数默认为 4 时,1 张当月表,4 张过去的月份表,4 张未来的月份表,1 张默认表):

postgres=# \d+ dbo.test3;
                                     Partitioned table "dbo.test3"
    Column    |            Type             | Collation | Nullable |                 Default                 |
--------------+-----------------------------+-----------+----------+-----------------------------------------+
 Id           | bigint                      |           | not null | nextval('dbo."test3_Id_seq"'::regclass) |
 DetectedTime | timestamp without time zone |           |          |                                         |
Partition key: RANGE ("DetectedTime")
Partitions: dbo.test3_p2021_10 FOR VALUES FROM ('2021-10-01 00:00:00') TO ('2021-11-01 00:00:00'),
            dbo.test3_p2021_11 FOR VALUES FROM ('2021-11-01 00:00:00') TO ('2021-12-01 00:00:00'),
            dbo.test3_p2021_12 FOR VALUES FROM ('2021-12-01 00:00:00') TO ('2022-01-01 00:00:00'),
            dbo.test3_p2022_01 FOR VALUES FROM ('2022-01-01 00:00:00') TO ('2022-02-01 00:00:00'),
            dbo.test3_p2022_02 FOR VALUES FROM ('2022-02-01 00:00:00') TO ('2022-03-01 00:00:00'),
            dbo.test3_p2022_03 FOR VALUES FROM ('2022-03-01 00:00:00') TO ('2022-04-01 00:00:00'),
            dbo.test3_p2022_04 FOR VALUES FROM ('2022-04-01 00:00:00') TO ('2022-05-01 00:00:00'),
            dbo.test3_p2022_05 FOR VALUES FROM ('2022-05-01 00:00:00') TO ('2022-06-01 00:00:00'),
            dbo.test3_p2022_06 FOR VALUES FROM ('2022-06-01 00:00:00') TO ('2022-07-01 00:00:00'),
            dbo.test3_default DEFAULT

就这么简单。

贴一下 create_parent 的完整签名备忘。

create_parent(p_parent_table text, p_control text, p_type text, p_interval text,
              p_constraint_cols text[] DEFAULT NULL, p_premake int DEFAULT 4,
              p_automatic_maintenance text DEFAULT 'on', p_start_partition text DEFAULT NULL,
              p_inherit_fk boolean DEFAULT true, p_epoch text DEFAULT 'none',
              p_upsert text DEFAULT '', p_publications text[] DEFAULT NULL,
              p_trigger_return_null boolean DEFAULT true, p_template_table text DEFAULT NULL,
              p_jobmon boolean DEFAULT true, p_date_trunc_interval text DEFAULT NULL)
      RETURNS boolean

3.2 手动维护分区表

使用 run_maintenancerun_maintenance_proc 进行分区维护,两者的签名如下。

run_maintenance(p_parent_table text DEFAULT NULL, p_analyze boolean DEFAULT NULL,
                p_jobmon boolean DEFAULT true)
    RETURNS void

run_maintenance_proc(p_wait int DEFAULT 0, p_analyze boolean DEFAULT NULL,
                p_jobmon boolean DEFAULT true)

可以手动针对 dbo.test3 进行维护:

select partman.run_maintenance('dbo.test3', false);

3.3 自动维护分区表(Windows 计划任务)

上面是我们在某个数据库下,手动指定某一张表,因此传入了表名作为参数;自动维护时,我们可以调用使用默认参数(NULL, NULL, true)的例程 select partman.run_maintenance(); 就能自动维护 part_config 内的 automatic_maintenance 为真的表。

postgres=# select automatic_maintenance from partman.part_config where parent_table='dbo.test3';
 automatic_maintenance
-----------------------
 on
(1 row)

我们只需编写一个可执行程序,在连接字符串内拼接好指定的数据库名字(数据库名字可以从配置文件读取),调用维护例程即可;主体源码大概如下。

string connString = "Server=127.0.0.1;Port=5432;User Id=postgres;Password=XXX;Database=";
foreach (string dbname in new string[] { "db1", "db2", "db3" })
{
    using (var conn = new NpgsqlConnection(connString + dbname))
    {
        try
        {
            conn.Open();
            conn.Execute("select partman.run_maintenance();");
            conn.Close();
            logger.LogInfo($"maintain {s}: success");
        }
        catch (Exception e)
        {
            logger.LogError($"maintain {s}: failed -- {e}");
        }
    } // end using
}  // end foreach

最后利用 schtasks /create /? 添加到 Windows 计划任务内。

3.4 丢弃分区表

维护分区,不但包含新建分区,也包含丢弃分区。

如何丢弃分区?retention 为 NULL,默认是不丢弃的。

postgres=# select retention, retention_keep_table from partman.part_config where parent_table='dbo.test3';
 retention | retention_keep_table
-----------+----------------------
           | t
(1 row)

对于本文中基于时间(DetectedTime 栏位)的分区方式,将 retention 设置为某个 interval 即可。而 retention_keep_table 为真表示只 detach 或 no inherit 表,并不删除数据。

postgres=# update partman.part_config set retention=interval '1 year' where parent_table='dbo.test3';
UPDATE 1

postgres=# select retention, retention_keep_table from partman.part_config where parent_table='dbo.test3';
 retention | retention_keep_table
-----------+----------------------
 1 year    | t
(1 row)

4 小结

本文介绍了 pg_partman 这个工具的构建、安装和使用。

5 扩展阅读