Skip to main content
This feature requires StarTree Cloud 0.18.0 or later. It is opt-in and off by default.
Partition pruning imports the partition spec of an Iceberg table into the Pinot External Table. Each segment is stamped with the partition of the data file it reads. The broker then skips every segment whose partition can’t match the query’s filter. It works with Iceberg hidden partitioning. You filter on the source column, such as 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 to tableIndexConfig.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 IcebergBucket and IcebergTruncate functions.
  • 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=controller and no enabled key in the ExternalTableSyncTask config. Tables created with SQL DDL get executor=controller automatically.
  • 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:
The partition config and the partition pruner are written when the table is created. Every segment from the first sync is stamped.

On an existing table

  1. Get the table config:
  2. Add "enablePartitionPruning": "true" under task.taskTypeConfigsMap.ExternalTableSyncTask.
  3. Put the config back:
The next sync run imports the spec and turns on the pruner, even if there are no new files. Segments that already exist are not restamped. They have no partition id, so the broker keeps querying them. Only segments created from files synced after the change are pruned. To prune the whole table, create it with the option on.

Turn it off

Set enablePartitionPruning 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 partition pruner, unless you still have your own partition columns. In that case it stays and becomes yours.
  • Its partitionPruning.* keys.
Segment stamps stay in segment metadata but have no effect without the pruner.

Owning your own segmentPartitionConfig

The importer never changes partition config it didn’t write:
  • If the table already has a segmentPartitionConfig when you turn the feature on, the importer leaves the table alone. It adds no columns, doesn’t add the partition pruner 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 by bucket(16, customer_id) and day(order_ts), created in Spark SQL:
Create the External Table through the controller:
Add ?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:
The controller log names every field that was left out:
Each segment’s metadata carries its own partition:

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.
Run the same query with a filter on a non-partition column. The difference is what pruning saves.

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

  1. Check the table config:
    Expect your partition columns under columnPartitionMap and partition in the pruner list.
  2. Check one segment’s stamp:
    Expect one partition id per partition column. A bucket on a date column, and identity or truncate on a decimal column, also list partition 0 on every segment, so older brokers stay correct during an upgrade. Segments synced before you turned the feature on have no stamp.
  3. Run a point query on a partition column. Expect numSegmentsQueried well below the table’s segment count and numSegmentsPrunedByBroker above 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.
A source column renamed in Iceberg still prunes. Pinot keeps the original column name and resolves the new name as an alias.

Limits

  • Only bucket, identity and truncate prune. year, month, day, hour and void are skipped.
  • Supported source types: The Pinot type must match exactly. A bucket on an Iceberg int needs a Pinot INT, not a LONG. time, timestamp, float, double and boolean sources 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) and bucket[8](x), only one is kept. The later field in spec order wins and the controller logs a warning.
  • identity and truncate always 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 enabled key 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.

Common errors and symptoms