What changes
- Which files get ingested. At each sync, every data file in the Iceberg snapshot is dated. A file is skipped only when its metadata proves that all of its data is older than the cutoff. Every other file is ingested as before.
- Whole files, not rows. The filter decides per file. A file that spans the cutoff is kept whole, older rows included. It is not an exact row-level time predicate.
- The cutoff. For
Nd, the cutoff is midnight UTC today minus N days. ForNh, it is the start of the current UTC hour minus N hours. So30dkeeps today plus the 30 previous full UTC days. - How the window rolls. When the UTC day (or hour) cutoff moves forward, the next sync run re-evaluates the table, even if nothing new was committed to Iceberg. Files that have aged out are dropped from the table at that run.
- Snapshot and row counts. The published snapshot lists only the kept files, so
COUNT(*)reflects the window, not the full Iceberg table. If every file in a snapshot is outside the window, the table serves zero rows for it. - Files that age out. They disappear from query results at the next sync run. The Pinot segments behind them are not deleted right away: they are cleaned up later by snapshot retention, like segments of files removed from Iceberg. Keep snapshot retention enabled. The filter never deletes or changes your Iceberg data files.
- Queries over old dates. A query for dates before the cutoff returns no rows, except rows from files the filter had to keep (see Limits and what gets kept).
Requirements
- StarTree Cloud 0.18.0 or later.
- An Iceberg REST catalog:
catalogType=iceberg-rest. Raw S3 or GCS listings and native Hive tables are rejected. - The snapshot sync path:
executor=controller, with noenabledkey in theExternalTableSyncTaskconfig. enableSnapshotConsistency=true. This is the default; don’t set it tofalse. It lets brokers hide the files the filter excludes.- Every broker, server and controller runs StarTree Cloud 0.18.0 or later. Until the last one is upgraded, sync runs on a filtered table fail. See Limits and what gets kept.
- To filter by a column, that column must exist in the table’s Pinot schema.
Enable or disable
Add the keyiceberg.snapshotProcessing.fileFilter to task.taskTypeConfigsMap.ExternalTableSyncTask. The value is a JSON list with exactly one predicate, written as a string.
Any other field is rejected, so a typo can’t silently widen the filter.
How files are dated
The filter picks one of three dating modes.
Notes on each mode:
- Time partition. Only a time partition on the Pinot time column counts. A time partition on another column, such as
month(birth_date), is ignored because it may not date the rows. A file the partition can’t decide (null partition value, or a partition that spans the cutoff) falls back to commit time. A backfill or compaction written into an old partition is dated by the partition, so it is excluded. - Commit time. A file committed long ago but rewritten recently (compaction, import, migration) looks new and is kept. If the snapshot that added a file has expired, the file can only be bounded by a later retained snapshot. If nothing bounds it, it is kept. Tables onboarded after their history expired, and Iceberg format v1 tables, should use a time partition or
columninstead. - Column. A
DATETIMEfield or aTIMESTAMPfield needs noformat. Any other column (a stringdspartition, an epoch number) needsformat. Aformatyou set wins over a DateTime field’s own format. String patterns must sort chronologically:yyyy-MM-dd,yyyyMMddoryyyy-MM-dd-HH. Atz(...)zone in the format is honored; otherwise values are read as UTC. The column is matched by its Iceberg field id when the Pinot schema has one, so renaming the column in Iceberg keeps the filter working.
Set it with SQL DDL
CREATE TABLE ... WITH (...) passes unrecognized options through to the ExternalTableSyncTask config, so you can set the filter at creation time (see SQL DDL):
Change the window
Edit the value and update the table config. The change applies when the current pass finishes: the next run lists the snapshot again with the new window. A wider window brings back files that are now inside it. A narrower one hides files that fall out.Remove the filter
Delete the key and update the table config. The next run lists the snapshot again with no filter, and files that were excluded come back into query results. No new Iceberg commit is needed. Expect that run to ingest every file that was previously skipped.Example
Day-partitioned table, last 30 days
The Iceberg table is partitioned byday(event_ts), and event_ts is the Pinot time column. Leave out column: files are dated by their day partition.
fileFilter line is new.
String ds column with a format
The Iceberg table is partitioned by a string ds column (2026-11-04), kept as a dimension in the Pinot schema. Name the column and give its format. Keep the last 60 days:
ds value, not by commit history. With no column, the same table would fall back to commit time and keep the old files.
Other valid values:
Query
Queries don’t change. The table simply holds less data. To confirm the window:Indexes
Nothing specific to this feature. Indexes are built on the files that are ingested, as usual.Verify
-
Check the sync checkpoint:
Expect
"state": "DONE". If it showsFAILED, readerrorMessageandconsecutiveFailures. -
Check the controller log. Each listing batch logs a line like:
Each run also logs the dating mode, for example
dating files by the time partition on time column 'event_ts',no time partition on time column '...', orthe table has no Pinot time column. -
Run the
MIN/MAXand count-by-day queries above. Expect the oldest day at or after the cutoff, plus any older rows from kept files. -
The controller meter
EXTERNAL_TABLE_SYNC_FILES_SKIPPED_BY_FILTERcounts skipped files per table.
Limits and what gets kept
- Whole-file granularity. A file is dropped only when its metadata proves it is older than the window. A file that spans the cutoff is kept whole.
- Undatable files are kept. If the filter can’t date a file, it keeps it. With commit time, a file whose adding snapshot has expired and isn’t bounded by a later retained snapshot can’t be dated.
- Nulls are kept. In column mode, a file with any null value in the column is kept. In partition mode, a file with a null partition value falls back to commit time.
- The filter never blocks a sync. If it can’t apply to a snapshot (the column is missing from the Iceberg schema, the type is unsupported, a value can’t be parsed), that snapshot is listed unfiltered and the controller logs a warning starting
File filter ... cannot apply to snapshot ...; every file is kept. Save-time checks see only the Pinot schema, so a format that fits the Pinot type but not the Iceberg type (a PinotTIMESTAMPover an Iceberglong) saves and then hits this case. - Commit-time clock skew. Commit-time dating allows 1 hour of clock difference between Iceberg writers. Writers whose clocks differ by more can cause a file to be wrongly excluded when its adding snapshot has expired.
- Cost. Partition and commit-time modes cost about the same as no filter. Column mode reads per-file column statistics from manifests, which uses more controller CPU and memory on wide tables.
- Segment groups. Supported. Each filtered run regroups the kept files. Prefer time-bucketed groups so the window drops whole groups. Every controller must be upgraded before you save a filter on a segment-group table.
- Rolling upgrade. Sync runs on a filtered table fail until every broker, server and controller runs StarTree Cloud 0.18.0. After
iceberg.snapshotProcessing.maxRetriesfailed runs in a row (default3), the snapshot goesFAILED. It moves forward again when a new Iceberg snapshot is committed, or when you seticeberg.snapshotProcessing.retryFailedSnapshot=true(defaultfalse), which retries the failed snapshot on each sync run. To avoid the failures, add the filter after the upgrade finishes. - Downgrade. Removing the filter doesn’t make a downgrade safe. Once a filtered table has synced, keep brokers, servers and controllers on StarTree Cloud 0.18.0 or later.
Common errors
Save-time errors come back from the table create or update call as:forceConfig doesn’t bypass them.

