PolarDB for PostgreSQL supports columnstore indexes on partitioned tables. If your system handles both transactional and analytical workloads and you want to run analytical queries on partitioned historical data without a separate ETL pipeline, you can add a columnstore index directly on your partitioned table.
In benchmark tests with a two-level partitioned table and 320 million rows (~16 GB), the columnstore index is over 35 times faster than native PostgreSQL parallel execution at a degree of parallelism of 4:
| Query | Native PostgreSQL parallel execution | Columnstore index |
|---|---|---|
| Q1 | 2.13 s | 0.05 s |
| Q2 | 6.42 s | 0.18 s |
| Q3 | 10.51 s | 0.30 s |
Prerequisites
Before you begin, make sure that:
Your cluster runs one of the supported versions: To check your minor engine version, run
SHOW polardb_version;or view the minor engine version in the console. If your version doesn't meet the requirement, upgrade the minor engine version.PostgreSQL 16, minor engine version 2.0.16.8.3.0 or later
PostgreSQL 14, minor engine version 2.0.14.10.20.0 or later
The partitioned table has a primary key, and the primary key column is included when you create the columnstore index.
The
wal_levelparameter is set tological. This adds the information required for logical replication to the write-ahead log (WAL).ImportantSet the
wal_levelparameter in the console. Changing this parameter restarts the cluster—plan your maintenance window accordingly.
Choose an enablement method
The enablement method depends on your minor engine version. Identify your version range before proceeding.
PostgreSQL 16 (2.0.16.9.8.0 or later) or PostgreSQL 14 (2.0.14.17.35.0 or later)
Two methods are available for these versions:
| Add an In-Memory Column Index (IMCI) read-only node (recommended) | Use the pre-installed extension | |
|---|---|---|
| Setup | Add a node through the console. | No setup required—use the extension directly. |
| Memory allocation | The columnstore engine uses all node resources exclusively. | The columnstore engine is limited to 25% of memory; the remaining 75% goes to the row-store engine. |
| Workload isolation | Transactional processing (TP) and analytical processing (AP) workloads run on separate nodes and don't affect each other. | TP and AP workloads share the same node and may affect each other. |
| Cost | IMCI read-only nodes incur additional charges, billed at the same rate as regular compute nodes. | No additional cost. |
Add an IMCI read-only node if you need TP/AP isolation and can allocate dedicated resources for analytics. Use the pre-installed extension if you want to test the feature at no extra cost or if workload isolation isn't a requirement.
PostgreSQL 16 (2.0.16.8.3.0 to 2.0.16.9.8.0) or PostgreSQL 14 (2.0.14.10.20.0 to 2.0.14.17.35.0)
For these versions, the columnstore index is available only as the polar_csi extension. Install the extension in each database where you want to use the feature.
Enable the columnstore index
Follow the instructions for your version and chosen method.
Add an IMCI read-only node (recommended for newer versions)
Your cluster must already have at least one read-only node. You cannot add an IMCI read-only node to a single-node cluster.
Add in the console:
Log on to the PolarDB console and select the region where your cluster is located. Open the Add/Remove Node wizard using one of these paths:
On the Clusters page, click Add/Remove Node in the Actions column.

On the Basic Information page of your cluster, click Add/Remove Node in the Database Nodes section.

Select Add Read-only IMCI Node and click OK.
On the upgrade/downgrade page, configure and purchase the node:
Click Add an IMCI Node and select the node specifications.
Select a switchover time.
(Optional) Review the Product Terms of Service and Service Level Agreement.
Click Buy Now.

Return to the cluster details page. The IMCI node is ready when its status changes to Running.

Add during purchase:
On the PolarDB purchase page, set the number of nodes for the IMCI Read-Only Nodes parameter.

Use the pre-installed extension (newer versions)
No additional setup is required. The extension is pre-installed and ready to use.
Install the polar_csi extension (older versions)
The polar_csi extension is scoped to the database level. Install it separately in each database where you want to use the columnstore index. The database account used to install the extension must be a privileged account.
Install from the console:
Log on to the PolarDB console. In the left navigation pane, click Clusters, select your region, and click your cluster ID to open the cluster details page.
In the left navigation pane, choose Settings and Management > Extension Management. On the Extension Management tab, select Uninstalled Extensions.
In the upper-right corner, select the target database. In the row for
polar_csi, click Install in the Actions column. In the Install Extension dialog box, select the target Database Account and click OK.
Install from the command line:
Connect to the database cluster and run the following statement in the target database:
CREATE EXTENSION polar_csi;Set up a partitioned table and create a columnstore index
This example creates a two-level partitioned table with 320 million rows (~16 GB), partitioned by year and then by month.
Table schema:
| Table | Description |
|---|---|
sales | Primary (top-level) table |
sales_2023 | 2023 data, partitioned by month |
sales_2023_a | Months 1–6 of 2023 |
sales_2023_b | Months 7–12 of 2023 |
sales_2024 | 2024 data, partitioned by month |
sales_2024_a | Months 1–6 of 2024 |
sales_2024_b | Months 7–12 of 2024 |
Create the partitioned table. The partition key is
sale_date. The table requires a composite primary key that includes the partition key.CREATE TABLE sales ( sale_id serial, product_id int NOT NULL, sale_date date NOT NULL, amount numeric(10,2) NOT NULL, primary key(sale_id, sale_date) ) PARTITION BY RANGE (sale_date); CREATE TABLE sales_2023 PARTITION OF sales FOR VALUES FROM ('2023-1-1') TO ('2024-1-1') PARTITION BY RANGE (sale_date); CREATE TABLE sales_2023_a PARTITION OF sales_2023 FOR VALUES FROM ('2023-1-1') TO ('2023-7-1'); CREATE TABLE sales_2023_b PARTITION OF sales_2023 FOR VALUES FROM ('2023-7-1') TO ('2024-1-1'); CREATE TABLE sales_2024 PARTITION OF sales FOR VALUES FROM ('2024-1-1') TO ('2025-1-1') PARTITION BY RANGE (sale_date); CREATE TABLE sales_2024_a PARTITION OF sales_2024 FOR VALUES FROM ('2024-1-1') TO ('2024-7-1'); CREATE TABLE sales_2024_b PARTITION OF sales_2024 FOR VALUES FROM ('2024-7-1') TO ('2025-1-1');Insert ~16 GB of test data.
INSERT INTO sales (product_id, sale_date, amount) SELECT (random()*100)::int AS product_id, '2023-01-1'::date + i/3200000*7 AS sale_date, (random()*1000)::numeric(10,2) AS amount FROM generate_series(1, 320000000) i;Create a columnstore index on
sales. Include the primary key column (sale_id) along with all columns used in analytical queries.CREATE INDEX ON sales USING CSI(sale_id, product_id, sale_date, amount);
Run queries with the columnstore index
The following three queries filter by different sale_date ranges to exercise partition pruning across different partition boundaries.
Using the columnstore index (degree of parallelism = 4):
-- Enable the columnstore index and set the degree of parallelism to 4.
SET polar_csi.enable_query TO on;
SET polar_csi.exec_parallel TO 4;
-- Q1: 2-month range (within a single sub-partition)
EXPLAIN ANALYZE SELECT sale_date, COUNT(*) FROM sales WHERE sale_date BETWEEN '2023-1-1' AND '2023-3-1' AND amount > 100 GROUP BY sale_date;
-- Q2: 8-month range (spans two sub-partitions)
EXPLAIN ANALYZE SELECT sale_date, COUNT(*) FROM sales WHERE sale_date BETWEEN '2023-1-1' AND '2023-9-1' AND amount > 100 GROUP BY sale_date;
-- Q3: 14-month range (spans two years)
EXPLAIN ANALYZE SELECT sale_date, COUNT(*) FROM sales WHERE sale_date BETWEEN '2023-1-1' AND '2024-3-1' AND amount > 100 GROUP BY sale_date;Using the row-store engine for comparison (degree of parallelism = 4):
-- Disable the columnstore index and use native PostgreSQL parallel execution.
SET polar_csi.enable_query TO off;
SET max_parallel_workers_per_gather TO 4;
-- Q1
EXPLAIN ANALYZE SELECT sale_date, COUNT(*) FROM sales WHERE sale_date BETWEEN '2023-1-1' AND '2023-3-1' AND amount > 100 GROUP BY sale_date;
-- Q2
EXPLAIN ANALYZE SELECT sale_date, COUNT(*) FROM sales WHERE sale_date BETWEEN '2023-1-1' AND '2023-9-1' AND amount > 100 GROUP BY sale_date;
-- Q3
EXPLAIN ANALYZE SELECT sale_date, COUNT(*) FROM sales WHERE sale_date BETWEEN '2023-1-1' AND '2024-3-1' AND amount > 100 GROUP BY sale_date;