Back to blog

Slow Trino Queries on Iceberg? 7 Fixes for Faster Trino Iceberg Performance

Slow Trino queries on Apache Iceberg are rarely a compute problem — they're a table layout problem. Unmaintained Iceberg tables turn sub-second Trino scans into minute-long full reads. This guide covers seven proven fixes for faster Trino Iceberg performance: compaction, query-aware sorting, partition strategy, manifest optimization, lifecycle cleanup, multi-engine routing, and continuous observability. Why Trino-native maintenance falls short — and how to automate each fix at scale.

Chris P
Apache Icebergslow Trino queries IcebergTrino Iceberg performancefaster Trino Icebergfix slow Trino queriesTrino query optimization IcebergTrino Iceberg slow

Chris P

25 min read
Faster Trino with Iceberg — Trino rabbit mascot with a speed gauge and layered Iceberg data blocks accelerating query performance

Slow Trino queries on Iceberg are almost never a compute problem. Trino is one of the fastest engines for interactive SQL on Apache Iceberg — its stateless, distributed architecture pushes predicate evaluation into the scan planning phase. Partition pruning, file-level min/max filtering, and row group skipping work together to reduce I/O by orders of magnitude. When tables are well-maintained, Trino Iceberg performance is excellent.

In practice, most production Iceberg tables are not in a state that lets Trino's optimizer do its job. The coordinator calls planFiles() to deserialize every manifest entry into memory — file paths, partition tuples, column statistics. On a table with 10,000 manifests averaging 500 file entries each, the coordinator must hold metadata for 5 million files simultaneously. The TableStatisticsReader processes statistics in a single-threaded path, so 5–10% of queries on large tables spend 1–10 minutes in planning alone. Meanwhile, small files from streaming ingestion force thousands of tiny splits, unsorted data renders file-level statistics useless (every file's min/max spans the entire domain), and fragmented manifests turn planning into a cascade of S3 GET requests. The query hasn't read a single row of data, and it's already slow.

The result: dashboards that used to refresh in 2 seconds now take 90. Analysts complain. Data engineers firefight. The temptation is to throw more Trino workers at the problem — but adding compute doesn't help when the root cause is the physical layout and metadata state of the Iceberg tables underneath. The path to faster Trino Iceberg performance is fixing the tables, not scaling the cluster.

Trino provides basic maintenance procedures (OPTIMIZE, optimize_manifests, expire_snapshots), but these are manual, single-table operations that run inside Trino itself — consuming the same coordinator and worker resources your queries need. They provide no sort-based compaction, no cross-table policies, no scheduling, no sequencing, and no observability into what changed or why. Spark-based alternatives (via rewriteDataFiles) add sort support but cost ~$50/TB on dedicated clusters. Starburst's scheduled maintenance adds automation for Starburst-managed environments but cannot see query patterns from other engines querying the same tables. AWS S3 Tables offers managed compaction for tables in S3 table buckets, but only for tables created there — it cannot optimize existing tables in Glue catalogs, Polaris, Nessie, or Gravitino. At scale — hundreds of tables, multiple engines, continuous ingestion — manual approaches and single-engine tools break down.

This guide covers seven concrete strategies to fix slow Trino queries on Iceberg — ordered by impact, from fastest wins to architectural improvements. Each one addresses a specific bottleneck in how Trino plans and executes scans, explains why Trino-native tools cannot solve it alone, and shows how LakeOps automates the fix across your entire lake for dramatically faster Trino Iceberg query performance. For a comprehensive treatment of Trino's Iceberg internals, see the Trino Iceberg Optimization guide. For the definitive comparison of compaction engines, see 7 Compaction Engines for Iceberg.

1. Compact small files into optimally-sized data files

The problem. Streaming pipelines (Kafka, Flink, Spark Structured Streaming) commit data to Iceberg every few seconds or minutes. Each commit creates new data files — often 1–10 MB each. A table receiving 100 commits per hour accumulates 2,400 new small files per day. After a month: 72,000 files in a single partition.

Trino must schedule a split for each file. Each split requires an S3 GET request, metadata parsing, and coordinator overhead. At 72,000 files, the coordinator's planFiles() call alone takes multiple seconds. The workers saturate S3 prefix-level rate limits (3,500 GET requests per second per prefix). The query is I/O-bound not because there's too much data, but because there are too many tiny files. Worse, each file carries fixed overhead in the manifest — column statistics, partition tuples, file paths — so the metadata volume grows linearly with file count even when data volume stays constant.

The fix. Compaction merges small files into target-sized outputs (256–512 MB for analytical workloads). Fewer files means fewer splits, fewer S3 GETs, and faster planning. A table with 42,633 files compacted to 69 is not unusual in production.

Trino provides a native OPTIMIZE command:

sql
1ALTER TABLE lakehouse.events EXECUTE optimize2WHERE event_date >= CURRENT_DATE - INTERVAL '7' DAY;

This merges files below 100 MB (configurable via file_size_threshold). The limitations are significant:

  • Resource contention. OPTIMIZE runs as a Trino query, consuming the same coordinator and workers your analytics workload needs. Compaction competes with dashboards for cluster capacity.
  • Binpack only. Trino's OPTIMIZE does not support sort-based compaction — Strategy 2 is impossible with Trino alone. The Trino Iceberg roadmap tracks cross-file global sorting, Z-order support, and full-partition rewrites as open work items.
  • Single writer per partition. Trino's compaction cannot parallelize within a partition — one writer per partition, so partitions with many files become a serial bottleneck.
  • Concurrent write conflicts. If a streaming pipeline writes to the same partition during compaction, Trino's OPTIMIZE fails with a commit conflict. There is no built-in retry — the engineer must re-run manually.
  • No automation. Cannot be scheduled, policy-driven, or applied across hundreds of tables without external orchestration.

Spark-based rewriteDataFiles adds sort support and per-partition parallelism, but requires provisioning dedicated clusters at ~$50/TB — expensive for continuous maintenance. It also runs on the JVM, so large tables trigger GC pauses and occasionally OOM the driver.

How LakeOps delivers it. A purpose-built Rust compaction engine built on Apache DataFusion — 95% faster than Spark at approximately 10% of the cost per TB. Runs completely outside Trino, so compaction never competes with queries for cluster resources. Supports both binpack and sort strategies with full per-partition parallelism and no JVM overhead. Uses Iceberg's optimistic concurrency control with automatic retry on conflict — safe for tables with concurrent streaming writers. Executes autonomously on configurable cron schedules, or via Adaptive Maintenance that monitors table activity signals (file count growth, average file size drift, delete-file accumulation) and fires operations only when structural degradation is detected.

Production result: raw_clickstream — 42,633 files compacted to 69 in 2m 18s. customer_orders — 1.24 TB, 16→1 files in 4 seconds. balance_snapshots — 1,192 GB at 1,572 MB/s throughput (Spark OOM'd on the same hardware). Benchmark across 5.5 TB / 10 tables: 101K→19K files (81% reduction), peak throughput 2,522 MB/s, compaction cost 10% of Spark on identical infrastructure.

LakeOps Optimization tab
Per-table compaction — target file size 512 MB, binpack vs. sort strategy, scheduled at 02:00 AM. Snapshot retention with configurable periods. Each table's maintenance is governed by inherited policies or local overrides.

2. Sort data by actual query filter columns

The problem. Trino's most powerful optimization after partition pruning is min/max file pruning — checking each file's column statistics stored in Iceberg manifests to skip files whose value range doesn't overlap with the query predicate. But this only works when file-level value ranges are tight and non-overlapping.

If a transactions table has 10,000 files written in arrival order, every file's account_id range spans the entire domain (min=1, max=10,000,000). A query filtering WHERE account_id = 42 cannot skip any file — the min/max range of every file includes 42. Trino reads all 10,000 files. Full scan. The same problem cascades into Parquet row group statistics: within each file, row groups ordered by arrival time have wide value ranges, disabling the third level of data skipping.

After sorting the same table by account_id, each file covers a narrow contiguous range (e.g., file 1: accounts 1–1,000, file 2: accounts 1,001–2,000). The same query now reads 1–3 files instead of 10,000. A 3,000x reduction in I/O from sorting alone — no partitioning change required. Within each surviving file, row group statistics also become tight, enabling sub-file skipping for a further 10–50x scan reduction on wide tables.

The fix. Sort compaction rewrites data files in an order that matches your most common query predicates. Three layout strategies apply depending on access pattern diversity:

  • Linear sort — single or multi-column sort. Optimal when one column dominates 80%+ of filters (e.g., event_timestamp on time-series tables). Each file covers a narrow range of the dominant column.
  • Z-order — multi-dimensional clustering that interleaves bit representations of 2–4 columns. Optimal when multiple columns each appear in 30–40% of queries. Benchmarks show ~46% file skip rate vs. 0% without sorting.
  • Hilbert curve — an alternative space-filling curve that avoids Z-order's quadrant-boundary discontinuities. ~57% file skip rate on the same datasets. Available via the LakeOps Rust engine for tables that justify the additional compute cost.

The challenge is knowing which strategy fits each table. If you sort by account_id but queries mostly filter on created_at, you've optimized for the wrong pattern. And patterns change — new dashboards, new reports, new AI agents start querying tables in ways nobody anticipated.

This is where single-engine approaches fundamentally fail. Trino's OPTIMIZE does not support sort at all — it rewrites files in binpack mode only. Setting the sorted_by table property ensures new writes land sorted, but it cannot re-sort existing files. Spark's rewriteDataFiles supports linear sort and Z-order, but requires you to specify the sort columns manually — and Spark only sees Spark workloads. If Trino queries dominate a table's access but Spark handles compaction, the sort order is informed by guesswork rather than actual query patterns. Neither engine supports Hilbert curves or adaptive re-evaluation of sort strategy over time.

How [LakeOps](https://lakeops.dev) delivers it. Cross-engine telemetry captures every query across Trino, Spark, Snowflake, Athena, DuckDB, and Flink — tracking which columns appear in WHERE, JOIN, and GROUP BY clauses across all engines simultaneously. The field access frequency analysis shows exactly which columns dominate real-world filtering on each table. The platform evaluates linear sort, multi-column sort, Z-order, and Hilbert curve strategies independently per table and selects the approach that maximizes data skipping for the actual query mix — not a global default. For a deep dive into all compaction strategies, see the dedicated guide.

When access patterns shift (a new dashboard filters on region instead of account_id), the sort order adapts automatically on the next compaction cycle. Layout Simulations run proposed sort orders on isolated Iceberg branches — real data, real metadata, actual execution times — before committing to production. The Layout Customization Diff compares multiple strategies side-by-side with projected file sizes and data-relationship analysis. Result: Trino's file pruning works at maximum effectiveness on every table, continuously — without manual EXPLAIN analysis or guesswork. Up to 12x query acceleration from sort-order optimization alone.

LakeOps Layout Simulations
Layout Simulations — field access frequency chart (SELECT, FILTER, JOIN per column from production queries), real SQL patterns with query counts, and Layout Customization Diff comparing strategies. Test sort orders on isolated Iceberg branches before applying to production.

3. Fix your partition strategy

The problem. Partition pruning is Trino's highest-impact optimization — when a query filters on a partition column, entire groups of files are eliminated at the manifest-list level before any per-file statistics are checked. But two failure modes are common:

  • Under-partitioned tables — no partition at all, or partition by a column queries don't filter on. Every query scans the full file set regardless of predicates.
  • Over-partitioned tables — partitioning by a high-cardinality column (e.g., identity transform on user_id) creates millions of tiny partitions, each with a handful of small files. Planning time explodes because Trino must evaluate millions of partition values in the manifest list. Each partition may also fall below the minimum split weight, causing inefficient scheduling.

A subtle third failure mode involves Iceberg's hidden partitioning. If a table is partitioned by day(event_timestamp) but a query filters on event_timestamp > '2026-07-01' without also constraining to a specific day, Trino may not prune partitions effectively — the filter must align with the partition transform granularity. Engineers who don't check EXPLAIN output miss that their predicate isn't triggering pruning at all.

The sweet spot: partition by the column that appears in the WHERE clause of most queries, at a granularity that produces partitions of 100 MB–1 GB each. For event-driven tables, day(event_timestamp) is the standard default. For entity tables, bucket(entity_id, 16) or truncate(region, 3) balances cardinality with file sizes.

Iceberg's partition evolution lets you fix this without rewriting historical data — change the partition spec and new data lands in the new scheme. But old data in the old scheme still suffers from the original layout until it's compacted. And partition evolution is a metadata-only operation: Trino must still handle queries that span both the old and new schemes, which complicates planning on tables with years of history.

The challenge: how do you know your partition strategy is wrong? Trino provides EXPLAIN output, but it only shows whether partition pruning is active for a specific query — it doesn't tell you whether your partition scheme is optimal for the actual mix of queries hitting the table across all engines.

How LakeOps delivers it. The Insights engine detects partition skew automatically — flagging partitions that are significantly larger or smaller than average (indicating hot partitions or over-partitioning). The Partitions tab shows per-partition file counts, byte distribution, and delete-file concentration — identifying the specific partitions driving planning timeouts or small-file explosions from streaming writes. Cross-engine telemetry reveals which columns appear in predicates most frequently across Trino, Spark, Athena, and all connected engines, informing whether the current partition scheme aligns with actual access patterns. For detailed partition design guidance, see Iceberg Partitioning Best Practices.

LakeOps Partitions
Partition drill-down — 87 partitions, 3,012 total files, 34.6 avg files/partition, 6 delete files across 3 partitions (orange). Identifies skew, delete-file hotspots, and under/over-partitioned tables before they impact Trino planning.

4. Consolidate fragmented manifests

The problem. Trino's query planning runs entirely on the coordinator. During planning, the coordinator reads the manifest list, then reads each manifest file to build the split list — deserializing file paths, partition tuples, and column statistics from Avro format into JVM heap. When a table has hundreds or thousands of small manifests — common under frequent streaming commits, micro-batch ingestion, or repeated compaction — planning itself becomes the bottleneck.

A table with 487 manifests, each containing a few file entries, forces the coordinator to issue 487 S3 GET requests sequentially during planning. If each GET takes 15–20ms (typical for S3 in us-east-1), that's 7–10 seconds of planning latency before a single byte of data is scanned. For interactive dashboards expecting sub-second responses, this alone makes the query feel broken. With iceberg.planning-threads set to the default (availableProcessors * 2 since Trino 480), manifest reading parallelizes — but the improvement is bounded by heap capacity. On a coordinator sized for query orchestration, not metadata volume, 487 manifests × hundreds of entries per manifest can push heap utilization to the point where GC pauses add further latency.

The fix. Manifest rewriting consolidates many small manifests into fewer, larger ones clustered by partition. After consolidation, the coordinator reads 10–15 manifests instead of 487 — planning drops from seconds to milliseconds. Critically, the new manifests must be partition-clustered so that partition pruning can eliminate entire manifests without reading their contents.

Trino provides optimize_manifests:

sql
1ALTER TABLE lakehouse.events EXECUTE optimize_manifests;

This is a manual, one-shot operation with a notable regression risk: in Trino versions after 476, the default manifest rewriting strategy changed to distribute files across manifests by hash rather than clustering by partition. For time-partitioned tables where queries filter on timestamp, this means every query now reads all manifests instead of just the few covering the target partitions — manifest pruning is effectively disabled. The fix (clustering by partition) was re-added in later versions, but the episode illustrates why manifest management cannot be treated as fire-and-forget. Beyond version-specific issues, optimize_manifests doesn't run on a schedule, doesn't integrate with compaction sequencing (compaction creates new manifests, so running manifests before compaction is wasted work), and cannot be applied as a policy across hundreds of tables.

How LakeOps delivers it. Scheduled manifest consolidation via Rewrite Manifests policies — running daily (default 0 4 * * *) or triggered by Insights when manifest count exceeds a configurable threshold (default 50). Manifests are always rewritten partition-clustered, ensuring partition pruning continues to work at the manifest level. Critically, consolidation executes after compaction in the correct sequence (expire → orphan cleanup → compact → manifest rewrite), ensuring it operates on the final file layout. The Insights engine flags "Excessive Manifests" at HIGH severity with the specific count and percentage of undersized manifests, driving action before Trino planning degrades. LakeOps also generates Puffin statistics files — column-level NDV, min/max, and null counts that engines use for cost-based optimization — without requiring you to run Trino's ANALYZE command on each table.

Production result: 487 → 12 manifests in 2.1 seconds. Planning time for queries on that table dropped from 8.3s to 120ms. For the full treatment of Iceberg metadata at scale, see the dedicated guide.

LakeOps Insights
Per-table insights — HIGH severity for excessive manifests (92 files, threshold 50, 43% undersized severely impacting query performance), WARNING for partition data file skew, LOW for small file accumulation. Each insight links directly to the table with specific remediation action.

5. Expire snapshots and clean up orphan files

The problem. Every write to an Iceberg table creates a new snapshot. Without expiration, snapshots accumulate unbounded — a table receiving 100 commits/hour has 72,000 snapshots after 30 days. Each snapshot references manifest files, which reference data files. The metadata tree becomes massive: the metadata.json file grows with every snapshot entry, the coordinator's memory footprint during planning scales linearly with manifest depth, and stale snapshots pin old data files — preventing storage reclamation.

The coordinator impact is direct. Querying Iceberg metadata tables like $all_entries (used for auditing and debugging) forces the coordinator to call reachableManifests(), which materializes the union of every ManifestFile across every snapshot into a single HashSet on the JVM heap. This allocation is untracked by Trino's memory accounting — so query.max-memory-per-node never fires. The coordinator OOMs with a raw OutOfMemoryError, crash-loops under restart policies, and aborts every in-flight query on the cluster. Even a simple SELECT count(*) FROM table.$all_entries can bring down the coordinator on a table with a year of snapshot history.

Orphan files compound the issue: data files from failed writes, aborted compaction, or concurrent conflicts sit in storage unreferenced by any snapshot. They inflate S3 LIST costs, pollute storage metrics, and in production lakehouses account for 25–40% of total storage spend — pure waste that Trino cannot detect or clean.

Trino provides expire_snapshots and remove_orphan_files procedures, but these are manual, single-table, single-execution operations. They don't run on a schedule, don't sequence with each other (expiring before orphan cleanup is critical — files pinned by a snapshot aren't detected as orphans until that snapshot is expired), and provide no visibility into how much waste exists before or after execution. Additionally, remove_orphan_files issues concurrent deletes that can trigger S3 prefix-level SlowDown throttling (3,500 DELETE requests/second shared with reads), potentially degrading production query performance during cleanup. At 786 tables, nobody is running these procedures manually on each one.

The fix. Automated lifecycle policies that run on configurable schedules, sequence correctly, respect safety thresholds, and produce audit trails proving execution. The correct sequence is always: expire → orphan cleanup → compact → manifest rewrite. Getting this wrong wastes compute (compacting files about to be garbage-collected) or misses reclaimable storage (orphan cleanup before expiration misses the largest category of orphans). For the full lifecycle strategy, see Iceberg Retention Policy and Orphan File Cleanup.

How LakeOps delivers it. Snapshot expiration policies run on configurable schedules (recommended: hourly via 0 * * * * for high-write tables, daily for batch). Orphan cleanup runs daily (default 0 3 * * *) with a 7-day safety threshold protecting in-progress writes — configurable per catalog for environments with long-running Flink or backfill jobs. The full sequence executes automatically: expire → orphan cleanup → compact → manifest rewrite. Every operation is logged with before/after metrics (snapshots expired, files removed, bytes reclaimed, duration) in the Events trail. LakeOps also rewrites position delete files — consolidating many small delete files into fewer large ones — reducing the merge-on-read overhead that Trino incurs on delete-heavy tables.

Production results: one run expired 22,034 snapshots and 675,510 files, reclaiming 179 GB. Another removed 59,831 orphan files (74.8 GB) from balance_snapshots in 13 minutes. Across 324 tables, approximately 200 TB of orphan data removed in a single orchestrated pass.

LakeOps Table Events
Per-table event history — every compaction (24→16 files, 11→8 files, 970→87 files), snapshot expiration, and manifest rewrite with duration and impact metrics. Complete audit trail proving maintenance governance is active.
LakeOps Lake-Wide Events
Lake-wide events — Compact Data Files (1.24 TB, 16→1 files in 4s), Expire Snapshots, Remove Orphan Files (59,831 files, 74.8 GB in 13m), Rewrite Manifests (487→12) across all catalogs. Filter by catalog, type, status.

6. Route queries to the optimal engine

The problem. Trino excels at interactive analytics — short-latency, high-concurrency, predicate-heavy queries on well-maintained tables. But not every query hitting your Trino cluster is a good fit for Trino. A full-table aggregation across 4 TB might be cheaper on Athena (scan-priced at $5/TB scanned, no cluster to maintain). A point lookup on a small table returns in 50ms on DuckDB instead of 500ms on Trino (which pays the overhead of distributed coordination even for trivial queries). A batch ETL job consuming Trino workers degrades interactive dashboard performance for everyone sharing the cluster.

The cost math is asymmetric across pricing models. Compute-priced engines (Trino, StarRocks) charge for cluster uptime — CPU-seconds are cheap, but the cluster runs regardless of utilization. Scan-priced engines (Athena, BigQuery) charge per byte read — selective queries with good partitioning pay pennies, but compute-heavy joins across large tables become expensive. A complex join that costs $0.03 on Trino costs $0.08 on Snowflake. A simple filter-and-limit that costs $0.01 on DuckDB costs $0.05 on Athena when it scans the full partition. Without cost-aware routing, every query pays the wrong pricing model.

No single-engine tool solves this. Trino cannot route queries to other engines. Starburst manages Trino clusters but doesn't dispatch to non-Trino backends. Apache Iceberg's format-level interoperability means the same tables are readable by any engine — but taking advantage of that requires an intelligent routing layer. Without one, engineers manually configure which clients connect to which engines — a fragile, per-application approach that drifts immediately as workloads evolve.

How LakeOps delivers it. The multi-engine routing layer dispatches queries across Trino, Spark, DuckDB, Snowflake, Athena, StarRocks, and ClickHouse based on query shape, latency targets, cost ceilings, and real-time engine health. Clients connect to one SQL endpoint (PostgreSQL wire, MySQL wire, Arrow Flight, or Trino HTTP protocol) — multiple backends, routing by policy. Three routing strategies (cost, latency, throughput) apply per routing group. Routing groups organize engines by workload type: storefront analytics on Trino + DuckDB (latency strategy), ETL on Spark + Athena (cost strategy), BI reporting on Snowflake + Trino (balanced). When an engine degrades or overloads, queries failover automatically to healthy alternatives — zero-downtime, zero client-side changes.

The routing layer is informed by table health: as LakeOps compacts and sorts tables (Strategies 1–2), more engines become viable for more query shapes — expanding routing options dynamically. For the full multi-engine architecture, see the dedicated guide. Result: 56% cost reduction by matching queries to optimal pricing models, 4.6x latency improvement for point lookups routed to DuckDB instead of Trino.

LakeOps Routing Endpoints
Routing groups — Analytics routes SELECT and AGGREGATE to Trino + DuckDB (High priority). BI handles transactional queries on Snowflake + Trino. Data-Team ETL runs INSERT and MERGE on AWS Athena + StarRocks. Each group has its own stable endpoint URL — applications connect once, routing handles engine selection automatically.
LakeOps Engine Comparison
Engine comparison — Trino at $0.03/query and 1.8s average runtime; Snowflake at $0.08/query and 2.1s; DuckDB at $0.01/query and 0.5s. Success rates: 99.5%, 99.8%, and 100%. The routing layer uses this cost and latency data to select the optimal engine for each query shape.

7. Enable continuous observability and health monitoring

The problem. All six strategies above work — once. Then tables drift. New pipelines start writing. Commit frequency changes. Access patterns shift with new consumers. The compaction schedule that worked last month produces suboptimal file sizes this month. The sort order that matched query patterns drifts when a new dashboard filters on different columns. Position delete files accumulate silently on merge-on-read tables, adding read amplification that doesn't appear in Trino's EXPLAIN output.

Without continuous monitoring, optimization is a point-in-time exercise that decays immediately. Teams discover regressions reactively — when analysts file tickets about slow dashboards — not proactively when structural degradation begins. Trino exposes engine-level metrics via its /metrics OpenMetrics endpoint (query latency, running queries, splits processed), but these are engine metrics, not table metrics. They cannot tell you why a query is slow at the table level: is it small files? Manifest fragmentation? Wrong sort order? Missing partition pruning? Position delete overhead? These are structural problems that require Iceberg metadata analysis — file counts per partition, manifest depth, snapshot accumulation, delete-file ratios, sort-order alignment — not query profiling.

You can query Iceberg's metadata tables ($snapshots, $files, $manifests, $partitions) from Trino for ad-hoc investigation. But this gives you a point-in-time snapshot of one table, not trend data. There are no built-in health scores, no alerting, no lake-wide dashboards, and no connection between observed degradation and automated remediation. And critically, Trino only sees Trino queries — but your tables are often queried by Spark, Athena, DuckDB, and AI agents simultaneously. Optimization based on single-engine telemetry misses the full picture.

The fix. Continuous structural health monitoring that classifies every table, surfaces degradation before it impacts queries, and connects directly to automated remediation. Observability must span the full stack: file counts, file sizes, manifest fragmentation, snapshot accumulation, partition skew, delete-file ratios, query latency, and cost attribution — across every engine.

How LakeOps delivers it. Every table is continuously classified as Critical (severe fragmentation, planning at risk), Warning (degradation underway, will reach Critical without action), or Healthy (structural indicators within bounds). Health is computed from real Iceberg signals: file count and size distribution, manifest depth, snapshot accumulation, delete-file ratio, partition skew, and sort-order alignment with actual query patterns. The Iceberg Lakehouse Observability system surfaces problems at four severity levels (CRITICAL, HIGH, WARNING, LOW) with specific remediation actions linked to the affected table — before Trino users notice.

Cross-engine telemetry shows how Trino, Spark, Athena, and other engines each use shared tables — field access frequency, query volume, latency distribution, cost per query per engine. The Monitoring screen tracks active engines, average query time, CPU and memory utilization per engine, and surfaces optimization suggestions derived from engine telemetry (e.g., "Add date_created as partition key" or "Enable sort compaction on customer_id"). The Dashboard provides executive visibility: total operations (12,211), query acceleration factor (12.4x average), cost savings ($1,374,672 over 3 months), and health distribution across all 786 tables. When the system detects a table trending toward Critical, Adaptive Maintenance fires automatically — compaction, manifest rewrite, snapshot expiration — without human intervention. For the full State of Iceberg FinOps in 2026, see the dedicated guide.

LakeOps Tables
Lake-wide table health — every Iceberg table classified as Healthy, Warning, or Critical. Filter by catalog, namespace, or status. Tables like raw_clickstream (2.7B records, 4.6 TB, CRITICAL) surface for immediate triage — these are the tables slowing your Trino queries right now.
LakeOps Dashboard
Executive Dashboard — 12,211 operations, 12.4x query acceleration, $1,374,672 cost savings, -76% CPU and storage across 786 tables and 112.4 PB of lake data. Governance effectiveness quantified at a glance.

The compounding effect

These seven strategies are not independent — they compound multiplicatively. Compaction produces larger files that Trino reads more efficiently (Strategy 1). Sort compaction makes those files prunable by min/max statistics — at both the file and row-group level (Strategy 2). Correct partitioning eliminates entire file groups at the manifest-list level before statistics are even checked (Strategy 3). Manifest consolidation ensures the planning phase — where all these optimizations are evaluated — completes in milliseconds instead of seconds, and that manifest-level partition pruning works correctly (Strategy 4). Snapshot expiration keeps the metadata tree lean so the coordinator doesn't OOM during planning and storage costs stay bounded (Strategy 5). Routing ensures Trino only handles the workloads it excels at — interactive analytics — while cost-optimal engines handle the rest (Strategy 6). And observability verifies that all of this keeps working as tables evolve, triggering remediation before degradation reaches users (Strategy 7).

The combined result in production: queries that previously scanned 4.6 TB and took 90 seconds complete in under 7 seconds — a 12x acceleration — with no changes to Trino configuration, no additional workers, and no changes to the queries themselves. Slow Trino queries on Iceberg become fast Trino queries on Iceberg. The improvement comes entirely from fixing the tables underneath.

Getting started

LakeOps connects to your existing catalogs (AWS Glue, Polaris, Nessie, Gravitino, S3 Tables) in under 10 minutes — no agents to install, no data to move, no pipeline changes. Data never leaves your account.

Step 1: Connect catalogs and review the Tables view. Critical and Warning tables are visible immediately — these are the tables slowing your Trino queries right now. Step 2: Enable compaction on the worst offenders. Start with Execute (one-shot run, verify results in Events and Metrics) before enabling scheduled automation. Step 3: Define catalog-wide maintenance policies — snapshot expiration hourly, orphan cleanup daily, manifest rewrites daily after compaction. New tables inherit automatically via policy inheritance. Step 4: For validated tables, enable Adaptive Maintenance. The platform handles the full optimization stack autonomously — compaction (binpack or sort based on cross-engine telemetry), snapshot expiry, orphan cleanup, manifest consolidation, and position-delete rewrites — governed by a single data-driven policy that adapts to table activity signals.

The Managed Iceberg in 2026 guide covers the full control-plane architecture. The Iceberg Lakehouse Optimization article walks through each operational pillar in depth. The Intelligent Lakehouse post shows how LakeOps delivers what Netflix built internally — accessible to every team.

Your Trino queries are not slow because Trino is slow. Slow Trino Iceberg performance is almost always a table maintenance problem — not an engine problem. Fix the tables, and Trino delivers the sub-second performance it was built for.

Watch demoYouTube ↗
Watch LakeOps in action — from catalog connection through autonomous table optimization and 12x query acceleration in minutes.

Tags

Apache Icebergslow Trino queries IcebergTrino Iceberg performancefaster Trino Icebergfix slow Trino queriesTrino query optimization IcebergTrino Iceberg slow

Related articles

Found this useful? Share it with your team.