All Products
Search
Document Center

PolarDB:Guide to creating a columnstore index

Last Updated:Mar 28, 2026

A clustered columnar index (CCI) stores data in column-oriented format on a column store read-only instance, enabling fast analytical processing (AP) queries on large datasets. This document covers when to use CCIs and how to configure partitions, sort keys, and dictionary encoding for optimal performance.

Important

CCIs are asynchronously built using Change Data Capture (CDC) nodes to maintain consistent query results. Data synchronization from the primary instance to column store read-only instances introduces second-level latency. Avoid using CCIs in scenarios that require real-time data access.

Use cases

Use CCIs in the following scenarios:

  • Mixed workloads or complex query acceleration: Your application runs complex AP queries—such as aggregations and joins on large tables—and row-based storage no longer meets performance requirements. CCIs significantly improve query performance in these situations.

  • Cold data archiving: Your data grows over time and storage costs are a concern. Column store read-only nodes access data through CCI metadata stored in Object Storage Service (OSS), reducing storage costs for cold data. For details, see TTL.

  • Historical snapshots: You need to retain and query historical data over time without expiration. A CCI acts as a non-expiring replica of historical data, making it well-suited for audit and backup workflows.

  • ETL pipelines: You need a data replica of the primary instance to run extract, transform, load (ETL) jobs without impacting production. Connect to a column store read-only instance to run ETL and transfer data to downstream systems.

Partitioned tables

Partitioning methods

Partitioning divides large tables into smaller, independently managed segments, improving query performance and data management for large datasets. The supported syntax for partitioning CCIs is:

PARTITION BY
    HASH({column_name | partition_func(column_name)})
  | KEY(column_list)
  | RANGE({column_name | partition_func(column_name)})
  | RANGE COLUMNS(column_list)
  | LIST({column_name | partition_func(column_name)})
  | LIST COLUMNS(column_list)

The available partitioning types are:

  • RANGE: Divides data based on value ranges of a column. For example, partition a sales history table by year so that all records from the same year share a partition.

  • LIST: Divides data based on whether column values fall within a predefined set. This is common in SaaS scenarios—for example, partitioning a user table by country.

  • HASH / KEY: Divides data based on the hash value of a column, distributing rows evenly across partitions. Use this when data distribution is unpredictable or when even distribution is required.

For a complete reference, see Partitioning types.

Choose a partitioning method

CCIs are designed primarily to accelerate AP queries involving aggregations and joins. To take full advantage of columnar parallel scanning, use hash partitioning in most cases.

Follow these guidelines for specific scenarios:

ScenarioRecommended method
AP queries (aggregations, joins)Hash partitioning
Cold data archiving based on timeRange partitioning on a time column
Time-related query filtersAdd a time column as a subpartition key
Data queryable by a predefined value setList partitioning (verify row-oriented storage doesn't suffice first)

For range- or list-partitioned tables: if row-oriented storage meets your query requirements, prefer it over columnar partitioning.

For time-based subpartitions, see Subpartitioning. Avoid creating subpartitions unless queries heavily rely on time-range conditions.

Configure the number of partitions

Calculate the number of partitions using this formula:

Number of computing nodes × Number of cores per compute node

The default is 16 partitions—do not use this default for production workloads. You can set the partition count higher than the formula result to accommodate future data growth.

Note

For queries that join multiple tables, keep the partition count consistent across all involved tables within the same instance.

Select a partition key

The partition key determines how data is distributed across partitions. A poor partition key leads to data skew, which concentrates load on a single partition and degrades performance.

Choose a column with evenly distributed values, such as:

  • Transaction ID

  • Device ID

  • User ID

  • Auto-increment column

Avoid time-based columns (date, timestamp) as the partition key. Most writes tend to concentrate in the same time period, causing data skew. Time-range queries also end up hitting a single partition rather than distributing load across nodes. If time filtering is important for your queries, use the time column as a subpartition key instead.

Choose a column frequently used in `JOIN` or `GROUP BY` to minimize data redistribution overhead. For example, if you analyze historical orders by customer, use the customer ID column.

Choose a column frequently used in equality or non-range conditions to enable partition pruning.

Each table has one partition key, which can consist of multiple fields. Fewer fields in the partition key generally improve adaptability in complex query scenarios.

Important
  • If no partition key is specified when you create a table, the system uses the primary key. If no explicit primary key exists, the system uses the implicit primary key.

  • After creating a CCI, run check columnar partition db_name.tbl_name to inspect data distribution across partitions. Review the output to confirm the partition key is appropriate and to identify potential data skew.

Sort key

How it works

The sort key controls how data is ordered within the CCI file. Each column data block stores the minimum and maximum values of the data it contains. When a query runs, the Pruner component evaluates these min/max values against the query conditions and classifies each block as:

  • Relevant: must be scanned

  • Possibly relevant: scanned as a candidate

  • Irrelevant: skipped entirely

A well-chosen sort key increases the proportion of blocks Pruner can skip, directly reducing scan volume and improving query speed.

image

For details on Pruner, see Configure a filter algorithm for IMCI-based queries.

Select a sort key

Query patternSort key recommendation
Queries that filter on a value range of a specific columnUse that column as the sort key
Paginated queries with ORDER BYUse the ORDER BY column as the sort key
All other casesUse the partition key as the sort key

Dictionary encoding

How it works

Dictionary encoding converts string values into integers, which speeds up GROUP BY and FILTER operations, improves compression ratios, and reduces storage costs.

Specify columns for dictionary encoding when creating a CCI:

-- Specify columns for dictionary encoding.
DICTIONARY_COLUMNS='col1,col2';

-- Create a CCI with dictionary encoding.
CREATE CLUSTERED COLUMNAR INDEX `cc_i_seller` ON t_order (`seller_id`)
    PARTITION BY HASH(`order_id`) PARTITIONS 16
    DICTIONARY_COLUMNS='order_id,seller_id';
Note

In a distributed database, dictionary-based queries require parsing and merging the dictionary across nodes, which adds overhead. Dictionary-based filtering is disabled by default during query execution. To enable it, set ENABLE_COLUMNAR_SLICE_DICT to TRUE.

Select columns for dictionary encoding

Encode columns with low cardinality—columns with a small number of distinct values relative to total row count, such as gender or region.

Avoid encoding all character columns. Columns with high cardinality offer minimal compression benefit and add encoding and decoding overhead.

FAQ

Does changing cluster specifications affect the number of partitions?

No. The partition count is set at CCI creation and is not affected by changes to cluster specifications.

Can I change the partition key, sort key, number of partitions, or dictionary-encoded columns after creating a CCI?

No. Drop the CCI and recreate it with the new configuration.

Do I need a column store read-only instance to create CCIs?

No. CCIs can be created on the primary instance. To query CCI data, we recommend that you purchase a column store read-only instance.