All Products
Search
Document Center

PolarDB:Storage pools

Last Updated:Mar 28, 2026

Storage pools let you divide the physical data nodes (DNs) in a PolarDB-X instance into named resource groups. By binding a database, table, or table partition to a specific pool, you get physical storage isolation between tenants — without complex manual migration or resharding.

Use storage pools to:

  • Isolate storage resources between VIP and regular tenants in a multitenant SaaS or e-commerce platform

  • Guarantee stable online traffic for high-value sellers while sharing resources among smaller ones

  • Adjust tenant service levels — upgrade or downgrade isolation granularity — without taking services offline

  • Migrate from a standalone database to a distributed one gradually, with standalone and distributed tables coexisting on separate storage nodes

Key concepts

TermDescription
Storage poolA named group of one or more DNs. DNs in different pools never overlap.
`_default`The system pool that initially contains all active DNs. Objects with no explicit LOCALITY go here.
`_recycle`The system pool for idle DNs — those removed from other pools or not yet allocated. New pools draw from here.
`LOCALITY`The SQL clause used to bind a database, table, or partition to one or more storage pools.
`UNDELETABLE_DN`A designated DN within each pool that cannot be removed until the pool itself is deleted. Ensures minimum pool availability.
Data node (DN)A physical storage node in the PolarDB-X instance.

Prerequisites

Before you begin, ensure that you have:

How storage pools work

Each storage pool is a logical group of DNs. When you bind a database object to a pool, PolarDB-X physically stores that object's data on the DNs in that pool.

  • On binding: Data is migrated automatically and asynchronously to satisfy the new layout. Migration is online and transparent to your application.

  • On pool membership change: Adding or removing DNs from a pool triggers the same automatic migration.

  • Broadcast tables: Replicas are created in every pool listed in the object's storage_pools parameter.

  • Inheritance: If a child object (table or partition) has no explicit LOCALITY, it inherits the primary_storage_pool from its parent database.

Choose an isolation strategy

StrategyBest forTrade-offs
Database-levelEach large tenant gets a dedicated database and storage poolSimplest to set up; schema changes per tenant are independent
Partition-levelMany tenants share one logical table; fine-grained per-partition isolationAll partitions share the same schema; partition split/merge lets you change service levels online
Migration (standalone to distributed)Gradually moving from a standalone relational databaseReuse existing table creation statements with minimal changes; transform tables one at a time

Manage storage pools

Important

Use the Data Node Management feature in the PolarDB-X console to create and manage storage pools in production. The SQL statements below are provided to help you understand how storage pools work.

View storage pools

Query the information_schema.storage_pool_info view to see all pools and their current state.

SELECT * FROM information_schema.storage_pool_info;

Sample output:

+------+----------+-----------------------------------------------------+-----------------+---------------------------+--------+---------------------+---------------------+
| ID   | NAME     | DN_ID_LIST                                          | IDLE_DN_ID_LIST | UNDELETABLE_DN_ID         | EXTRAS | GMT_CREATED         | GMT_MODIFIED        |
+------+----------+-----------------------------------------------------+-----------------+---------------------------+--------+---------------------+---------------------+
|    1 | _default | dn0,dn1,dn2,dn3,dn4,dn5,dn6,dn7,dn8,dn9             |                 | dn0                       | NULL   | 2024-04-15 19:46:05 | 2024-04-15 19:46:05 |
|    2 | _recycle |                                                     |                 |                           | NULL   | 2024-04-15 19:46:05 | 2024-04-15 19:46:05 |
+------+----------+-----------------------------------------------------+-----------------+---------------------------+--------+---------------------+---------------------+

Create a storage pool

Combine idle DNs from _recycle into a new named pool.

CREATE STORAGE POOL <pool_name> DN_LIST = "<dn_id_1>,<dn_id_2>,..." UNDELETABLE_DN = "<dn_id>";
ParameterDescription
pool_nameName of the new storage pool
DN_LISTComma-separated list of idle DN IDs to include
UNDELETABLE_DNThe DN that cannot be removed until the pool is deleted; ensures minimum availability

Rules:

  • Each pool must have at least one DN, with one designated as UNDELETABLE_DN.

  • DNs specified at creation must not contain any database objects.

  • DNs cannot belong to more than one pool.

  • A single pool can contain DNs of different specifications.

Example: Create three pools from available nodes.

-- pool_1: three nodes
CREATE STORAGE POOL pool_1 DN_LIST="dn4,dn5,dn6" UNDELETABLE_DN="dn4";
-- pool_2: one node
CREATE STORAGE POOL pool_2 DN_LIST="dn7" UNDELETABLE_DN="dn7";
-- pool_3: two nodes
CREATE STORAGE POOL pool_3 DN_LIST="dn8,dn9" UNDELETABLE_DN="dn8";

Scale a storage pool

Scale in (remove a node)

Removing a node moves it to _recycle. Data on that node is migrated automatically and asynchronously.

ALTER STORAGE POOL <pool_name> DRAIN NODE "<dn_id_list>";

Example: Scale in _default to keep only dn0. All other nodes move to _recycle.

ALTER STORAGE POOL _default DRAIN NODE 'dn1,dn2,dn3,dn4,dn5,dn6,dn7,dn8,dn9';

Result:

+------+----------+-----------------------------------------------------+-----------------+---------------------------+--------+---------------------+---------------------+
| ID   | NAME     | DN_ID_LIST                                          | IDLE_DN_ID_LIST | UNDELETABLE_DN_ID         | EXTRAS | GMT_CREATED         | GMT_MODIFIED        |
+------+----------+-----------------------------------------------------+-----------------+---------------------------+--------+---------------------+---------------------+
|    1 | _default | dn0                                                 |                 | dn0                       | NULL   | 2024-04-15 19:46:05 | 2024-04-15 19:46:05 |
|    2 | _recycle | dn1,dn2,dn3,dn4,dn5,dn6,dn7,dn8,dn9                 |                 |                           | NULL   | 2024-04-15 19:46:05 | 2024-04-15 19:46:05 |
+------+----------+-----------------------------------------------------+-----------------+---------------------------+--------+---------------------+---------------------+
The UNDELETABLE_DN cannot be removed. Each pool must always contain at least one DN.

Scale out (add a node)

ALTER STORAGE POOL <pool_name> APPEND NODE "<dn_id_list>";

Rules:

  • The node must not contain any database objects.

  • DNs cannot belong to more than one pool.

  • A pool can mix DNs of different specifications.

Example: Move dn1 back to _default.

ALTER STORAGE POOL _default APPEND NODE 'dn1';

Result:

+------+----------+-----------------------------------------------------+-----------------+---------------------------+--------+---------------------+---------------------+
| ID   | NAME     | DN_ID_LIST                                          | IDLE_DN_ID_LIST | UNDELETABLE_DN_ID         | EXTRAS | GMT_CREATED         | GMT_MODIFIED        |
+------+----------+-----------------------------------------------------+-----------------+---------------------------+--------+---------------------+---------------------+
|    1 | _default | dn0,dn1                                             |                 | dn0                       | NULL   | 2024-04-15 19:46:05 | 2024-04-15 19:46:05 |
|    2 | _recycle | dn2,dn3,dn4,dn5,dn6,dn7,dn8,dn9                     |                 |                           | NULL   | 2024-04-15 19:46:05 | 2024-04-15 19:46:05 |
+------+----------+-----------------------------------------------------+-----------------+---------------------------+--------+---------------------+---------------------+

Bind database objects to storage pools

Use the LOCALITY clause to assign a database, table, or partition to one or more storage pools.

Define a binding

CREATE DATABASE | TABLE ... LOCALITY = "storage_pools=<pool_list>;primary_storage_pool=<pool_name>";
ParameterDescription
storage_poolsAll pools the object can use. For broadcast tables, a replica is created in every pool in this list.
primary_storage_poolThe default pool for child objects that have no explicit LOCALITY.
When you explicitly specify a LOCALITY for a child object (table or partition), the specified pool must belong to the storage_pools list of its parent database.

Example: Create a database that can use three pools, with pool_1 and pool_2 as defaults.

CREATE DATABASE db_test MODE = "auto" LOCALITY = "storage_pools=pool_1,pool_2,pool_3;primary_storage_pool=pool_1,pool_2";

Modify a binding

Change the storage pools for an existing database, table group, or partition. The system runs a background migration task to move data from the old pool to the new one. The process is online and transparent to your application.

ALTER DATABASE | TABLE <object_name> [SET] LOCALITY = "...";

What happens when you change the association:

  • Add a pool: No data is migrated. Broadcast tables expand to the new pool's nodes.

  • Remove a pool: The pool's bindings for the object and all its child objects are removed, and their data is migrated to the remaining pools.

  • Clear all pools (not supported at the database level): The object's association reverts to the default storage pool.

For database-level objects, you cannot remove the primary_storage_pool from the storage_pools list, and you cannot change a primary_storage_pool that has already been defined. These restrictions do not apply to table groups and partitions.

Example:

ALTER TABLE db_test LOCALITY = "storage_pools=pool_1;primary_storage_pool=pool_1";

Scenarios

image.png

Scenario 1: Database-level tenants

An e-commerce platform needs to serve multiple sellers: a few large sellers with heavy order volumes and many smaller sellers. The requirements are:

  • Large sellers each get dedicated storage to guarantee stable online traffic and independent analytics.

  • Small sellers share a common storage pool with balanced resource distribution.

  • When a small seller grows into a large seller, they can be migrated to dedicated storage without affecting others.

Set up tenant isolation

Divide DNs into dedicated pools for large sellers and use _default as the shared pool for small sellers.

CREATE STORAGE POOL sp1 dn_list="dn4,dn5,dn6" undeletable_dn="dn4";
CREATE STORAGE POOL sp2 dn_list="dn7,dn8,dn9" undeletable_dn="dn7";

-- Small sellers share _default (dn1, dn2, dn3)
CREATE DATABASE orders_comm MODE = "auto" LOCALITY= "storage_pools='_default'";

-- Large Seller 1 uses sp1 (dn4, dn5, dn6)
CREATE DATABASE orders_seller1 MODE = "auto" LOCALITY= "storage_pools='sp1'";
-- Large Seller 2 uses sp2 (dn7, dn8, dn9)
CREATE DATABASE orders_seller2 MODE = "auto" LOCALITY= "storage_pools='sp2'";

After running these statements:

  • Large sellers and small sellers are physically isolated on separate DNs.

  • Small sellers share dn1, dn2, and dn3 in _default.

  • Each seller's database has an independent schema and storage allocation.

Rebalance resources among small sellers

To distribute data evenly across small-seller nodes:

REBALANCE TENANT "_default" POLICY="data_balance";

This automatically distributes all non-partitioned tables and partitions across dn1, dn2, and dn3.

Onboard a new large seller

Add nodes, create a dedicated pool, and assign a new database to it. New nodes are initially in _recycle.

CREATE STORAGE POOL spn dn_list = "dn10,dn11,dn12" undeletable_dn="dn10";
CREATE DATABASE orders_sellern MODE = "auto" LOCALITY="storage_pools='spn'";

Offboard a large seller

Delete the database and release the storage pool. Existing services on other pools are not affected, and applications experience minimal interruption.

DROP DATABASE orders_sellern;
DELETE STORAGE POOL spn; -- Nodes automatically move to _recycle
ALTER STORAGE POOL _recycle DRAIN NODE "dn7, dn8, dn9"; -- Release nodes from the cluster

Scenario 2: Partition-level tenants

Partition-level isolation gives you the same physical separation as database-level isolation, with two additional advantages:

  • All partitions share a single logical table definition. Schema changes (adding columns, indexes) apply uniformly across all tenants.

  • Partition management operations (split, merge) let you change tenant service levels online — for example, upgrading a small seller to VIP by splitting their partition onto a dedicated pool.

Set up tenant isolation

Create a database that spans all pools, then partition the orders table so each large tenant maps to a dedicated pool.

CREATE DATABASE orders_db MODE = "auto"
       LOCALITY = "storage_pools='_default,sp1,sp2,...',primary_storage_pool='_default'";

USE orders_db;

-- Broadcast table: replicated across all pools so joins can be pushed down
CREATE TABLE commodity(
  commodity_id int,
  commodity_name varchar(64)
) BROADCAST;

-- Partitioned by seller_id; large tenants get dedicated pools
CREATE TABLE orders_sellers(
  order_id int AUTO_INCREMENT primary key,
  customer_id int,
  commodity_id int,
  country varchar(64),
  seller_id int,
  order_time datetime not null)
partition BY list(seller_id)
(
  partition p1 VALUES IN (1, 2, 3, 4),
  partition p2 VALUES IN (5, 6, 7, 8),
  -- p1 and p2 use _default by default (primary_storage_pool)
  ...
  partition pn VALUES IN (k) LOCALITY = "storage_pools='sp1'",
  partition pn+1 VALUES IN (k+1, k+2) LOCALITY = "storage_pools='sp2'",
  -- pn and pn+1 exclusively occupy sp1 and sp2, respectively
  ...
) LOCALITY = "storage_pools='_default,sp1,sp2,...'";

Onboard a new small tenant

Add a partition. No data migration occurs — only the new partition is created.

ALTER TABLE orders_sellers ADD PARTITION
(PARTITION p3 values in (32, 33));

Onboard a new large tenant

Add nodes, create a pool, register it with the database, then add a partition bound to the new pool.

CREATE STORAGE POOL spn dn_list = "dn10,dn11,dn12" undeletable_dn="dn10";

ALTER DATABASE orders_db SET LOCALITY =
  "storage_pools='_default,sp1,sp2,...,spn',primary_storage_pool='_default'";
-- Adding spn triggers automatic migration of the broadcast table to spn's nodes

ALTER TABLE orders_sellers ADD PARTITION
(PARTITION p4 values in (34) LOCALITY="storage_pools='spn'");

Offboard a tenant

Drop the partition or narrow the values it covers.

-- Remove a large tenant partition entirely
ALTER TABLE orders_sellers DROP PARTITION p4;

-- Or remove specific sellers from a shared partition
ALTER TABLE orders_sellers MODIFY PARTITION p1 DROP VALUES (4, 5);

All partition operations are online and affect only the target partition. Other partitions remain unaffected.

Upgrade a small seller to VIP

Split the partition to move the seller to a dedicated pool.

ALTER TABLE orders_sellers SPLIT PARTITION p2 INTO
(PARTITION p2 VALUES in (6, 7, 8),
PARTITION `pn+2` VALUES in (5) LOCALITY = "storage_pools='spn+3'");

Downgrade a large seller

Merge partitions to consolidate them back into the shared pool.

ALTER TABLE orders_sellers MERGE PARTITION p1, pn TO p1 LOCALITY="";

The merged partition is stored in the default storage pool.

Rebalance resources

Balance load across shared-pool partitions with the same REBALANCE command:

REBALANCE TENANT "_default" POLICY="data_balance";

Scenario 3: Migration from standalone to distributed

If you're migrating from a standalone relational database, use storage pools to run standalone and distributed tables side by side. Start with non-partitioned tables in a single pool, then progressively move high-traffic tables to dedicated pools and convert them to partitioned tables — one at a time, without disrupting other tables.

Create databases and tables

Create a database with DEFAULT_SINGLE=on so that standard table creation statements from your standalone database work without modification.

CREATE DATABASE orders_db MODE = "auto" DEFAULT_SINGLE=on
    LOCALITY = "storage_pools='_default',primary_storage_pool='_default'";

use orders_db;

-- These tables are automatically balanced across _default nodes
CREATE TABLE orders_region1(
  order_id int AUTO_INCREMENT primary key,
  customer_id int,
  country varchar(64),
  city int,
  order_time datetime not null);
CREATE TABLE orders_region2(
  order_id int AUTO_INCREMENT primary key,
  customer_id int,
  country varchar(64),
  city int,
  order_time datetime not null);
CREATE TABLE orders_region3(
  order_id int AUTO_INCREMENT primary key,
  customer_id int,
  country varchar(64),
  city int,
  order_time datetime not null);

-- Broadcast table: replicated across all _default nodes
CREATE TABLE commodity(
  commodity_id int,
  commodity_name varchar(64)
) BROADCAST;

Isolate high-traffic non-partitioned tables

When a table's volume or traffic exceeds what a shared pool can handle, move it to a dedicated pool.

ALTER DATABASE orders_db set LOCALITY = "storage_pools='_default,sp1',primary_storage_pool='_default'";
-- The broadcast table automatically expands to sp1's nodes

ALTER TABLE orders_region_single1 single LOCALITY = "storage_pools='sp1'";
-- orders_region_single1 is now isolated on sp1

Convert to distributed tables

When a table's workload exceeds the capacity of a single physical table, convert it to a partitioned distributed table on a dedicated pool.

ALTER DATABASE orders_db set LOCALITY = "storage_pools='_default,sp1,sp2,sp3',primary_storage_pool='_default'";

ALTER TABLE orders_region_single2 partition by hash(order_id) partitions 16
  LOCALITY = "storage_pools='sp2'";

ALTER TABLE orders_region_single3 partition by hash(order_id) partitions 16
  LOCALITY = "storage_pools='sp3'";

All sharding operations run online. You can specify custom partitioning methods and storage pools without interrupting other tables.