Recommended path

Use this insight in three moves

Read the framing, connect it to implementation proof, then keep the weekly signal loop alive so this page turns into a longer relationship with the site.

01 · Current insight

Trino Dynamic Filtering Partition Pruning in Star Schema

Optimize Trino Dynamic Filtering to prevent table scans on multi-terabyte fact tables, reducing query latency and cut execution costs across lakehouses.

You are here

02 · Implementation proof

GCP Modern Data Stack

Use the matching case study to move from strategic framing into architecture and delivery tradeoffs.

See the proof

03 · Repeat value

Get the weekly signal pack

Stay connected to the next market shift and the next delivery pattern without needing to hunt for them manually.

Join the weekly loop
Trino Dynamic Filtering Partition Pruning in Star Schema
Query Optimization & Lakehouse Engine Tuning

Trino Dynamic Filtering Partition Pruning in Star Schema

Optimize Trino Dynamic Filtering to prevent table scans on multi-terabyte fact tables, reducing query latency and cut execution costs across lakehouses.

2026-08-04 • 8 min

ShareLinkedInX

Trino Dynamic Filtering Partition Pruning in Star Schema

Trino Dynamic Filtering failures during Monday reporting spiked query latencies from 800ms to 45s. A 15-terabyte transactional fact table was being scanned completely because runtime join predicates failed to pass partition boundaries down to the worker nodes before scanning storage buckets. When executive dashboards refresh simultaneously, unpruned scans flood worker memory, exhaust cluster threads, and trigger cascading worker OOM terminations.

In analytical storage environments, partitioning massive tables by date or region is standard architecture. However, traditional static partition pruning requires fixed literal filters in the WHERE clause. When queries join a large fact table against a selectively filtered dimension table, static analyzers cannot determine which partition keys will be needed until after the dimension side of the join finishes executing. Without dynamic pushdown runtime hints, the engine reverts to reading every parquet file registered in the catalog.

To resolve this operational risk, distributed engines use dynamic filtering to construct run-time bloom filters or value sets on the build side of a join and push them into the probe side scan operators. When implemented correctly across cloud lakehouses such as those built with our GCP Modern Data Stack, this mechanism cuts input I/O by orders of magnitude while preserving interactive response times.

Anatomy of a Distributed Join Memory Bottleneck

When a client issues a star schema join query, the Trino coordinator compiles an execution plan divided into logical stages. In a standard hash join, the build side processes the dimension table filters, constructs an in-memory hash table on worker nodes, and then waits for the probe side to stream rows from the fact table.

-- Executive Dashboard Aggregation Query
SELECT 
    d.region_name,
    d.category_code,
    SUM(f.net_amount) AS total_revenue
FROM lakehouse_gold.fact_store_sales f
JOIN lakehouse_gold.dim_stores d
  ON f.store_id = d.store_id
WHERE d.region_name = 'LATAM_NORTH'
  AND d.is_active = TRUE
GROUP BY 1, 2;

If the engine waits until the hash join operator executes to perform filtering, the table scan stage for fact_store_sales must read trillions of records across hundreds of S3 or GCS prefixes. This produces three primary system failure points:

  1. Network Saturation: Worker nodes pull gigabytes of irrelevant Parquet footers and row groups over remote object storage connections.
  2. CPU Starvation: Worker threads spend processing cycles deserializing Snappy or ZSTD compressed columns only to discard 98% of the rows at the join stage.
  3. Buffer Exhaustion: In-flight exchange buffers between the scan stage and join stage fill up, backpressuring the coordinator and forcing spill-to-disk operations.

When alerting models miss these operational signals, incident escalation escalates quickly, as documented in operational reviews like Airbnb Rebuilt Alert Development.

How Trino Dynamic Filtering Eliminates Full Fact Scans

Trino Dynamic Filtering coordinates runtime information exchange between execution stages. As the build side of a join stage processes records from dim_stores, Trino collects the matching join keys (store_id).

If the number of unique join keys remains within pre-allocated memory limits, the worker node generates a dynamic filter—either an explicit set of values or a compact Bloom filter structure. The coordinator broadcasts this filter payload to the running table scan operators responsible for reading fact_store_sales.

[ Build Stage: dim_stores ] 
       │
       ├─► Evaluates WHERE region_name = 'LATAM_NORTH'
       ├─► Collects matching store_id values
       └─► Generates Dynamic Bloom Filter / Value Set
                 │
                 ▼ (Coordinator Broadcast)
[ Probe Stage: fact_store_sales Scan ]
       │
       ├─► Evaluates Dynamic Filter against Partition Metastore
       ├─► Prunes non-matching Date/Region Partitions at Hive/Iceberg layer
       └─► Pushes Row-Group Min/Max Statistics down to Parquet Reader

When the scan operator receives the dynamic filter before or during file streaming, two levels of pruning take place:

  1. Partition-Level Pruning: If the join key correlates with partition keys (or if the underlying table format supports partition evolution and hidden partitioning), irrelevant partition directories are removed from the scan list dynamically.
  2. Row-Group Level Pruning: For non-partitioned key columns, the Parquet or ORC reader uses the bloom filter alongside column statistics (min/max values) inside metadata footers to skip entire file blocks without reading raw row data.

Configuring Dynamic Filter Pushdown and Wait Time Limits

By default, Trino enables dynamic filtering, but standard cluster settings are often too conservative for heavy enterprise star schemas. When fact tables are massive, the scan operator may start streaming data before the dynamic filter is collected, rendering the optimization useless.

To ensure scan operators wait for dynamic filters without deadlocking queries, tune these parameters in Trino's config.properties:

# Enable dynamic filtering support across distributed joins
enable-dynamic-filtering=true

# Set maximum dynamic filter size per build operator (in bytes/entries)
 dynamic-filtering.large-cache-size=1000000
 dynamic-filtering.max-distinct-values-per-driver=10000

# Maximum time a table scan operator waits for dynamic filters before proceeding
enable-large-dynamic-filters=true
dynamic-filtering.wait-timeout=15s

# Configure bloom filter parameters for high cardinality join keys
dynamic-filtering.bloom-filter.expected-insertions=200000
dynamic-filtering.bloom-filter.false-positive-probability=0.03

When dynamic-filtering.wait-timeout is set to 0ms, table scan operators do not wait for the build stage to generate dynamic filters. They instantly begin reading all storage prefixes. Setting a timeout between 5s and 15s forces the scan operator to pause briefly. In high-throughput workloads, waiting 2 seconds for a filter saves 40 seconds of object storage I/O.

Newsletter

Want the next signal before it hits your backlog?

One short weekly note: market pressure, delivery pattern, and a proof link you can reuse.

One email per week. No spam. Only high-signal content for decision-makers.

Code Implementation: Dynamic Partition Pruning Verification

To verify whether a query uses dynamic filtering effectively, engineers must analyze the output of EXPLAIN ANALYZE. The execution plan explicitly highlights dynamic filter generation and consumption across stage boundaries.

-- Run EXPLAIN ANALYZE on a production query candidate
EXPLAIN ANALYZE
SELECT 
    f.order_date,
    f.store_id,
    SUM(f.gross_amount) AS revenue
FROM lakehouse_gold.fact_orders f
JOIN lakehouse_gold.dim_promotions p
  ON f.promo_id = p.promo_id
WHERE p.campaign_name = 'BLACK_FRIDAY_2025'
GROUP BY 1, 2;

Inspect the output operator tree for the following diagnostic metrics:

Fragment 1 [HASH_BUILD]
    CPU: 1.2s, Input: 1,500 rows
    DynamicFilterSource [id=df_101, dynamic_filter_type=BLOOM_FILTER]
      │
      └─► ScanFilterProject[table = hive:lakehouse_gold:dim_promotions]

Fragment 2 [HASH_PROBE]
    CPU: 4.8s, Input: 45,200 rows (Pruned from 1,200,000,000 rows)
    ScanFilterProject[table = hive:lakehouse_gold:fact_orders, dynamic_filters={df_101: promo_id}]
    DynamicFilterProc: 1,199,954,800 rows blocked/skipped by [df_101]

If DynamicFilterProc shows zero rows skipped or indicates Dynamic Filter Timeout Exceeded, check the following root causes:

  1. Cardinality Spillover: The filter candidate exceeded max-distinct-values-per-driver and was downgraded to an unbounded scan.
  2. Cross-Join Fallback: The query planner chose a non-equijoin predicate, preventing deterministic key collection.
  3. Network Skew: Build stage workers experienced GC pauses, delaying filter transmission beyond the wait timeout.

Monitoring pipeline health across storage layers is critical when diagnosing these query failures. Integrating continuous tracking with our Data Observability Platform provides immediate visibility into scan volume anomalies caused by dropped dynamic filters.

Performance Benchmarks: Unoptimized vs Dynamic Filtered Execution

We benchmarked a 12-node Trino cluster querying a 12-terabyte Iceberg table on Google Cloud Storage. The query joined a fact table of 8.5 billion rows against a dimension table filtered down to 450 records.

Configuration StateExecution TimeData Read (GCS)Peak Memory Per WorkerQuery Status
Dynamic Filters Disabled52.4 seconds1.18 TB14.2 GBSuccess (High I/O)
Wait Timeout = 0ms (Default)38.1 seconds840 GB11.8 GBPartial Pruning
Tuned Wait Timeout (10s)2.1 seconds3.4 GB1.8 GBOptimal Pruning
High-Cardinality Exceeded56.8 seconds1.18 TB15.6 GBWorker OOM Warning
Bloom Filter Enabled (200k)2.4 seconds3.8 GB2.1 GBOptimal Pruning

The benchmark demonstrates that enforcing an explicit wait timeout of 10 seconds reduced scanned data volume by 99.7% and improved end-to-end execution speed by 25x.

Operational Guardrails for Production Lakehouse Clusters

To safely roll out dynamic filtering optimizations across shared enterprise clusters without causing memory pressure or query hangs, apply the following engineering guidelines:

  1. Align Partition Key Order: Ensure join keys frequently used in star schema dimension tables match or map cleanly to the partitioning or bucket keys of the target fact tables.
  2. Set Strict Memory Boundaries: Limit memory allocation for dynamic filter construction so that runaway dimension queries do not consume node heap needed by execution hash tables.
  3. Instrument Metric Alerts: Monitor Trino JMX metrics for trino.execution.executor:name=DynamicFilters to track filter generation delays, collection timeouts, and dropped filter rates.
  4. Leverage Modern Formats: Pair dynamic filtering with Apache Iceberg or Delta Lake formats. These table formats expose file-level min/max statistics directly to the Trino engine, accelerating row-group skipping.

Optimizing dynamic filtering transforms lakehouse performance, converting multi-minute batch-style scans into low-latency interactive execution paths while drastically lowering cloud infrastructure costs.

ShareLinkedInX

Topic cluster

Explore this theme across proof and live signals

Stay on the same topic while changing format: move from strategic framing into implementation proof or a fresh market signal that keeps the session moving.

Newsletter

Receive the next strategic signal before the market catches up.

Each weekly note connects one market shift, one execution pattern, and one practical proof you can study.

One email per week. No spam. Only high-signal content for decision-makers.