pg_partman Documentation

repository·development·Indexed 25 days ago

https://github.com/pgpartman/pg_partman

A PostgreSQL extension that automates the creation and management of time-based and number-based table partition sets. It extends native declarative partitioning to handle automated maintenance, such as adding new partitions and dropping old ones based on retention policies. Includes documentation on installation, Background Worker (BGW) configuration, Row Level Security (RLS) for multi-tenant usage, and CLI utilities like dump_partition.py and vacuum_maintenance.py.

Tokens
26.7K
Snippets
46
Records
96
Agent score
34%

What's inside pg_partman

  1. Overview of pg_partman features and requirements

    development

    Overview

    pg_partman is a PostgreSQL extension designed to simplify managing time-based or number/ID-based table partitioning.

    Key Requirements & Compatibility

    • PostgreSQL Version: Minimum version 14 is required (as of version 5.0.1).
    • Partitioning Method: Uses built-in declarative partitioning. Trigger-based partitioning is no longer supported.
    • Partitioning Types:
      • Ranged Partitioning: Supported for time- and number-based intervals.
      • List Partitioning: Supported for number-based partitioning when the interval is 1.
      • Numeric/Decimal Support: Experimental support for numeric/decimal values in number-based partitioning is available (as of version 5.1), but the interval must remain an integer.

    Data Handling & Default Partitions

    • A default partition is automatically created for all partition sets to catch data falling outside existing child boundaries.
    • Use check_default() to monitor data in the default table.
    • Use the partition_data_* family of functions to move data from the default table into valid child partitions.
    • Warning: Future child table creation is based on current partition set data and ignores data in the default table by default. It is recommended to set a high premake value to ensure the expected data range is covered.
  2. Set up time-based partitioning with custom Text identifiers

    development

    If your partition key is a TEXT column containing timestamp information (e.g., an identifier like INV20240815), you must define custom encoder and decoder functions.

    1. Create an encoder function that takes a timestamptz and returns the formatted text identifier.
    2. Create a decoder function that takes the text identifier and returns a timestamptz.
    3. Call partman.create_partition passing these function names to p_time_encoder and p_time_decoder.
    -- 1. Define Encoder
    CREATE FUNCTION public.encode_timestamp(p_timestamp timestamptz, OUT encoded text)
        RETURNS text
        LANGUAGE plpgsql STABLE
        AS $$
    BEGIN
        SELECT concat('INV', to_char(p_timestamp, 'YYYYMMDD')) INTO encoded;
    END
    $$;
    
    -- 2. Define Decoder
    CREATE FUNCTION public.decode_timestamp(p_str text, OUT ts timestamptz)
        RETURNS TIMESTAMPTZ
        LANGUAGE plpgsql STABLE
        AS $$
    BEGIN
        SELECT substr(p_str, 4) INTO ts;
    END
    $$;
    
    -- 3. Initialize Partitioning
    SELECT partman.create_partition(
        p_parent_table := 'partman_test.time_taptest_table'
        , p_control := 'col3'
        , p_interval := '1 day'
        , p_time_encoder := 'public.encode_timestamp'
        , p_time_decoder := 'public.decode_timestamp'
    );
  3. Configure and use the pg_partman Background Worker (BGW)

    development

    The pg_partman BGW acts as a scheduler that runs run_maintenance_proc() automatically.

    Setup Requirements:

    1. Add pg_partman_bgw to shared_preload_libraries in postgresql.conf (requires restart).
    2. The BGW only maintains partition sets where automatic_maintenance is set to true in part_config.

    Configuration Options (postgresql.conf):

    • pg_partman_bgw.dbname: (Required) Comma-separated list of databases to run maintenance on.
    • pg_partman_bgw.role: (Required) The role to run maintenance as. Highly recommended to use a non-superuser role.
    • pg_partman_bgw.interval: Seconds between calls (Default: 3600).
    • pg_partman_bgw.analyze: Set to 'on' to enable p_analyze in run_maintenance() (Default: 'off').
    • pg_partman_bgw.jobmon: Set to 'on' to enable p_jobmon in run_maintenance() (Default: 'on').
  4. Configure a template table for non-partitioning keys

    development

    To ensure that child partitions have specific indexes or primary keys that do not include the partition key, you must pre-create a template table and pass it to partman.create_partition().

    If you do not pre-create a template table, pg_partman will create one automatically in the extension's schema. However, if you add indexes to that auto-generated template after calling create_partition(), existing child tables will not have those indexes applied.

  5. Use subpartitioning for data organization

    development

    Subpartitioning (e.g., time->time, id->id, time->id, or id->time) is supported but offers little performance benefit unless managing petabyte-scale data. Its primary use is for data organization and retention.

    Operational Considerations:

    • Locking: Large partition sets may require increasing max_locks_per_transaction in postgresql.conf to avoid shared memory issues.
    • Maintenance Contention: If run_maintenance() causes contention, set automatic_maintenance to false in the part_config table for that specific set. You must then call run_maintenance(parent_table) or the run_maintenance_proc() procedure (which commits after each set to reduce contention) manually.
    • Replication: Logical replication (PUBLICATION/SUBSCRIPTION) is NOT supported with subpartitioning.
  6. Partition an existing table using the Offline Partitioning method

    development

    Offline partitioning involves moving data from an existing non-partitioned table to a new partitioned table. This method is recommended when you have foreign keys pointing TO the table being partitioned, as it requires recreating the foreign key relationship.

    Steps:

    1. Rename the original table: Rename your current table so the new partitioned table can take its original name.
    2. Create the new partitioned parent: Create a new table using PARTITION BY and ensure it has the same properties (privileges, constraints, defaults, indexes) as the original.
    3. Initialize partitions: Use partman.create_partition() to set up the initial partition set.
    4. Migrate data: Use partman.partition_data_proc() to move data from the old table to the new one in controlled batches.
    5. Cleanup: Run VACUUM ANALYZE on the new parent table and the old source table after migration.
    -- 1. Rename original table
    ALTER TABLE public.original_table RENAME to old_nonpartitioned_table;
    
    -- 2. Create new partitioned parent
    CREATE TABLE public.original_table (
        col1 bigint not null
        , col2 text not null
        , col3 timestamptz DEFAULT now()
        , col4 text
    ) PARTITION BY RANGE (col1);
    
    CREATE INDEX ON public.original_table (col1);
    
    -- 3. Initialize partitions
    SELECT partman.create_partition(
        p_parent_table := 'public.original_table'
        , p_control := 'col1'
        , p_interval := '10000'
    );
    
    -- 4. Migrate data in batches
    CALL partman.partition_data_proc(
        p_parent_table := 'public.original_table'
        , p_loop_count := 200
        , p_interval := '1000'
        , p_source_table := 'public.old_nonpartitioned_table'
    );
    
    -- 5. Cleanup
    VACUUM ANALYZE public.original_table;
  7. Automate partition maintenance with run_maintenance() and run_maintenance_proc()

    development

    To automate the creation of child tables and the execution of retention policies, you must run maintenance tasks regularly. You can use a scheduled job (like cron) or the PostgreSQL Background Worker (BGW).

    Options for running maintenance:

    • run_maintenance() (Function): Runs maintenance for all partition sets where automatic_maintenance is set to true in part_config.
      • Use p_parent_table to run maintenance for a specific table only (overriding automatic_maintenance settings).
      • Use p_analyze (boolean) to run an ANALYZE on the parent table if new child tables were created. This is necessary for effective constraint exclusion/partition pruning.
      • Use p_jobmon (boolean) to control logging via the pg_jobmon extension.
    • run_maintenance_proc() (Procedure): Similar to the function, but it commits after each partition set's maintenance is finished. This is highly recommended for large numbers of partition sets to reduce transaction contention.
      • Use p_wait (int) to specify how many seconds to wait between each partition set's maintenance run.
  8. Batch partition large datasets with partition_data_proc

    development

    When partitioning a large amount of data, it is recommended to use the partition_data_proc procedure instead of the function. This procedure commits data in smaller batches, which reduces issues caused by long-running transactions and data contention.

    partition_data_proc (
        p_parent_table text
        , p_loop_count int DEFAULT NULL
        , p_interval text DEFAULT NULL
        , p_lock_wait int DEFAULT 0
        , p_lock_wait_tries int DEFAULT 10
        , p_wait int DEFAULT 1
        , p_order text DEFAULT 'ASC'
        , p_source_table text DEFAULT NULL
        , p_ignored_columns text[] DEFAULT NULL
        , p_quiet boolean DEFAULT false
        , p_ignore_infinity boolean DEFAULT false
    )
  9. Migrate ISO weekly partition sets to new suffix format

    development

    To migrate a weekly partition set using the old IYYYwIW format to the new YYYYMMDD format, follow these steps:

    1. Generate and run rename SQL: Use a query to generate ALTER TABLE ... RENAME TO ... statements. You must adjust the substring lengths based on your parent table name length + 2 (to account for _p).
    2. Update configuration: Change the datetime_string in partman.part_config to 'YYYYMMDD'.
    3. Test maintenance: Temporarily increase premake and set infinite_time_partitions = true, then run partman.run_maintenance() to verify new partitions follow the new naming convention.
    4. Revert settings: Restore premake and infinite_time_partitions to their original values.
    -- 1. Generate rename statements (Adjust '20' and '21' based on parent table name length + 2)
    SELECT format(
        'ALTER TABLE %I.%I RENAME TO %I;'
        , n.nspname
        , c.relname
        , substring(c.relname from 1 for 20) || to_char(to_timestamp(substring(c.relname from 21), 'IYYY"w"IW'), 'YYYYMMDD')
    )
    FROM pg_inherits h
    JOIN pg_class c ON h.inhrelid = c.oid
    JOIN pg_namespace n ON c.relnamespace = n.oid
    WHERE h.inhparent::regclass = 'your_schema.your_parent_table'::regclass
    AND c.relname NOT LIKE '%_default'
    ORDER BY c.relname;
    
    -- 2. Update config
    UPDATE partman.part_config SET datetime_string = 'YYYYMMDD';
    
    -- 3. Test
    UPDATE partman.part_config SET premake = premake+1, infinite_time_partitions = true;
    SELECT partman.run_maintenance('your_schema.your_parent_table');
  10. Manage partition naming length limits

    development
    PostgreSQL has a 63-byte limit for object names. pg_partman handles this by truncating the parent table name to accommodate the required partition suffix. To avoid naming conflicts or excessive truncation (especially with number-based partitioning), it is recommended to keep parent table names as short as possible.
  11. Configure time zones for maintenance operations

    development

    To avoid issues with time-based partitioning (especially during Daylight Saving Time changes), ensure all systems running pg_partman maintenance are consistent.

    • Recommendation: Always run your database system in UTC.
    • Client Behavior: pg_partman functions use the time zone set by the client at the time of the call. Ensure the client creating partition sets and running maintenance is also set to UTC.