Physical partitioned tables can develop two stability problems at scale: metadata bloat when a table group exceeds 10,000 child tables, and performance degradation from high-frequency DDL operations when new partitions are added daily. Hologres V3.1 introduces logical partitioned tables to address both issues. This topic describes how to migrate an existing physical partitioned table to a logical partitioned table.
When to migrate
Migrate to a logical partitioned table if your physical partitioned table meets either of the following conditions:
A table group contains more than 10,000 child partitioned tables, causing large metadata scale.
New partitions are added daily, resulting in frequent DDL operations.
Choose a migration approach
| Approach | When to use | Downtime | Risk |
|---|---|---|---|
| Dual-write verification (recommended) | Any migration where business continuity matters | Minimal — old and new tables run in parallel | Low |
| REBUILD (not recommended) | Acceptable to have a longer change window and allow some read/write failures | Longer — write tasks stop during migration | High — tasks may fail before adaptation is complete |
Migrate using dual-write verification (recommended)
This approach keeps the old and new tables running in parallel during migration, minimizing business impact.
Overview
Stop write tasks for the physical partitioned table, create a new logical partitioned table, and copy existing data.
Create a write task for the new table and enable dual writing to both tables.
Verify the new logical partitioned table.
Switch business queries and write tasks to the new table.
Adapt O&M tasks to the new table.
Step 1: Create the logical partitioned table and copy existing data
Choose one of the following methods based on your situation.
CLONE (recommended for moderate data volumes with no schema changes)
hg_clone_to_logical_partition automatically creates a new logical partitioned table and copies all data from the original table. The original physical partitioned table is not deleted.
Stop real-time write tasks before running CLONE. After CLONE completes, create new write tasks that write to both the old and new tables simultaneously to keep data in sync. After migration is complete, thoroughly verify both tables, complete the adaptation of import tasks, query tasks, and O&M tasks, and then clean up the original physical partitioned table.
CALL hg_clone_to_logical_partition('user_profile', 'user_profile_logical');For large data volumes, run CLONE asynchronously to avoid timeout. After submitting, use the returned query_id to check progress in Get query insights.
SET statement_timeout = 0;
ASYNC CALL hg_clone_to_logical_partition('user_profile', 'user_profile_logical');Manual migration (for schema changes or large data volumes)
Manual migration lets you optimize table properties and control the migration pace.
Stop real-time write tasks before importing data. After import completes, create new write tasks that write to both tables simultaneously. After migration is complete, thoroughly verify both tables, complete the adaptation of import tasks, query tasks, and O&M tasks, and then clean up the original physical partitioned table.
Create the logical partitioned table. See Table structure conversion for DDL changes required.
Import historical data using
hg_insert_overwrite:CALL hg_insert_overwrite('logical_partition_table', '{20250601, 20250602}'::text[], 'SELECT * FROM tb');
Step 2: Enable dual writing
Create a new write task for the logical partitioned table and run it alongside the existing write task for the physical partitioned table. This ensures both tables stay in sync during the verification period.
Step 3: Verify the new table
Run your business queries against the logical partitioned table and confirm the data and behavior match the physical partitioned table.
Step 4: Switch business to the new table
Use one of the following methods:
Option A (recommended): Update the table name in query tasks to point to the new logical partitioned table and add partition filter conditions where needed.
Option B: Use RENAME to swap the table names atomically:
This operation renames existing tables. Make sure your write and query tasks are ready to target the parent table before executing.
BEGIN;
ALTER TABLE <source_table_name> RENAME TO <source_table_name_archive>;
ALTER TABLE <target_table_name> RENAME TO <source_table_name>;
COMMIT;After switching, update write and query tasks that previously targeted child tables to use the parent table instead, and add partition filter conditions.
Step 5: Adapt O&M tasks
Update any O&M tasks that differ between physical and logical partitioned tables. See O&M task adaptation for details.
Migrate using REBUILD (not recommended)
During REBUILD migration, some read and write tasks may fail. Use this approach only if a longer change window is acceptable and brief task failures are tolerable. This method is not recommended for migrating partitioned tables.
When REBUILD completes, the physical partitioned table is renamed to tmp_rebuild_old_<query_id>_<unique_id>_<table_name>. Related tasks may generate errors until you complete task adaptation.
Stop write tasks for the physical partitioned table.
Run REBUILD to convert the table:
-- Add the NOT NULL constraint to the partition field. ASYNC REBUILD TABLE user_profile ALTER ds SET NOT NULL; -- Convert the physical partitioned table to a logical partitioned table (retains the original). ASYNC REBUILD TABLE user_profile WITH (keep_source) TO logical partition;For more information, see REBUILD.
Update write tasks and restart them.
Adapt query tasks to the new logical partitioned table.
Adapt O&M tasks to the new logical partitioned table.
Table structure conversion
When creating a logical partitioned table from an existing physical partitioned table DDL, make the following changes:
Add the
LOGICALkeyword beforePARTITION BY.Add
NOT NULLto the partition key column.(Optional) Add a second partition key. Physical partitioned tables support one partition key; logical partitioned tables support up to two.
Physical partitioned table:
BEGIN;
CREATE TABLE user_profile (
a TEXT,
b TEXT,
ds TEXT
)
PARTITION BY LIST (ds);
CREATE TABLE user_profile_202503 PARTITION OF user_profile FOR VALUES IN ('202503');
CREATE TABLE user_profile_202504 PARTITION OF user_profile FOR VALUES IN ('202504');
COMMIT;Logical partitioned table — one partition key:
CREATE TABLE user_profile_lp_1 (
a TEXT,
b TEXT,
ds TEXT NOT NULL)
LOGICAL PARTITION BY LIST (ds);Logical partitioned table — two partition keys (splitting ds into yy for year and mm for month):
CREATE TABLE user_profile_lp_2 (
a TEXT,
b TEXT,
yy TEXT NOT NULL,
mm TEXT NOT NULL)
LOGICAL PARTITION BY LIST (yy, mm);Import task adaptation
If your import task writes to the parent table via Fixed Plan: no changes needed.
If your import task writes to child tables: make the following changes:
Remove statements that create child tables. Logical partitioned tables have no physical child tables.
Change the target table name from the child table name to the parent table name.
Scenario 1: Create a child table and import data
-- Original: physical partitioned table
CREATE TABLE user_profile_202505 PARTITION OF user_profile FOR VALUES IN ('202505');
INSERT INTO user_profile_202505 SELECT a, b, ds FROM <source_table> WHERE ds = '202505';
-- Updated: logical partitioned table
INSERT INTO user_profile_lp_1 SELECT a, b, ds FROM <source_table> WHERE ds = '202505';Scenario 2: Refresh data in a partition
If the original task uses hg_insert_overwrite to refresh multiple child tables at once, split the refreshes by partition and run them serially after migrating to a logical partitioned table. Use the native INSERT OVERWRITE syntax instead. For more information, see INSERT OVERWRITE.
-- Original: physical partitioned table (temporary table method)
CREATE TABLE tmp_user_profile_202505(a text, b text, ds text);
INSERT INTO tmp_user_profile_202505 SELECT a, b, ds FROM <source_table> WHERE ds = '202505';
BEGIN;
DROP TABLE IF EXISTS user_profile_202505;
ALTER TABLE tmp_user_profile_202505 RENAME TO user_profile_202505;
ALTER TABLE user_profile ATTACH PARTITION user_profile_202505 FOR VALUES IN ('202505');
COMMIT;
-- Original: physical partitioned table (hg_insert_overwrite method)
CALL hg_insert_overwrite('user_profile', '202505', $$SELECT a, b, mm FROM <source_table> WHERE ds='202505'$$);
-- Updated: logical partitioned table (native INSERT OVERWRITE)
INSERT OVERWRITE user_profile_lp_1 PARTITION (ds = '202505') SELECT a, b, ds FROM <source_table> WHERE ds='202505';Query task adaptation
If your query targets the parent table: no changes needed.
If your query targets a child table: change it to query the parent table and add a partition filter condition.
-- Original: physical partitioned table
SELECT * FROM user_profile_202504;
-- Updated: logical partitioned table
SELECT * FROM user_profile_lp_1 WHERE ds = '202504';O&M task adaptation
Some O&M operations use different system tables or functions for logical partitioned tables.
| Operation | Physical partitioned table | Logical partitioned table |
|---|---|---|
| Query parent table DDL | hg_dump_script function | No difference |
| Query parent table properties | hologres.hg_table_properties | No difference |
| Query partition list | Query all child tables | hologres.hg_list_logical_partition |
| Query partition properties | hologres.hg_table_properties | hologres.hg_logical_partitioned_table_properties |
| Query parent table storage | hg_table_storage_status | hg_partition_file_status |
Property and parameter mapping
The following table maps dynamic partition management parameters and other table properties between physical and logical partitioned tables.
If the original physical partitioned table uses a non-time type field (such as TEXT) as the partition key and uses dynamic partition management, partition_expiration_time and partition_keep_hot_window are not supported on the logical partitioned table.
| Module | Feature | Physical partitioned table | Logical partitioned table |
|---|---|---|---|
| Dynamic partition management | Enable | auto_partitioning_enable | No equivalent parameter |
| Time unit | auto_partitioning_time_unit | No separate configuration; only time-type partitions are supported | |
| Time zone | auto_partitioning_time_zone | No equivalent parameter | |
| Pre-created partitions | auto_partitioning_num_precreate | No equivalent parameter | |
| Historical partitions to retain | auto_partitioning_num_retention | partition_expiration_time | |
| Hot partitions to retain | auto_partitioning_num_hot | partition_keep_hot_window | |
| Scheduling time | auto_partitioning_schd_start_time | No equivalent parameter | |
| Child table date/time format | auto_partitioning_time_format | No equivalent parameter | |
| Partition management | Retain partition | keep_alive | keep_alive |
| Hot/cold storage | storage_mode | storage_mode | |
| Binary log | Switch (parent table) | binlog_level | binlog_level |
| Lifecycle (parent table) | binlog_ttl | binlog_ttl | |
| Dynamic binary log by partition | Not supported | partition_generate_binlog_window | |
| Switch (partitions) | Inherits from parent; modification not supported | generate_binlog | |
| Lifecycle (partitions) | Child tables support modification | Inherits from parent; modification not supported | |
| Indexes and table properties | bitmap_columns | Child tables support modification | Inherits from parent; modification not supported |
dictionary_encoding_columns | Child tables support modification | Inherits from parent; modification not supported | |
orientation, table_group, and other properties | Inherits from parent; modification not supported | Inherits from parent; modification not supported | |
Primary key, distribution_key, clustering_key, and other indexes | Inherits from parent; modification not supported | Inherits from parent; modification not supported |