customer_id, not on a partition column.
What changes
When partition pruning is on, the importer makes three changes to the table. Table config. For every partition field it can reproduce exactly, the importer adds the source column totableIndexConfig.segmentPartitionConfig.columnPartitionMap. It then adds partition to routing.segmentPrunerTypes. Existing pruner types stay in place.
Iceberg records no partition count for
identity or truncate. Pinot hashes those values into 32 partitions. The count is fixed and you can’t configure it.
Task config. The importer records what it owns in the ExternalTableSyncTask config: partitionPruning.imported, partitionPruning.importedColumns and partitionPruning.prunerImported. Don’t set or edit these keys.
Segments. As each data file becomes a segment, the segment’s metadata is stamped with that file’s partition id. The id comes from the spec the file was written with, not from the table config. A file with no supported partition field is left unstamped.
Query fan-out. For a filter like customer_id = 42, the broker computes the partition for 42 and routes the query only to segments stamped with that partition. Unstamped segments are always queried, so results stay correct. With bucket[16], a point lookup reads about one sixteenth of the stamped segments.
Requirements
- StarTree Cloud 0.18.0 or later on every broker and server. Older brokers and servers don’t know the
IcebergBucketandIcebergTruncatefunctions. - An Iceberg table read through a catalog. A table over raw S3 or GCS files has no partition spec to import.
- Sync runs on the controller in snapshot mode:
executor=controllerand noenabledkey in theExternalTableSyncTaskconfig. Tables created with SQL DDL getexecutor=controllerautomatically. - A Pinot column for each partition source column, with a matching type (see Limits). Tables onboarded from the catalog get the right types by default.
Enable or disable
The DDL option writes
enablePartitionPruning into the task config. Both control the same feature.
At create time
Add the option to the DDL:partition pruner are written when the table is created. Every segment from the first sync is stamped.
On an existing table
-
Get the table config:
-
Add
"enablePartitionPruning": "true"undertask.taskTypeConfigsMap.ExternalTableSyncTask. -
Put the config back:
Turn it off
SetenablePartitionPruning to "false" and put the config back. On the next sync run, the importer removes only what it added:
- Partition columns it wrote and you haven’t changed.
- The
partitionpruner, unless you still have your own partition columns. In that case it stays and becomes yours. - Its
partitionPruning.*keys.
Owning your own segmentPartitionConfig
The importer never changes partition config it didn’t write:
- If the table already has a
segmentPartitionConfigwhen you turn the feature on, the importer leaves the table alone. It adds no columns, doesn’t add thepartitionpruner and doesn’t stamp segments. - If you edit a column the importer wrote, that column becomes yours. The importer stops updating it, stops stamping it, and leaves it in place when you turn the feature off.
Example
An Iceberg table partitioned bybucket(16, customer_id) and day(order_ts), created in Spark SQL:
?dryRun=true to see the table config without creating the table.
The importer writes this partition config. Iceberg long maps to Pinot LONG, so customer_id qualifies. day(order_ts) has no Pinot partition function and is left out:
Query
Pruning applies when the filter compares the bare partition column with literals.
The broker parses each literal as the column’s type before it computes the partition. An integral value written with a decimal point, such as
customer_id = 42.0 on a LONG column, is read as 42. A literal that can’t equal any stored value, such as customer_id = 42.5, matches no rows, so skipping segments for it is safe.
For a bucketed date column, the literal is the Iceberg day count, such as event_date = 20454, because Pinot stores the column as days since epoch.
In the multi-stage engine, broker pruning is on by default. To turn it off for one query, use SET useBrokerPruning = false;. Partition pruning doesn’t set up colocated joins.
See pruning on a query
Compare these fields in the query response:numSegmentsQueried: segments the broker sent the query to.numSegmentsPrunedByBroker: segments the broker skipped.
Indexes
Pruning happens at the broker, before any index is used. The partition column needs no index, and indexes on other columns still apply to the segments that are queried.Verify
-
Check the table config:
Expect your partition columns under
columnPartitionMapandpartitionin the pruner list. -
Check one segment’s stamp:
Expect one partition id per partition column. A
bucketon adatecolumn, andidentityortruncateon adecimalcolumn, also list partition0on every segment, so older brokers stay correct during an upgrade. Segments synced before you turned the feature on have no stamp. -
Run a point query on a partition column. Expect
numSegmentsQueriedwell below the table’s segment count andnumSegmentsPrunedByBrokerabove zero.
Partition evolution
Each data file is stamped from the spec it was written with. Iceberg keeps every spec the table has used, so files under old and new specs coexist in one Pinot table. For example,bucket[4] and bucket[8] segments for the same column each prune correctly. The broker reads the function and partition count from each segment, not from the table config.
On every sync run, the importer reads the current spec:
- A new supported partition column is added to the table config and the pruner, as long as the importer owns the partition config.
- A column from an older spec stays in the config while it is still valid, so older segments keep pruning.
- A column that becomes invalid is removed from the config. That happens if it’s dropped from the Pinot schema, becomes multi-value, changes type, or becomes the output of an ingestion transform.
- If the catalog is unreachable, the importer keeps the last valid config and tries again on the next run.
Limits
-
Only
bucket,identityandtruncateprune.year,month,day,hourandvoidare skipped. -
Supported source types:
The Pinot type must match exactly. A bucket on an Iceberg
intneeds a PinotINT, not aLONG.time,timestamp,float,doubleandbooleansources never prune. - Partition fields on nested struct fields are skipped. Pinot stores the struct as one JSON column.
- Multi-value columns, and columns produced by an ingestion transform, are skipped.
-
If one column has two partition fields, such as
identity(x)andbucket[8](x), only one is kept. The later field in spec order wins and the controller logs a warning. -
identityandtruncatealways use 32 partitions. Columns with many distinct values still share 32 partition ids. - Ranges never prune by partition.
- Existing segments aren’t restamped when you turn the feature on.
-
Tables on the periodic watcher path (an
enabledkey in the task config) are not pruned. If such a table has config written by the importer, the watcher removes it. - Each skipped field only loses pruning. Results stay correct.

