Inherit Regional Tablespaces with pg_partman in YugabyteDB v2025.1 and Later

Version scope: This tip applies to YugabyteDB v2025.1 and later, where the YSQL API is based on PostgreSQL 15.

For YugabyteDB v2024.2 and earlier, which use a PostgreSQL 11-based YSQL API, use the custom pg_partman template-table workaround described in the previous tip: Preserve Regional Index Tablespaces with pg_partman Templates .

YugabyteDB tablespaces can control the geographic placement of tables and indexes.

In a geo-partitioned design, a regional partition may itself be divided into smaller time-based partitions:

				
					events
├── events_region_a
│   ├── events_region_a_p2026_07_18
│   ├── events_region_a_p2026_07_19
│   └── events_region_a_p2026_07_20
└── events_region_b
    ├── events_region_b_p2026_07_18
    ├── events_region_b_p2026_07_19
    └── events_region_b_p2026_07_20
				
			

On YugabyteDB v2025.1 and later, the regional table and its ordinary secondary indexes can be defined directly on the partitioned parent.

When pg_partman creates and attaches a new partition:

  • ● The child table receives the parent table’s tablespace.
  • ● The child receives indexes based on the partitioned indexes defined on the parent.
  • ● The child indexes use the tablespace assigned to the corresponding parent indexes.
  • ● A custom template table and custom template indexes are not required for this placement pattern.

YugabyteDB v2025.1 and v2026.1 bundle pg_partman v4.7.4. Its child-partition creation code contains a PostgreSQL 12-and-later path that applies the parent tablespace to the child, while index inheritance is handled when the partition is attached.

What Changed?

Property YugabyteDB v2024.2 and Earlier YugabyteDB v2025.1 and Later
YSQL PostgreSQL base PostgreSQL 11 PostgreSQL 15
Child table tablespace Managed using the pg_partman template workaround Obtained from the partitioned parent
Ordinary secondary indexes A template-index workaround may be needed for explicit placement Inherited from the partitioned parent when the child is attached
Custom template required Yes, for the workaround No, not for ordinary table and secondary-index tablespaces
Internal template table Created and used for tablespace properties Still created, but not the source of these tablespace properties

Demo

This demo uses YugabyteDB v2026.1 and creates daily partitions for a fictional regional event table.

It assumes that the following YugabyteDB tablespace already exists:

				
					region_a_ts
				
			

The tablespace should contain the placement policy for the appropriate region and availability zones.

Demo prerequisite: The tablespace creation statement is omitted because its replica_placement configuration depends on the cloud, region, and availability-zone labels used by your YugabyteDB cluster.

Step 1: Confirm the YugabyteDB and PostgreSQL Versions

Run:

				
					SELECT version();

SHOW server_version;

SHOW server_version_num;
				
			

On YugabyteDB v2026.1, the output should identify a PostgreSQL 15–based YSQL API.

The most useful check for conditional PostgreSQL behavior is:

				
					SHOW server_version_num;
				
			

The result should begin with 15, typically represented numerically as a value such as:

				
					yugabyte=# SHOW server_version_num;
 server_version_num
--------------------
 150012
(1 row)
				
			

The exact reported PostgreSQL 15 minor version can vary by YugabyteDB patch release.

Step 2: Check the Available pg_partman Version

Before installing the extension, query pg_available_extensions:

				
					SELECT
    name,
    default_version,
    installed_version
FROM pg_available_extensions
WHERE name = 'pg_partman';
				
			

On YugabyteDB v2026.1, the expected default version is:

				
					.   name    | default_version | installed_version
------------+-----------------+-------------------
 pg_partman | 4.7.4           |
				
			

A blank installed_version means that the extension is available but has not yet been enabled in the current database.

YugabyteDB’s 2025.1 and 2026.1 source branches both define 4.7.4 as the bundled default version.

Step 3: Enable pg_partman

Enable the extension separately in the database used for the demo:

				
					CREATE SCHEMA IF NOT EXISTS partman;

CREATE EXTENSION IF NOT EXISTS pg_partman
WITH SCHEMA partman;
				
			

YugabyteDB supports the native partitioning mode of pg_partman; trigger-based, non-native partitioning is not supported.

Step 4: Show the Installed pg_partman Version

Use the PostgreSQL extension catalog:

				
					SELECT
    extension.extname AS extension_name,
    extension.extversion AS installed_version,
    namespace.nspname AS extension_schema
FROM pg_extension AS extension
JOIN pg_namespace AS namespace
  ON namespace.oid = extension.extnamespace
WHERE extension.extname = 'pg_partman';
				
			

Expected result:

				
					.extension_name | installed_version | extension_schema
----------------+-------------------+-----------------
 pg_partman     | 4.7.4             | partman
				
			

From ysqlsh, the following shortcut also displays the extension version:

				
					\dx pg_partman
				
			

Is a newer pg_partman version required? No. YugabyteDB v2026.1 includes pg_partman 4.7.4, and that bundled version contains the PostgreSQL 12-and-later tablespace-handling path used by this demo.

Use the version bundled and tested with your YugabyteDB release rather than installing an unrelated upstream pg_partman 5.x package.

Step 5: Create the Regional Partitioned Parent

Remove an older copy of the demo table, if necessary:

				
					DROP TABLE IF EXISTS public.events_region_a CASCADE;
				
			

Create the partitioned parent directly in the regional tablespace:

				
					CREATE TABLE public.events_region_a (
    event_time TIMESTAMPTZ NOT NULL,
    entity_id  BIGINT      NOT NULL,
    payload    JSONB
)
PARTITION BY RANGE (event_time)
TABLESPACE region_a_ts;
				
			

In this design, the partitioned parent is the source of the child table’s regional tablespace.

Step 6: Create the Regional Index on the Parent

Create the secondary index directly on the partitioned parent:

				
					CREATE INDEX events_region_a_entity_time_idx
    ON public.events_region_a (
        entity_id HASH,
        event_time DESC
    )
    TABLESPACE region_a_ts;
				
			

Unlike the PostgreSQL 11 workaround, do not create this ordinary secondary index on a custom template table.

The partitioned parent index is the source of the indexes created for new child partitions.

Step 7: Verify the Parent Definitions

Check the parent table’s tablespace:

				
					SELECT
    relation.oid::REGCLASS AS relation_name,
    relation.relkind,
    COALESCE(tablespace.spcname, 'pg_default')
        AS tablespace_name
FROM pg_class AS relation
LEFT JOIN pg_tablespace AS tablespace
  ON tablespace.oid = relation.reltablespace
WHERE relation.oid = 'public.events_region_a'::REGCLASS;
				
			

Check the parent index:

				
					SELECT
    index_relation.oid::REGCLASS AS index_name,
    COALESCE(tablespace.spcname, 'pg_default')
        AS index_tablespace,
    pg_get_indexdef(index_relation.oid) AS index_definition
FROM pg_index AS index_metadata
JOIN pg_class AS index_relation
  ON index_relation.oid = index_metadata.indexrelid
LEFT JOIN pg_tablespace AS tablespace
  ON tablespace.oid = index_relation.reltablespace
WHERE index_metadata.indrelid =
      'public.events_region_a'::REGCLASS;
				
			

Both should show:

				
					region_a_ts
				
			

Step 8: Register the Table with pg_partman

Register the partitioned parent without supplying p_template_table:

				
					SELECT partman.create_parent(
    p_parent_table => 'public.events_region_a',
    p_control      => 'event_time',
    p_type         => 'native',
    p_interval     => 'daily',
    p_premake      => 2
);
				
			

Notice what is missing:

				
					p_template_table
				
			

There is no custom template table and no template index in this demo.

The create_parent() call automatically creates several daily partitions around the current date.

Step 9: Examine the Automatically Created Template

Although a custom template was not provided, pg_partman v4.7.4 still creates an internal template table and records it in partman.part_config.

Display it:

				
					SELECT
    parent_table,
    template_table
FROM partman.part_config
WHERE parent_table = 'public.events_region_a';
				
			

Example:

				
					.     parent_table       |               template_table
-------------------------+-----------------------------------------------
 public.events_region_a  | partman.template_public_events_region_a
				
			

Now inspect the template’s tablespace and indexes:

				
					WITH configuration AS (
    SELECT template_table::REGCLASS AS template_oid
    FROM partman.part_config
    WHERE parent_table = 'public.events_region_a'
)
SELECT
    template.oid::REGCLASS AS template_table,
    COALESCE(template_tablespace.spcname, 'pg_default')
        AS template_tablespace,
    COUNT(index_metadata.indexrelid)
        AS template_index_count
FROM configuration
JOIN pg_class AS template
  ON template.oid = configuration.template_oid
LEFT JOIN pg_tablespace AS template_tablespace
  ON template_tablespace.oid = template.reltablespace
LEFT JOIN pg_index AS index_metadata
  ON index_metadata.indrelid = template.oid
GROUP BY
    template.oid,
    template_tablespace.spcname;
				
			

Expected result:

				
					.               template_table                 | template_tablespace | template_index_count
-----------------------------------------------+---------------------+----------------------
 partman.template_public_events_region_a       | pg_default          |                    0
				
			

This is the key part of the demonstration:

  • ● The internal template is not in region_a_ts.
  • ● The internal template has no secondary index.
  • ● The generated child tables and indexes should still use region_a_ts.

Important distinction: The automatically generated template table still exists because it is part of the bundled pg_partman 4.7.4 architecture.

The point of this tip is not that the template object disappears. The point is that a custom template table and custom template indexes are no longer required for ordinary table and index tablespace inheritance.

Step 10: Verify the Child Tables and Indexes

Run the following query:

				
					SELECT
    child.oid::REGCLASS AS partition_name,
    COALESCE(partition_tablespace.spcname, 'pg_default')
        AS partition_tablespace,
    child_index.oid::REGCLASS AS index_name,
    COALESCE(index_tablespace.spcname, 'pg_default')
        AS index_tablespace
FROM pg_inherits AS inheritance
JOIN pg_class AS child
  ON child.oid = inheritance.inhrelid
LEFT JOIN pg_tablespace AS partition_tablespace
  ON partition_tablespace.oid = child.reltablespace
LEFT JOIN pg_index AS index_metadata
  ON index_metadata.indrelid = child.oid
LEFT JOIN pg_class AS child_index
  ON child_index.oid = index_metadata.indexrelid
LEFT JOIN pg_tablespace AS index_tablespace
  ON index_tablespace.oid = child_index.reltablespace
WHERE inheritance.inhparent =
      'public.events_region_a'::REGCLASS
ORDER BY
    partition_name,
    index_name;
				
			

Example output:

				
					.       partition_name         | partition_tablespace |                   index_name                    | index_tablespace
-------------------------------+----------------------+-------------------------------------------------+-----------------
 events_region_a_p2026_07_17   | region_a_ts          | events_region_a_p2026_07_17_entity_id_event_idx | region_a_ts
 events_region_a_p2026_07_18   | region_a_ts          | events_region_a_p2026_07_18_entity_id_event_idx | region_a_ts
 events_region_a_p2026_07_19   | region_a_ts          | events_region_a_p2026_07_19_entity_id_event_idx | region_a_ts
 events_region_a_p2026_07_20   | region_a_ts          | events_region_a_p2026_07_20_entity_id_event_idx | region_a_ts
				
			

The exact child and index names depend on the current date and PostgreSQL’s generated index names.

The important result is:

				
					partition_tablespace = region_a_ts
index_tablespace     = region_a_ts
				
			

Step 11: Insert a Sample Record

Insert a record through the partitioned parent:

				
					INSERT INTO public.events_region_a (
    event_time,
    entity_id,
    payload
)
VALUES (
    now(),
    1001,
    '{"event_type":"login","source":"demo"}'
);
				
			

Show which physical partition received the record:

				
					SELECT
    tableoid::REGCLASS AS physical_partition,
    event_time,
    entity_id,
    payload
FROM public.events_region_a
WHERE entity_id = 1001;
				
			

Example:

				
					.      physical_partition       |          event_time           | entity_id |                    payload
--------------------------------+-------------------------------+-----------+-----------------------------------------------
 events_region_a_p2026_07_18    | 2026-07-18 14:30:00-04        |      1001 | {"source": "demo", "event_type": "login"}
				
			

Step 12: Create a Future Partition

Create a partition outside the initially premade range:

				
					SELECT partman.create_partition_time(
    p_parent_table => 'public.events_region_a',
    p_partition_times => ARRAY[
        date_trunc('day', now()) + INTERVAL '10 days'
    ]
);
				
			

Run the tablespace verification query again.

The newly created partition and its inherited child index should both use:

				
					region_a_ts
				
			

The YugabyteDB-bundled function explicitly applies the parent table’s tablespace for PostgreSQL 12 and later and relies on native index handling when the table is attached as a partition.

Production Maintenance

For production use, run pg_partman maintenance frequently enough to keep the required number of future partitions available:

				
					SELECT partman.run_maintenance(
    p_parent_table => 'public.events_region_a'
);
				
			

YugabyteDB disables the pg_partman background worker and recommends scheduling run_maintenance() using pg_cron or another external scheduler.

Example using pg_cron:

				
					CREATE EXTENSION IF NOT EXISTS pg_cron;

SELECT cron.schedule(
    'Maintain Region A event partitions',
    '0 * * * *',
    $$SELECT partman.run_maintenance(
        p_parent_table => 'public.events_region_a'
    )$$
);
				
			

This example runs maintenance at the start of every hour.

When Might a Template Still Be Needed?

Scenario Consideration
Ordinary secondary index defined on the parent No custom template should be required on YugabyteDB v2025.1 and later.
Unique index includes the partition key Define it on the partitioned parent and test its child-index placement.
Unique index does not include the partition key PostgreSQL cannot define it as a global unique index on the partitioned parent. Per-partition index management may still require template or custom automation.
Existing child partitions Parent changes are not necessarily retroactive. Verify and correct existing child objects separately.
Specialized template properties The internal template mechanism still exists and may be used for properties not handled by native partition inheritance.
Do not delete the internal template: Even though it is not the source of the table and ordinary secondary-index tablespaces in this demo, it remains part of the pg_partman configuration. Let pg_partman manage that object.

Final Takeaway

Use the partitioned parent as the source of truth. On YugabyteDB v2025.1 and later, assign the regional tablespace directly to the partitioned parent table and its ordinary secondary indexes.

When pg_partman creates new child partitions, the child tables and inherited indexes should receive the parent-defined tablespaces without requiring a custom template table or custom template indexes.

YugabyteDB v2026.1 includes pg_partman v4.7.4, which is sufficient for this behavior!

Use these commands to verify the installed version:

				
					SELECT
    name,
    default_version,
    installed_version
FROM pg_available_extensions
WHERE name = 'pg_partman';
				
			

Or:

				
					\dx pg_partman
				
			

Resources

Resource Description
PostgreSQL 15 Features in YugabyteDB Documents the PostgreSQL 15–based YSQL API used by YugabyteDB v2025.1 and later.
YugabyteDB pg_partman Documentation Explains how to enable and operate pg_partman and documents YugabyteDB-specific limitations.
YugabyteDB v2026.1 pg_partman Control File Shows that YugabyteDB v2026.1 bundles pg_partman v4.7.4.
YugabyteDB pg_partman Child-Partition Function Shows the PostgreSQL 12-and-later parent-tablespace logic and native index inheritance path.
PostgreSQL 15 CREATE TABLE Documents native partition creation and the cloning of parent indexes and constraints.

Have Fun!

As we prepare for our move to Dallas, we’ve been selling a ton of stuff on Facebook Marketplace. Oddly enough, almost everything we’ve sold so far has belonged to me... mostly electronics and other gadgets that I've had for years. Well, today my wife finally had to part with something she truly cherished: our living room couch and loveseat. I told her not to worry... I’ll make sure they live on forever by memorializing them in today’s YugabyteDB Tip! 😂🛋️🛋️