Gururaj Mahajan
07/15/2026, 3:08 PMIn our EXPLAIN ANALYZE,showsNonDefaultVariables. The system default is AUTO, so something is overriding it to NO_COMPRESSION for our queries. We are NOT explicitly setting this in our SET_VAR hints. Our hint is:transmission_compression_type: AUTO → NO_COMPRESSION
SET_VAR(cbo_cte_reuse_rate=1, enable_materialized_view_rewrite=false, connector_max_split_size=8388608, pipeline_dop=4, enable_connector_adaptive_io_tasks=false, enable_shared_scan=true)
Question: Which of these hint variables causesQ2 — Aggregation pushdown disabledto flip to NO_COMPRESSION? Is ittransmission_compression_type? And can we override it per-query withenable_shared_scan=truealongside our other hints? Would LZ4 compression help when EXCHANGE SHUFFLE is 30% of execution time and moving ~54GB allocated?SET_VAR(transmission_compression_type=LZ4_FRAME)
EXPLAIN ANALYZE showsandenable_rewrite_simple_agg_to_hdfs_scan: true → falsein NonDefaultVariables. The system default for both isenable_scan_predicate_expr_reuse: true → false, but our queries run with themtrue. We are not explicitly setting these. Our queries usefalseandenable_connector_adaptive_io_tasks=false.enable_shared_scan=true
Question: Which of our SET_VAR settings is disablingQ3 — ScheduleTime 99.8%+ with EXCHANGE SHUFFLE bottleneck? If we restore it toenable_rewrite_simple_agg_to_hdfs_scan, will StarRocks push SUM/COUNT aggregations into the Iceberg Parquet scan (reducing rows flowing to EXCHANGE)? For our queries that aggregate 22-25M rows from Iceberg down to ~100K output rows, this seems like it would be a major win.true
For our all-brands queries, EXPLAIN shows:
• `ScheduleTime: 99.82%`of ExecutionTime
• `Network: 159%`of ExecutionTime (multiple BEs' network time overlapping)
• EXCHANGE SHUFFLE at 32% of total time (2.6s out of 9s execution)
The ScheduleTime pattern suggests pipeline backpressure — downstream operators wait for EXCHANGE data. We're at`pipeline_dop=4`.
Question: For Iceberg queries where EXCHANGE SHUFFLE dominates ScheduleTime, is increasing pipeline_dop helpful (more sender/receiver threads) or is there another mechanism — e.g.,Q4 — NEST_LOOP_JOIN (CROSS JOIN) with 2-row build side,exchange_node_data_chunk_size, or`transmission_encode_level`— that reduces EXCHANGE stall time? Our previous tests showed DOP=8 was neutral for all-brands but that was with OLAP MVs; now with Iceberg the bottleneck is different.exchg_node_buffer_size_bytes
In, the plan has:latest_final_all_brands_product
NEST_LOOP_JOIN (id=73) [CROSS JOIN, NONE]
<BUILD> total_combined_cte — 1 row (scalar aggregation result)
<PROBE> combined_data — 301K rows
This is a 301K × 1 cross-join, which produces 301K rows. The NEST_LOOP_JOIN takes 520µs which is fine. But we usepattern intentionally to broadcast scalar totals.INNER JOIN total_combined_cte tcc ON 1=1
Question: Is there a more efficient pattern thanQ5 — Optimalcross-join for broadcasting a single-row scalar CTE? For example, wouldON 1=1explicitly force BROADCAST and avoid any chance of NEST_LOOP? And does StarRocks ever convert a 1-row CROSS JOIN to a constant-fold optimization (where the single row's values are treated as literals in the child scan)?CROSS JOIN [BROADCAST] total_combined_cte
connector_max_split_size for Parquet on S3
We're using(8MB splits) for Iceberg tables on S3. Our Parquet files target 128MB–256MB. So each file generates ~16-32 splits. Our cluster has 3 BEs withconnector_max_split_size=8388608= 12 total parallel reader threads.pipeline_dop=4
Question: With 3 BEs × DOP=4 = 12 parallel readers and ~16-32 splits per 128MB file, what's the optimal? Is 8MB optimal, or should we match to file size / (BEs × DOP) = 128MB / 12 ≈ 10MB? Does the split size affect S3connector_max_split_sizerequest sizes, or does StarRocks always read full Parquet row groups regardless of split boundary?GetObject
Rocky
07/15/2026, 3:08 PMEXPLAIN ANALYZE and system settings, here is the breakdown of the behaviors you are seeing and how to optimize them.
Q1 — Network Compression (AUTO → NO_COMPRESSION)
* The Cause: The shift from AUTO to NO_COMPRESSION is likely triggered by the rpc_compress_ratio_threshold (default 1.1). When set to AUTO, StarRocks samples the data; if the compression ratio (uncompressed_size / compressed_size) is less than 1.1, it disables compression to save CPU. For 54GB of data, if it's already semi-compressed (e.g., Parquet data being moved after decompression in the scan), the sampler might decide the CPU cost isn't worth the small bandwidth gain.
* The Shared Scan Link: While enable_shared_scan doesn't directly toggle compression, it changes the pipeline structure. In some versions, the FE reports NO_COMPRESSION if the data volume per "chunk" in the pipeline is too small for the adaptive compressor to kick in.
* Recommendation: Yes, you should override this. Set SET_VAR(transmission_compression_type=LZ4) (or LZ4_FRAME). For a shuffle that accounts for 30% of execution time and moves 54GB, forcing LZ4 is a major win. It will significantly reduce the bytes on the wire at a very low CPU cost.
Q2 — Aggregation Pushdown Disabled
* The Culprit: enable_shared_scan=true is disabling your aggregation pushdown.
* Why: Aggregation pushdown (e.g., enable_rewrite_simple_agg_to_hdfs_scan) modifies the scanner to perform `SUM`/`COUNT` inside the Parquet/Iceberg reader. However, enable_shared_scan is designed to let multiple downstream operators share the same data stream from one scanner. If one operator wants a SUM(x) and another wants raw rows, StarRocks cannot push the aggregation into the scan.
* The Win: For your case (22M rows → 100K rows), pushing the aggregation is far more valuable than sharing the scan. Sharing a scan saves S3 I/O, but pushing the aggregation saves the massive cost of shuffling 22M rows.
* Action: Try setting enable_shared_scan=false and ensuring enable_rewrite_simple_agg_to_hdfs_scan=true. This should push the `SUM`/`COUNT` into the Iceberg scan.
Q3 — ScheduleTime 99.8% & Exchange Bottlenecks
* Diagnosis: A ScheduleTime of 99%+ combined with high NetworkTime indicates Pipeline Backpressure. The execution threads are "Ready" but blocked waiting for buffers. The receiver is likely full, or the sender is waiting for network ACKs.
* Tuning Variables:
* exchg_node_buffer_size_bytes: The default is often too low for large shuffles (10MB–64MB). Increase this to 134217728 (128MB) or 268435456 (256MB) to absorb network bursts.
* pipeline_dop: Increasing DOP to 8 might help if you have spare CPU, but if the bottleneck is the 54GB network move, more threads may just increase contention.
* max_transmit_batched_bytes: Increase this (e.g., to 1MB or 2MB) to batch more data into fewer RPC calls, reducing the overhead of the network stack.
Q4 — NEST_LOOP_JOIN (1-row Cross Join)
* Efficiency: StarRocks is already handling this efficiently. Even though it's a NEST_LOOP_JOIN, since the build side is 1 row, it is effectively a Broadcast and the cost is negligible (as seen in your 520µs result).
* Alternative: You can use CROSS JOIN [BROADCAST] total_combined_cte to be explicit, but the optimizer already defaults to this for 1-row results.
* Constant Folding: StarRocks does not typically "constant-fold" a join into the scanner (i.e., treating the 1-row total as a literal in the scan's filter) because the join happens after the scan. However, for a 1-row CTE, the memory overhead is non-existent.
Q5 — Optimal connector_max_split_size for S3
* The 8MB Problem: 8MB is likely too small for S3 Parquet. Every split results in at least one S3 GetObject request (plus metadata calls). With 8MB splits on a 128MB file, you are making 16 requests.Rocky
07/15/2026, 3:08 PMconnector_max_split_size=67108864 (64MB) or even 128MB.
* Calculation: With 3 BEs and DOP=4, you have 12 slots. If your query hits multiple files, 64MB-128MB splits will still provide plenty of work to keep those 12 threads busy without the overhead of tiny S3 requests.
References
• administration/management/BE_configuration.md
• unloading/Spark_connector.md
• administration/management/FE_configuration.mdGururaj Mahajan
07/15/2026, 6:23 PMOur query'sQ2 — Why 155 GB allocated memory / ScheduleTime 99.96%:CTE (ICEBERG_SCAN id=53, 84.87M rows) feeds into a FULL OUTER JOIN (id=58fact_insights_market_product_seller_daily_poc) as the PROBE side. This scan has zero runtime filter. The other large scanBUCKET_SHUFFLE(S)(ICEBERG_SCAN id=20, 106M rows) feeds into BROADCAST INNER JOINs and gets a 42.77% runtime filter. Is it correct that runtime filters cannot be pushed to the probe side of a FULL OUTER JOIN in StarRocks? If so, what query restructuring would allow a runtime filter on fact_seller_daily — e.g., could we change the FULL OUTER JOIN to a LEFT OUTER JOIN + UNION approach, or add an explicit predicate to the CTE that allows filter pushdown?fact_insights_market_product_daily_poc
Our queryQ3 — Date partitioning to skip non-queried years:,QueryAllocatedMemoryUsage: 155 GB(severe pipeline back-pressure). The two large fact scans output 84.87M + 106.35M rows into aggregation nodes. What is causing 155 GB allocation — is this the hash aggregation table for GROUP BYScheduleTime: 99.96%over 190M rows? Our cluster has 3 BE nodes. Would Iceberg date partitioning on these two fact tables (to skip non-queried years) meaningfully reduce both the scan rows and the memory allocation?market_product_id
Both fact tables scan all historical data (84.87M + 106.35M rows) even though our query has explicit date range predicatesQ4 —. We use Spark to write these Iceberg tables (no Hive partitioning). If we add Iceberg partitioningdate BETWEEN '2025-01-01' AND '2025-12-31' OR date BETWEEN '2024-01-01' AND '2024-12-31'to both fact tables, will StarRocks 3.3 use partition pruning to skip all data outside 2024-2025? What's the recommended partition spec for this use case —PARTITIONED BY (years(date)),years(date), ormonths(date)— to minimize partition overhead while maximizing pruning?identity(year)
enable_scan_predicate_expr_reuse: true → false cluster override:
Our EXPLAIN showsQ5 — pipeline_dop strategy for memory vs throughput:as a cluster-level NonDefaultVariable. Our query uses IF(date BETWEEN X AND Y, ..., 0) as many as 30+ times per fact scan — bothenable_scan_predicate_expr_reuse: true -> falseand2025-01-01 AND '2025-12-31'date ranges appear in every metric. What does2024-01-01 AND '2024-12-31'do exactly, and would enabling it via SET_VAR help avoid re-evaluating the same date BETWEEN predicates 30+ times per row? Should we tryenable_scan_predicate_expr_reusein our SET_VAR hint?enable_scan_predicate_expr_reuse=true
With(from SET_VAR hint),pipeline_dop=4, and 155 GB memory across 3 BEs (~52 GB/BE), we're in severe back-pressure. We also haveScheduleTime=99.96%in the session-level SET (but the hint overrides to 4). Would increasing pipeline_dop to 8 or 16 help spread the aggregation state across more threads and reduce per-thread memory peaks? Or would higher DOP worsen the situation by creating more concurrent scans that compete for memory?pipeline_dop=16
Rocky
07/15/2026, 6:24 PMEXPLAIN ANALYZE metrics and your current query patterns, here is a breakdown of the performance bottlenecks and recommended optimizations for your StarRocks 3.3 environment.
Q1 — FULL OUTER JOIN Runtime Filter Limitation
* The Limitation: It is correct that runtime filters cannot be pushed to the probe side of a FULL OUTER JOIN. By definition, a FULL OUTER JOIN must return rows from the probe side even if they do not match the build side. If StarRocks applied a runtime filter at the scan level to prune probe-side rows that don't match the build side, those "unmatched" rows would be lost, leading to incorrect results.
* Restructuring: To get filter pushdown, you can restructure the query using a UNION ALL approach:
1. Perform a LEFT JOIN (Fact A to Fact B) where Fact A is the probe side (runtime filters can be pushed here if B is the build side).
2. UNION ALL with a RIGHT ANTI JOIN (Fact A to Fact B) to get the rows only in Fact B.
Alternatively, if you can determine that one table always contains the superset of keys or if you only need matches, switching to a LEFT JOIN or INNER JOIN will immediately enable runtime filters on the probe side.
Q2 — Memory Allocation & ScheduleTime Backpressure
* The Cause: The 155 GB allocation is almost certainly the Hash Table used for the GROUP BY market_product_id aggregation. When you aggregate ~190M rows (84M + 106M) into a single aggregation node, StarRocks builds a massive in-memory hash table to track every unique market_product_id.
* The Backpressure: ScheduleTime: 99.96% confirms your BE threads are spending all their time waiting for memory or for the aggregation to finish processing the massive volume of rows.
* The Win: Partitioning is the most effective way to solve this. Reducing the 190M rows at the source (via pruning) directly reduces the size of the hash table and the amount of data moving through the pipeline.
Q3 — Iceberg Date Partitioning Strategy
* Recommendation: Use PARTITIONED BY (years(date)) (Iceberg Hidden Partitioning).
* StarRocks 3.3 Support: StarRocks 3.3 fully supports Iceberg's hidden partitions. When you use a predicate like date BETWEEN '2025-01-01' AND '2025-12-31', StarRocks will recognize that this covers exactly the 2025 year partition and skip all other files.
* Granularity: years(date) is optimal for your query pattern. months(date) would provide even finer pruning but creates more metadata overhead. Since your queries target full years, years(date) strikes the best balance.
Q4 — enable_scan_predicate_expr_reuse
* Definition: This variable controls whether the scanner reuses the results of complex expressions that appear multiple times in the query.
* Your Case: Since you have 30+ metrics using the same IF(date BETWEEN X AND Y, ...) logic, StarRocks is likely re-evaluating that date range check for every single column per row.
* Action: You should set enable_scan_predicate_expr_reuse=true. This allows the scanner to evaluate the date BETWEEN predicate once and reuse the boolean result for all 30+ metrics in that row, significantly reducing CPU cycles at the scan stage.
Q5 — pipeline_dop Strategy: Memory vs. Throughput
* DOP and Memory: Increasing pipeline_dop (e.g., to 16) will increase memory pressure. Each pipeline instance maintains its own buffers and partial aggregation hash tables. With 155 GB already allocated, a higher DOP may cause your BEs to hit their mem_limit and trigger a query failure or heavy spilling.
* Recommendation: Keep pipeline_dop at 4 or 8 until you implement partitioning.
* If you implement partitioning and the row count drops, you can increase DOP to 16 to maximize throughput.Rocky
07/15/2026, 6:24 PMenable_spill=true to allow the aggregation to offload some of that 155 GB to disk, which will reduce the ScheduleTime backpressure at the cost of some I/O.
Summary of Recommended Hints
sql
SELECT /*+ SET_VAR(
enable_scan_predicate_expr_reuse=true,
transmission_compression_type='LZ4',
pipeline_dop=4,
enable_shared_scan=false,
enable_rewrite_simple_agg_to_hdfs_scan=true
) */ ...
References
• sql-reference/System_variable.md
• faq/resource_isolation_faq.md
• knowledge/usage/best-practice.md
• using_starrocks/caching/query_cache.md
• faq/Sql_faq.md