Tag: iceberg

  • Databricks Unity Catalog + Apache Iceberg in 2026

    Databricks Unity Catalog + Apache Iceberg in 2026

    The table format question is settled. Apache Iceberg won. Snowflake, Databricks, AWS, Google, and every serious data platform has committed to it. What hasn’t settled — what’s actively being fought over right now, with real architectural consequences for every data team making lakehouse decisions — is the catalog question. And the catalog matters far more than the format.

    The catalog resolves metadata, controls access, vends credentials, sequences commits, and acts as the single API boundary between every engine and every byte of data your organization owns. Pick the wrong one and you inherit operational debt that grows with each table you add. At Data + AI Summit 2026, Databricks made its position clear: Unity Catalog is the most comprehensive and open catalog across both the Delta Lake and Apache Iceberg ecosystems — with Managed Iceberg GA, Iceberg v3 GA, cross-engine ABAC, and new federation connectors including Snowflake Horizon and Salesforce Data Cloud.

    This is the guide that breaks down what Unity Catalog actually does for Iceberg workloads in 2026 — not the keynote version, but the one that tells you which features are GA, which are preview, what the access model looks like, and where the edges still are.

    TL;DR

    → Unity Catalog now governs both Delta Lake and Apache Iceberg tables from a single catalog. Managed Delta tables are GA. Managed Iceberg tables are in Public Preview (available on Databricks Runtime 16.4 LTS and above).

    → External engines access Unity Catalog managed tables through two open APIs: the Unity REST API (read/write/create for Delta clients) and the Iceberg REST Catalog (IRC) (read/write/create for Iceberg clients). Both support credential vending — temporary, scoped credentials that inherit the requesting principal’s privileges.

    → Iceberg clients that can write to Unity Catalog managed tables: Apache Spark, Apache Flink, Trino, and Snowflake. Path-based access to managed tables is not supported — it bypasses access controls and breaks managed table features.

    → Lakehouse Federation lets Unity Catalog govern tables in foreign catalogs: AWS Glue, Hive Metastore, Snowflake Horizon, Salesforce Data Cloud, Google Cloud Lakehouse, and Palantir. For Snowflake-managed Iceberg tables specifically, Catalog Federation reads directly from object storage (Databricks compute only, no Snowflake compute billed). Non-Iceberg Snowflake tables fall back to Query Federation.

    → Cross-engine ABAC is now GA: column masks and row filters enforced during server-side scan planning through the Iceberg REST Scan APIs. Any engine implementing the Iceberg 1.11 scan-planning client gets the same policies applied without a Databricks runtime.

    → A new FILE type (beta) lets managed Delta and Iceberg tables natively govern unstructured data — PDFs, images, audio, video — in open formats, tracked in Unity Catalog alongside structured tables.

    Why the catalog became the battleground

    Diagram showing how external engines like Apache Spark, Trino, and Snowflake access Unity Catalog managed tables via Unity and Iceberg REST APIs, with cloud object storage managed by Unity Catalog.

    Unity Catalog governs both table formats through one metadata layer. Policies enforce at scan-planning time — before any data file is read — so governance travels with the catalog, not the engine.

    When Delta Lake launched, the catalog was a formality. A Hive Metastore tracked table locations and schemas, and the format handled everything interesting. With Iceberg winning as the shared format, the catalog became the differentiator. Every engine can read Iceberg. The question is which engine decides who can read it, what they can see within each table, and how commits are sequenced when multiple engines write concurrently.

    That’s what Unity Catalog answers for Databricks workloads. It sits between every engine and every table, enforcing access policies at the point where scan planning happens — before any data file is read. Because the Iceberg REST Catalog API exposes those policies at the server-side scan-planning layer, a compliant engine (Spark, Trino, DuckDB via the Iceberg 1.11 client) receives the same row filters and column masks that a Databricks notebook would see, without needing to run inside Databricks. The governance travels with the catalog, not with the runtime.

    Managed tables: what Unity Catalog controls

    The key distinction in Unity Catalog is between managed and external tables. Managed tables are the default and recommended type. Unity Catalog owns everything: where the data files live, how they’re organized, compaction, statistics, optimization. You reference tables by three-part name (catalog.schema.table). Path-based access is explicitly not supported for managed tables — it bypasses Unity Catalog’s access controls and breaks features like Predictive Optimization and Liquid Clustering.

    Managed Delta tables (GA) — Unity Catalog’s default. The Delta format with ACID transactions, schema evolution, and Databricks-specific optimizations. External engines access them read-only through the Unity REST API or as Iceberg via UniForm (Delta tables exposed with an Iceberg read layer). Write access for external Delta clients is in Public Preview.

    Managed Iceberg tables (Public Preview, Databricks Runtime 16.4+) — native Apache Iceberg tables owned by Unity Catalog. External engines with Iceberg REST Catalog support can read, write, and create managed Iceberg tables. Supported write clients today: Apache Spark, Apache Flink, Trino, and Snowflake. Predictive Optimization and Liquid Clustering apply automatically.

    The practical implication: if your workload needs Snowflake to write data that Databricks then transforms, managed Iceberg is the architecture — Snowflake connects via the Iceberg REST Catalog, writes to the managed table, and Databricks reads with full governance. If the flow is Databricks-to-Snowflake reads only, UniForm on a managed Delta table is simpler than standing up a separate managed Iceberg table.

    Cross-engine access: the two APIs

    Unity REST API — for Delta Lake clients. Provides read and write access to managed and external Delta tables. Both modes support credential vending: Unity Catalog issues temporary credentials scoped to the requesting principal’s privileges, so external engines never hold long-lived Databricks credentials and governance policies apply at the storage layer.

    Iceberg REST Catalog (IRC) — for Iceberg clients. Read/write/create access to managed Iceberg tables; read-only access to Delta tables with Iceberg reads enabled (UniForm). The credential vending model is the same: temporary, scoped, inheriting the requesting principal’s privileges from Unity Catalog’s access control list.

    Both APIs hit the Unity Catalog server, not object storage directly. That’s what makes policy enforcement possible at the catalog level rather than being a layer each engine has to implement independently.

    Lakehouse Federation: governing tables you don’t own

    Unity Catalog’s federation model extends governance to tables in foreign catalogs — systems outside Databricks that Unity Catalog can query and, in some cases, govern. The federated catalog list as of mid-2026: AWS Glue, Snowflake Horizon, Hive Metastore, Salesforce Data Cloud, Google Cloud Lakehouse, and Palantir.

    The Snowflake federation case has a meaningful internal split worth understanding separately:

    Catalog Federation (for Snowflake-managed Iceberg tables) — Unity Catalog reads Snowflake Iceberg tables directly from cloud object storage. Databricks compute executes the query; Snowflake compute is never invoked, so there is no Snowflake credit charge for the read.

    Query Federation (for native Snowflake tables) — Non-Iceberg Snowflake tables are always accessed via Query Federation. Unity Catalog sends a query to Snowflake’s compute, which runs it and returns the result. Snowflake credits fire. The distinction is the same split as Salesforce Data Cloud’s File vs Query Federation — the Iceberg format is what enables compute-free storage-layer reads across both platforms.

    Cross-engine ABAC: governance that travels with the catalog

    Cross-engine ABAC is now GA: column masks and row filters defined in Unity Catalog are enforced during server-side scan planning through the Iceberg REST Scan APIs. Any engine that implements the Iceberg 1.11 scan-planning client — Spark, DuckDB, Trino, any compliant engine — gets those policies applied before it reads a single data file.

    Traditional column masking was enforced at query execution time, inside the compute layer. An engine that bypassed the query layer and read files directly could skip the masks. Server-side scan planning enforcement moves the policy check to the catalog, so an Iceberg-compliant client gets an already-filtered manifest — it can only see the files and columns it’s allowed to see, and the catalog decided that before any compute ran.

    Predictive Optimization and Liquid Clustering

    Predictive Optimization automatically identifies tables that need compaction, clustering, or statistics updates based on workload patterns and applies those operations proactively. For managed Iceberg tables, this means the same performance tuning Databricks applies to Delta workloads now runs on open-format tables accessed by external engines.

    Liquid Clustering replaces the manual partition-column decision with an adaptive co-location scheme: you specify clustering keys, and Unity Catalog reorganizes files continuously based on actual query patterns. For Iceberg tables read by Snowflake or Trino, this means better file pruning and lower scan costs even without partition-level optimization on the reader side.

    The gotchas nobody warns you about

    Managed Iceberg tables are Public Preview, not GA. Production workloads should track the GA release — preview status means the API can change.

    Path-based access breaks managed table features. If an external tool or legacy process accesses managed table files directly by path, it bypasses access controls and disables Predictive Optimization and Liquid Clustering. The migration from external tables to managed tables requires updating every access pattern to use three-part names and the catalog APIs.

    Snowflake Catalog Federation requires Iceberg-backed Snowflake tables. The compute-free federation path only works for Snowflake-managed Iceberg tables. Native Snowflake tables fall back to Query Federation with Snowflake compute charges on every federated read.

    Foreign table metadata freshness. For federated tables from Snowflake or other external catalogs, Unity Catalog caches metadata. Tables updated frequently in the external system may appear stale until a metadata refresh runs. For high-frequency foreign tables, configure periodic refresh via Lakeflow jobs.

    The Iceberg v4 roadmap changes the file structure. Databricks engineers are actively proposing Iceberg v4 changes: an adaptive metadata tree (most operations write a single file), relative path support, and a modernized statistics model for VARIANT and GEOMETRY types. Architectures built on Unity Catalog now are well-positioned for v4 because the catalog abstracts format evolution.

    The one principle

    The catalog is a write-path decision, not a read-path one. Any engine can read Iceberg. The question is which catalog controls who writes, how commits are sequenced, and which policies apply at scan time. Unity Catalog’s answer — two open APIs, credential vending, server-side ABAC, foreign catalog federation — is coherent and production-ready for Iceberg workloads today, with managed Iceberg tables a quarter behind on GA. Choose your catalog before you choose your partition strategy, because the catalog is the layer that makes your governance durable as you add engines.

    Related reading: What’s new with Unity Catalog at Data + AI Summit 2026 · Unity Catalog managed tables docs · The 2026 Migration Trap: Native Tables to Dynamic Iceberg v3 · Governing the AI Agent: Snowflake CoCo + MCP Security

  • The 2026 Migration Trap: Moving from Native Tables to Dynamic Apache Iceberg v3 in Snowflake

    The 2026 Migration Trap: Moving from Native Tables to Dynamic Apache Iceberg v3 in Snowflake

    The pitch is intoxicating and mostly true: keep your data in open Apache Iceberg format on your own object storage, let external engines read it, and let Snowflake’s Dynamic Tables handle the low-latency transformations on top — one declarative pipeline, no lock-in, a real lakehouse. In 2026, with Iceberg v3 generally available on Snowflake since May 7, teams are migrating native tables to dynamic Iceberg tables expecting exactly that. Most of them hit the same wall in the same order.

    The wall is this: “open and interoperable” describes the storage format, not the write path, and “low latency” describes Dynamic Tables under conditions that partitioned Iceberg writes and cross-engine change tracking quietly violate. The migration doesn’t fail loudly. It succeeds, ships, and then your incremental pipeline starts doing full refreshes you didn’t ask for, your partitioned writes fan out into a metadata problem, and the external engine you promised could write to these tables turns out to be read-only. This is the guide to the traps — the ones that don’t show up in the quickstart — and how to design around them before they cost you a quarter.

    TL;DR

    → Iceberg v3 (GA on Snowflake May 2026) brings deletion vectors, row lineage (native CDC), VARIANT, and multi-argument partition transforms. You cannot upgrade v2 to v3 in place — you recreate the table. Plan the migration, don’t expect an ALTER.

    → Dynamic Iceberg tables support PARTITION_BYTARGET_FILE_SIZE, and PATH_LAYOUTPATH_LAYOUT = HIERARCHICAL only produces Hive-style partitioned paths when paired with PARTITION_BY — and over-partitioning (more than a few thousand partitions) turns your metadata layer into the bottleneck.

    → The cross-engine reality: Snowflake-managed Iceberg tables are read-write for Snowflake, read-only for external engines. Writes from external engines to Snowflake-managed v3 tables via Horizon Catalog aren’t supported yet. External engines can only write to externally-managed tables.

    → Dynamic Tables track changes at the row level for native tables but the file level for externally-managed Iceberg base tables. Frequent copy-on-write on the external table degrades incremental refresh — a file changes, the whole file is “changed.”

    → INSERT OVERWRITE on a base table resets change-tracking metadata and forces a full refresh. Row lineage (v3) and primary keys with RELY are how you keep incrementality alive across rewrites.

    → Deletion vectors (v3 merge-on-read) are governed by heuristics: Snowflake only writes a deletion vector if fewer than ~5% of a file’s rows are deleted and the file is larger than ~1.6 MB. External engines that don’t understand v3 deletion vectors force you to set ICEBERG_MERGE_ON_READ_BEHAVIOR = 'DISABLED' (copy-on-write) for compatibility.

    What v3 actually changed, and why in-place upgrade isn’t a thing

    The target architecture: a Bronze/Silver/Gold lakehouse where Dynamic Iceberg tables handle incremental transforms and write open Iceberg that external engines can read. The traps live in the arrows, not the boxes.

    Iceberg v3 is a genuine step change, not a point release. It adds deletion vectors (up to ~10x faster DML by avoiding positional-delete merges at read time), row lineage for native change data capture, a VARIANT type for semi-structured data with structured-query performance, default column values, geometry/geography types, nanosecond timestamps, and multi-argument partition transforms. On Snowflake it went to preview in March 2026 and GA on May 7, 2026.

    Here’s the first thing that trips migrations: you can’t upgrade an Iceberg table from v2 to v3. There is no ALTER TABLE ... SET ICEBERG_VERSION = 3 that rewrites your existing table in place. You configure the default Iceberg version and create new v3 tables, migrating data into them. This matters because teams plan the migration as a flag flip and discover it’s a recreate-and-backfill — which, for a large partitioned table, is a real project with a real compute bill, not a maintenance-window toggle. The related gotcha: v2 tables using copy-on-write represent an updated or relocated row in a standard stream as a DELETE followed by an INSERT for the same row, so any CDC logic you built on v2 stream semantics needs re-validation against v3’s row lineage before you cut over.

    The partitioned-write trap: HIERARCHICAL paths and the metadata ceiling

    Dynamic Iceberg tables expose three storage-shaping properties: PARTITION_BYTARGET_FILE_SIZE, and PATH_LAYOUT. The one that surprises people is PATH_LAYOUT. It defaults to FLAT, meaning all Parquet data files land directly under the data/ directory. Set it to HIERARCHICAL and Snowflake writes Hive-style partitioned paths — but only in combination with PARTITION_BY. Setting HIERARCHICAL without a partition spec does nothing useful; the two are a pair.

    A minimal partitioned dynamic Iceberg table looks like this:

    CREATE DYNAMIC ICEBERG TABLE my_dt (
      product_id NUMBER, product_name STRING, order_time TIMESTAMP_NTZ
    )
      TARGET_LAG = '20 minutes'
      WAREHOUSE = my_wh
      EXTERNAL_VOLUME = 'my_vol'
      CATALOG = 'SNOWFLAKE'
      BASE_LOCATION = 'my_dt'
      PARTITION BY (YEAR(order_time))
      PATH_LAYOUT = HIERARCHICAL
      AS SELECT product_id, product_name, order_time FROM staging;

    The trap isn’t the syntax; it’s the partition cardinality. Iceberg’s metadata tracks files per partition, and every partition you create adds manifest overhead. Snowflake’s own guidance is blunt: avoid creating more than a few thousand partitions, and test query performance against your actual workload before finalizing a partitioning strategy. The failure mode when you ignore this is quietly brutal — partition by DAY(event_time) on a table with a few years of history and a high-cardinality secondary key, and you can generate tens of thousands of tiny partitions, each with its own small files. Now your Dynamic Table refresh spends its time in metadata planning rather than moving data, and your “low-latency” pipeline has a latency floor set by manifest bookkeeping.

    The design rule that keeps you out of trouble: partition on the coarsest grain that still prunes your dominant query pattern (usually a month or a broad category), let TARGET_FILE_SIZE and Snowflake’s file management handle within-partition layout, and reach for clustering rather than finer partitions when you need more selective pruning. Hierarchical paths are for interoperability and human-navigable storage, not a license to over-partition.

    The cross-engine write trap: “interoperable” is asymmetric

    This is the one that derails architecture diagrams. The interoperability story — external engines like Spark and Trino reading your Iceberg data — is real, but it runs in one direction for Snowflake-managed tables. Snowflake-managed Iceberg tables are read-write for Snowflake and read-only for external engines. As of the v3 GA, reading Snowflake-managed v3 tables from an external engine via the Horizon Iceberg REST Catalog API is generally available; writing from external engines to Snowflake-managed v3 tables through Horizon is explicitly not supported yet.

    If your architecture needs an external engine to write Iceberg that Snowflake then transforms, you must use externally-managed tables — data written by Spark into a catalog like AWS Glue, which Snowflake reads via a catalog integration and a linked database. That’s a supported and powerful pattern (it’s the canonical Bronze layer of an open lakehouse), but it’s a different architecture with different semantics than “Snowflake-managed tables that everyone can write to,” which does not exist today. Decide early which engine owns writes for each table, because that choice dictates managed-vs-external, and switching later means a migration. A further sharp edge: you can’t write with vended credentials to cloned or converted tables, and you can’t write at all to a table that was converted from externally-managed to Snowflake-managed — conversions are one-way for write access.

    The change-tracking trap: file-level vs row-level

    The granularity of change tracking decides how much work an incremental refresh does. Row-level (native) processes a tight delta; file-level (external Iceberg) can reprocess an entire file because one row moved.

    Dynamic Tables get their speed from incremental refresh — processing only what changed since the last refresh. The catch that native-table migrators don’t see coming: Dynamic Tables track changes at the file level for externally-managed Iceberg base tables, whereas they track at the row level for native Snowflake tables. That single difference reshapes your performance profile.

    With a native base table, if one row in a micro-partition changes, Snowflake knows it was that row, and the incremental refresh processes a tight delta. With an externally-managed Iceberg base table, change tracking is file-granular: a copy-on-write update that rewrites a data file marks the entire file as changed, so the refresh reprocesses every row in it, even if one row moved. On a table with frequent small updates and copy-on-write behavior, this inflates the change set dramatically and can make an “incremental” refresh behave like it’s doing far more work than the actual data change justifies. Snowflake’s documentation states it plainly: frequent copy-on-write operations on externally-managed Iceberg tables may impact incremental-refresh performance.

    Then there’s the metadata reset. INSERT OVERWRITE on a base table — a common pattern for batch reloads — resets change-tracking metadata, and the next Dynamic Table refresh falls back to a full recomputation. If your ingestion rewrites tables wholesale, your downstream “incremental” pipeline isn’t incremental at all.

    How v3 features rescue the change-tracking story

    The good news is that v3 exists partly to solve this, and using its features deliberately is the difference between a fast lakehouse and a slow one.

    Row lineage is the headline. In v3, tables track _row_id (a stable unique identifier assigned to each row) and _last_updated_sequence_number (the commit that last touched the row). This lets any compliant engine reliably match the same row across snapshots and detect row-level changes — native CDC in the format itself, not bolted on. Row lineage is supported for both Snowflake-managed and externally-managed v3 tables and underpins append-only and standard streams on Snowflake-managed v3 tables.

    Primary keys with RELY are the pragmatic rescue for the INSERT OVERWRITE problem. If you declare a reliable primary key on the base table, Snowflake compares rows by key value instead of leaning on change-tracking columns — so even when a table is fully rewritten, it computes the minimal set of actual changes rather than reprocessing everything. This is also how you enable incremental refresh downstream of a full-refresh dynamic table, by giving Snowflake a stable identity to diff against. For append-only CDC, the QUALIFY ROW_NUMBER() OVER (PARTITION BY id ORDER BY updated_at DESC) = 1 pattern gives you latest-row-per-key with a derived unique key, handling out-of-order arrival without extra logic.

    Deletion vectors replace v2’s positional deletes for merge-on-read, and they’re governed by heuristics worth knowing: Snowflake writes a deletion vector only if fewer than ~5% of a data file’s rows are deleted and the file exceeds ~1.6 MB; otherwise it rewrites the file (copy-on-write). You control the behavior with ICEBERG_MERGE_ON_READ_BEHAVIOR. The compatibility trap: if an external engine in your stack doesn’t yet understand v3 deletion vectors, you must set that parameter to 'DISABLED' to force copy-on-write, or the external engine will misread the table. Interoperability constrains you to the capabilities of the least capable engine that touches the table.

    The gotchas nobody warns you about

    Change tracking must be on, with non-zero Time Travel, on every underlying object. Incremental refresh silently depends on it. Snowflake will try to enable it automatically for incremental dynamic tables, but if you recreate a base object you must re-enable it — and a base object with Time Travel set to zero quietly breaks incrementality.

    The GRANT syntax has a trap for dynamic Iceberg tables. To grant access to future dynamic Iceberg tables in a schema, you use GRANT … ON FUTURE ICEBERG TABLES without the DYNAMIC keyword. The intuitive ON FUTURE DYNAMIC ICEBERG TABLES does not cover them, so a reasonable-looking grant leaves new tables inaccessible.

    Gen2 warehouses matter more than you’d expect. Snowflake’s Dynamic Table performance work — measured up to ~2.8x faster refresh over the past year — is specifically tied to Gen2 warehouses for patterns like top-level aggregates, QUALIFY row/rank = 1, cluster-by, and joins. If your incremental pipeline is on Gen1, you’re leaving a large multiple of refresh speed on the table before any Iceberg tuning.

    Cross-region and cross-cloud tables bill for transfer. A Snowflake-managed Iceberg table whose external volume sits in a different region or cloud than your account incurs cross-region data-transfer charges under the DATA_LAKE transfer type. Keep external volumes in the same region as your account unless you have a deliberate DR reason not to.

    A migration order that avoids the traps

    Sequence matters. First, decide per table who owns writes — if an external engine writes, it’s externally-managed; if only Snowflake writes, Snowflake-managed — because that’s the irreversible-ish decision. Second, set your default Iceberg version to v3 and plan recreate-and-backfill for existing v2 tables rather than expecting an upgrade. Third, choose a coarse partition grain (validated against real query patterns, staying well under a few thousand partitions) and use clustering for finer pruning. Fourth, make change tracking deliberate: declare reliable primary keys where base tables get rewritten, lean on row lineage for CDC, and confirm change tracking plus non-zero Time Travel on every base object. Fifth, pin ICEBERG_MERGE_ON_READ_BEHAVIOR to match the least-capable engine that reads the table. Then move workloads to Gen2 warehouses and measure incremental-refresh times against your latency target before you call it done.

    The one principle

    “Open Iceberg lakehouse with low-latency Dynamic Tables” is true only when the write path, the partition cardinality, and the change-tracking granularity all line up — and by default they don’t. Migrating native tables to dynamic Iceberg v3 is a design exercise, not a format swap: decide who writes, partition coarsely, give Snowflake a stable row identity to diff against, and constrain merge-on-read to your least-capable engine. Get those four right and the lakehouse is genuinely fast and open. Get them wrong and you’ve built a slow data lake with extra steps, one full refresh at a time.

    Related reading: Create dynamic Apache Iceberg tables (official docs) · Manage Iceberg tables: row lineage & deletion vectors · Snowflake Iceberg v3: When to Migrate · dbt State on Snowflake: Skip Unchanged Models · Dynamic Airflow DAGs via Snowflake Metadata

  • Snowflake Iceberg V3: When to Actually Migrate(vs Native Tables)

    Snowflake Iceberg V3: When to Actually Migrate(vs Native Tables)

    Most data engineers I talk to still store everything in Snowflake native format. It’s simple: load data, query data, done. But here’s what nobody’s talking about: if you’re querying that data from anywhere else — Spark, Databricks, even just a local Python script — you’re paying a hidden “data tax.” Redundant storage, egress fees, ETL pipeline complexity. For Fortune 500 companies, that tax runs $2 million to $7 million a year. And Snowflake’s new Apache Iceberg v3 support (GA May 2026) actually changes the math. But migrating is a choice, not a reflex — and there are specific gotchas that’ll bite you if you don’t plan right.

    The honest decision tree: migrate if you’re paying egress fees or running multi-engine queries.

    TL;DR

    → Apache Iceberg v3 is GA on Snowflake (May 2026). New features: deletion vectors (10x faster DML), row lineage for CDC, VARIANT type for semi-structured data, nanosecond timestamps, default column values.

    → Month-to-month costs are roughly equal to native Snowflake tables (compute identical, storage ~$23/TB native vs ~$0.023/GB S3, negligible difference).

    → Migrate if: (a) you query from Spark/Databricks (egress fees kill you), (b) you’re paying >$500/month for Snowflake storage, (c) you want single copy of truth across multiple engines.

    → Don’t migrate if: you only query from Snowflake, storage bill is small, and you’re not building a multi-engine architecture.

    → New gotcha: You can’t upgrade v2 tables in-place to v3. No writing to v3 tables via external engines (Spark) yet. External engine compaction gets billed starting May 21, 2026.

    → Real win: Snowflake Storage for Iceberg (GA April 2026) means you don’t manage S3 buckets. Snowflake handles it, with Fail-safe recovery built in.

    → The “data tax” of $2M–$7M annually on Fortune 500 costs more than Iceberg migration ever will.

    The mental model that’s keeping you locked in

    Here’s the picture most teams hold: Snowflake stores data. We query it in Snowflake. Done. Native tables, simple syntax, life is easy. And if you only query in Snowflake, that model works fine. You get the speed, the simplicity, the integration with dbt, the Time Travel.

    But the moment you have data living in two systems — Snowflake for reporting, Spark for ML training, Databricks for a BI tool, even just a DuckDB instance on your laptop — you’ve broken the simple model. Now you have two copies of the data, or worse, a pipeline that’s constantly syncing between them. You’re paying Snowflake egress fees to get data out ($0.02 per GB across regions, $0.08 between clouds). You’re rebuilding the same transformation logic in both systems. You’re managing schema evolution in two places. The complexity compounds.

    Iceberg was built to solve exactly this. One copy of the data, on open cloud storage (S3, Azure Blob, GCS), readable by any engine that supports the Iceberg format. Snowflake, Spark, Databricks, Trino, DuckDB. All of them see the same table, the same schema, the same snapshot. No replication, no egress fees, no syncing.

    But Iceberg isn’t free. It trades simplicity for flexibility. And for teams that genuinely don’t need that flexibility, native tables are still the right call.

    The hidden cost of locking data into proprietary formats. For large teams, it’s massive.

    What changed in Iceberg v3, and why it matters

    Iceberg v2 shipped in 2023 and covered the basics: open format, ACID transactions, schema evolution, snapshots. v3 (released June 2025, GA on Snowflake May 7, 2026) added seven new capabilities. Only three actually change how you’d use it.

    Deletion vectors. In v2, if you deleted or updated a row, Iceberg had to rewrite the entire data file (copy-on-write). Slow and expensive. v3 adds deletion vectors — a separate, small metadata file that marks rows as deleted without touching the original data. Result: 10x faster DML operations on large tables. If you’re doing frequent small updates (common in streaming ingestion), v3 matters.

    Row lineage. v3 tracks which rows were inserted, updated, or deleted with metadata fields (_row_id, _last_updated_sequence_number). This is how Snowflake implements change data capture (CDC) without external tooling. A Dynamic Iceberg Table can now refresh incrementally on only the rows that changed, not the whole partition. Critical for SCD2 and CDC pipelines.

    VARIANT type. v2 forced you to choose: store JSON as a string (slow parsing at query time) or explode it into a wide schema (thousands of nullable columns, query disasters). v3 adds native VARIANT support, and Snowflake automatically shreds it (extracts nested fields and indexes them) at write time. Query performance on semi-structured data jumps dramatically. This alone is why observability platforms are betting on Iceberg.

    The other four (default column values, geometry/geography types, nanosecond timestamps, partition transform improvements) are niche. Don’t worry about them unless you hit them.

    The cost math: Native vs Iceberg in real dollars

    Let’s be honest: most articles skip the cost comparison and jump to “Iceberg is cheaper!” It usually isn’t, month-to-month. Here’s why.

    Two-column cost breakdown. Native Snowflake: 2,000 credits at $3 = $6,000 compute, $23/TB storage = $230, total $6,230/month. Iceberg (Snowflake managed): same $6,000 compute, S3 at $0.023/GB = $235 storage, bundled compaction = $0, total $6,235/month. Verdict: same cost, but Iceberg enables multi-engine and zero egress.

    The real numbers. On a month-to-month basis, they’re nearly identical. The wins come from elsewhere.

    For a typical 10 TB table with 1,000 queries per month (small-to-medium workload):

    Native Snowflake: Compute 2,000 credits ($6,000) + Snowflake storage 10TB at $23/TB ($230) = $6,230/month.

    Iceberg (Snowflake-managed storage, GA April 2026): Compute 2,000 credits ($6,000) + S3 storage 10TB (10,240 GB × $0.023/GB = $235) + compaction bundled ($0) = $6,235/month.

    Basically the same. Where Iceberg wins is not in monthly costs. It wins in:

    Egress fees. If you query that 10 TB table from a Databricks cluster once a month, native Snowflake costs 10,000 GB × $0.08/GB (cross-cloud egress) = $800. Iceberg: $0. Over a year, that’s $9,600. At any real-world scale (multi-engine queries), egress dominates.

    No data duplication. If you’re currently syncing data between Snowflake and Databricks (ETL pipeline, manual export, Fivetran), that pipeline costs money too. Shared Iceberg table means you stop paying to move the data. One table, multiple readers.

    Storage simplicity. With Snowflake Storage for Iceberg (new, April 2026), you don’t manage S3 buckets yourself. Snowflake handles encryption, replication, Fail-safe recovery. You save the operational tax of bucket management, lifecycle policies, and debugging storage issues.

    So here’s the honest scorecard:

    For Snowflake-only users: Native tables win. Simpler, no migration pain, costs are identical.

    For multi-engine shops (Snowflake + Spark + Databricks): Iceberg wins. Egress fees alone justify the migration, and you get single source of truth as a bonus.

    The gotchas that will hurt your migration

    You can’t upgrade v2 tables to v3 in-place. There’s no ALTER TABLE ... SET ICEBERG_VERSION = 3. To get v3, you have to CREATE a new table. That means copying data (compute cost, time), repointing your queries, and hoping nothing breaks downstream. On large tables, this is a multi-day operation.

    External engines can’t write v3 tables yet. You can read v3 tables from Spark, Trino, DuckDB, all day. But writing is blocked. Snowflake says it’s “planned,” but if you’re building a shared Iceberg table that Spark needs to update, you’re stuck on v2. This is a major limitation if you’re counting on true multi-engine write access.

    Compaction gets billed starting May 21, 2026. When an external engine writes to an Iceberg table (via Spark, Trino, etc.), it creates small data files. Snowflake’s compaction automatically consolidates them into bigger files for query performance. Until May 21, that was free. Now it costs credits. Budget for ongoing compaction maintenance if you have heavy external write workloads.

    ⚠️ Don’t convert cloned tables with vended credentials. If you clone a native Snowflake table and then convert it to Iceberg, you can’t write to it with vended credentials (external query engine creds). You’d have to connect the external engine directly to your S3 bucket, defeating the whole point. Create the Iceberg table fresh if you’re using vended creds.

    Schema changes are cheap but metadata bloat is real. Iceberg tracks every schema change as a separate metadata version. On tables with thousands of ALTER COLUMN operations, metadata can get unwieldy. Compact your metadata regularly with CALL SYSTEM$OPTIMIZE(...).

    The mistakes teams make when migrating

    1. Migrating for the wrong reason. “Everyone’s talking about Iceberg, so we should move.” Wrong. Migrate only if you have a concrete use case: egress fees, multi-engine queries, or storage cost >$500/month. Otherwise you’re trading simplicity for nothing.

    2. Not testing external engine read performance first. Iceberg’s query performance depends heavily on your cloud setup, partitioning strategy, and how many small files are sitting around. Test Spark/Databricks queries on a small Iceberg table before migrating your 100 TB production table. You might find that your workload is slower on Iceberg, not faster.

    3. Assuming v3 is backward-compatible with v2. It’s not. Engines that only understand v2 (like older Spark runtimes, Trino versions) will fail on v3 tables. Check that every tool in your stack supports v3 *before* upgrading. v2 → v3 is one-way; there’s no downgrade.

    4. Ignoring the partition evolution story. Iceberg lets you change your partitioning scheme without rewriting the whole table. It’s a huge feature, but it’s also easy to mess up. Bad partitioning (e.g., partitioning by a column with 10 million distinct values) creates a partition explosion. Get your partitioning right before you migrate, not after.

    5. Migrating everything at once. Pick one critical table, migrate it, test multi-engine queries for a month, then move the rest. Iceberg is mature enough for production, but it’s not old enough that every edge case is documented. Be intentional.

    When to actually migrate: The real decision

    Stop and ask yourself: Do you actually need Iceberg?

    Yes, if: You query the same data from Snowflake and Spark/Databricks. You’re paying egress fees. You have data warehouses in multiple clouds and want to query across them. You’re building a data lakehouse and want to ditch proprietary formats.

    No, if: You only query from Snowflake. Your storage bill is <$500/month. You’re using Snowflake’s Time Travel, zero-copy clones, and other native features heavily. You don’t need to share data with other engines.

    For most teams, the answer is no. And that’s okay. Native Snowflake tables are extremely good. Simple, fast, well-integrated with dbt. There’s no shame in staying native.

    But for teams hitting the “data tax” — redundant copies, egress fees, multi-engine complexity — Iceberg v3 actually delivers. The gotchas are real, but they’re manageable. The cost savings are modest month-to-month, but the flexibility is transformative.

    The one principle that matters

    Interoperability beats simplicity when you’re already paying for fragmentation. If your current architecture already costs you $800/month in egress, $300/month in ETL pipelines, and engineering time chasing sync issues, Iceberg’s “complexity” is actually a simplification. You’re not adding complexity; you’re replacing it with a standard.

    If you’re simple and integrated today, stay there. Don’t pay the cost of flexibility you don’t need. But if you’re paying the data tax, Iceberg’s math changes fast.

    Related reading: Snowflake Apache Iceberg tables (official docs) · Snowflake Time Travel: The Real Architecture · Snowflake Optima: 15x Faster Queries at Zero Cost · Query Snowflake in DuckDB and Cut Costs

  • Delta Lake vs Apache Iceberg — Why I Chose Iceberg for Our Data Lakehouse

    Delta Lake vs Apache Iceberg — Why I Chose Iceberg for Our Data Lakehouse

    TL;DR
    → Delta Lake is easier to start with, especially if you’re already on Databricks
    → Iceberg wins on engine flexibility — works natively with Spark, Flink, Trino, Snowflake, and more without custom connectors
    → Delta Lake’s vendor coupling with Databricks is a real cost if you’re multi-cloud or multi-engine
    → Iceberg’s partition evolution lets you change partition schemes without rewriting data — that feature alone saved us a full weekend of migration work
    → Migration from Delta to Iceberg is harder than most blog posts suggest — budget four to eight weeks, not a weekend
    → If you’re greenfield, start with Iceberg. If Delta is working, don’t migrate until you hit a specific limit


    I didn’t choose Iceberg because I read a benchmark blog post. I chose it after six months of hitting Delta Lake’s limits in ways that weren’t obvious until they were expensive.

    We were running a mid-sized data lakehouse — S3-backed, Spark for processing, Snowflake for consumption, dbt for transformation. Delta Lake was the default choice. Everyone on the team had used it before. The documentation was solid. It worked — until it didn’t.

    This isn’t a “here are the specs” comparison. You can get that from the docs. This is what actually happened when I ran both in production, why I made the switch, and what I’d tell you before you pick one.


    What We Were Actually Trying to Solve

    Before I get into the comparison, context matters. Our stack at the time: raw data landing in S3, Apache Spark for heavy transformation, Snowflake as the consumption layer for analysts, dbt for modeling, and Apache Airflow for orchestration.

    We needed ACID transactions on S3, time travel for debugging, and the ability to do incremental loads without full partition rewrites. Delta Lake checked all those boxes — initially. The problems showed up at scale and at the edges.


    Where Delta Lake Started Hurting Us

    Engine Lock-In Was a Real Problem

    Delta Lake works great if Spark is your only compute engine. The moment we tried to query Delta tables directly from Snowflake or Trino, things got complicated. Delta’s transaction log format is proprietary. You need the Delta connector — and not every engine has a first-class one.

    We wanted analysts to query raw lakehouse tables directly from Snowflake without going through Spark first. With Delta, that required Snowflake’s Delta Sharing integration, which had limitations on what operations were supported. It wasn’t broken, but it added friction and another dependency to manage.

    Apache Iceberg solves this cleanly. The table format is open. Snowflake, Spark, Flink, Trino, Athena, Dremio — they all read and write Iceberg natively. No connectors to manage. No format translation layer.

    Partition Management Was Getting Messy

    With Delta Lake, partitioning decisions are set at table creation. Changing a partition scheme means rewriting the table. At 100M+ rows, that’s not a quick operation.

    We had a table partitioned by event_date. Six months in, query patterns changed — analysts were filtering by event_date and region together. Repartitioning meant a full backfill job over a weekend, plus repointing all downstream dbt models.I wrote about a similar pain point in the problem with dbt incremental models — the pattern is the same.

    Iceberg’s partition evolution lets you change the partition spec without rewriting data. Old data stays as-is. New data uses the new scheme. Queries still work against both.

    Hidden Partitioning Changed How We Design Tables

    Iceberg supports hidden partitioning — you define partition transforms like days(event_timestamp) or bucket(user_id, 16) and Iceberg handles physical partitioning transparently. Your queries don’t need to know about partition columns. The engine prunes automatically.

    With Delta Lake, you need to explicitly filter on partition columns or you’ll scan everything. That’s fine when everyone knows the rules. It’s a problem when a new analyst writes a query without knowing which columns are partition keys.


    Where Delta Lake Is Still Better

    If you’re on Databricks, stay on Delta. The integration is tight, the tooling is mature, and Databricks has invested heavily in Delta’s performance.. Liquid Clustering makes partition management much more flexible. If Databricks is your primary compute layer, switching to Iceberg gives you marginal benefit for non-trivial migration cost.

    Delta’s MERGE performance on Spark is excellent. For high-frequency CDC workloads where you’re doing upserts at scale on Spark, Delta’s MERGE implementation is well-optimised. Iceberg’s MERGE has improved significantly but Delta still has an edge in some Spark-specific CDC patterns.

    Delta has simpler operational overhead for small teams. Delta’s transaction log is easier to reason about. The tooling for vacuum, optimize, and Z-ordering is well-documented and predictable.


    The Comparison You Actually Need

    Feature Delta Lake Apache Iceberg
    Engine supportSpark-native; connectors for othersTruly multi-engine (Spark, Flink, Trino, Snowflake, Athena)
    Partition evolutionRequires full table rewriteSchema-safe, no data rewrite needed
    Hidden partitioningNot supportedSupported — engines auto-prune
    MERGE / CDC performanceExcellent on SparkStrong, improving; slightly behind Delta on Spark CDC
    Vendor alignmentDatabricks ecosystemVendor-neutral, Apache foundation
    Operational toolingMature, well-documentedMaturing fast; strong in 2024–2025
    Multi-cloud flexibilityPossible but frictionFirst-class support across clouds
    Migration effortN/A (starting point)Non-trivial; plan 4–8 weeks

    THE MIGRATION: WHAT IT ACTUALLY COST US

    The Migration: What It Actually Cost Us

    I’ll be direct: the migration was harder than I expected. If you’ve read my piece on automation in data engineering, you’ll recognise the pattern — the technical part is rarely the hard part. It’s the downstream work nobody accounts for.

    The core work wasn’t the data conversion — we used the delta-iceberg migration utility and it handled most of the heavy lifting. The harder parts were everything else.

    Downstream dependency mapping.

     Every dbt model, every Airflow DAG, every Spark job that referenced a Delta table path needed updating. We had 40+ models. Two had hardcoded partition paths we didn’t catch until QA.

    Metadata catalog updates.

     We use AWS Glue Data Catalog. Every table needed its metadata updated to reflect the Iceberg format. Glue’s Iceberg support has improved, but it’s not frictionless.

    Testing the rollback plan. We kept Delta tables live for 30 days post-migration with a cutover switch in Airflow. That meant double-writing during the transition window — additional storage cost and added pipeline complexity.

    ⚠️ The migration trap: The data conversion tooling works. What catches teams off guard is the downstream mapping work — every pipeline, model, and job that references a table path. Budget more time for that than for the actual format conversion.

    Total elapsed time: six weeks. Two engineers. Not a weekend project.


    When to Choose Delta Lake

    • Your primary compute layer is Databricks
    • You’re a small team that wants simpler operations
    • You’re doing high-frequency CDC on Spark
    • You’re early stage — get something working first

    When to Choose Iceberg

    • You’re running multiple query engines (Spark + Snowflake, Trino + Flink)
    • You need partition evolution without full table rewrites
    • You’re building a vendor-neutral architecture
    • Your analysts query the lakehouse directly from Snowflake

    What I’d Do Differently

    Start with Iceberg if you’re greenfield. The setup is slightly more involved, but you avoid the migration cost entirely. The ecosystem has matured enough in 2024-2025 that “Iceberg is less mature” is no longer a strong argument.

    If you’re already on Delta and it’s working — don’t migrate for the sake of it. Migrate when you hit a specific limit: engine lock-in, partition inflexibility, or multi-cloud requirements.

    And if you do migrate, don’t underestimate the downstream mapping work. The data conversion is the easy part.


    Frequently Asked Questions

    What is the main difference between Delta Lake and Apache Iceberg?

    Delta Lake is a table format developed by Databricks, optimised for Spark workloads with strong Databricks integration. Apache Iceberg is an open table format designed for multi-engine environments — it works natively with Spark, Flink, Trino, Snowflake, and Athena without custom connectors. The core difference is engine flexibility.

    Is Apache Iceberg better than Delta Lake?

    It depends on your stack. Iceberg is better if you’re running multiple query engines or building a vendor-neutral architecture. Delta Lake is better if Databricks is your primary compute layer. Neither format is objectively superior.

    Can Snowflake read Delta Lake tables?

    Yes, through Delta Sharing or Snowflake’s Delta connector — but with limitations. Snowflake reads Iceberg tables natively as a first-class citizen, which is why multi-engine stacks tend to favour Iceberg.

    How hard is it to migrate from Delta Lake to Apache Iceberg?

    Harder than most blog posts suggest. The data conversion tooling handles the format migration, but remapping downstream pipelines, updating metadata catalogs, and testing rollback scenarios adds significant effort. Budget four to eight weeks for a production migration with 30–50 tables.

    Does dbt support Apache Iceberg?

    Yes. dbt supports Iceberg through the Spark and Athena adapters, and Snowflake’s Iceberg table support works with dbt models running on Snowflake. Production-ready as of 2024.

    What is hidden partitioning in Apache Iceberg?

    Hidden partitioning lets Iceberg manage partition logic transparently. You define partition transforms like days(event_timestamp) at the table level, and Iceberg handles physical file organisation and query pruning automatically — no need to filter on partition columns explicitly.

  • Snowflake Managed Iceberg Tables 2026

    Snowflake Managed Iceberg Tables 2026

    ⚡ TL;DR (Too Long; Didn’t Read)

    What it is: Snowflake Managed Iceberg Tables store data in your cloud storage (S3, GCS, Azure) instead of Snowflake’s storage, while Snowflake manages the metadata and catalog.

    Key benefits:

    • Performance: Identical to native Snowflake tables (no slowdown)
    • Cost: 3x cheaper storage (cloud provider instead of Snowflake)
    • Multi-engine: Spark, Dbt, other tools can access same table
    • ACID: Full transaction guarantees, time travel, snapshots
    • Flexibility: Move data between tools without replication

    When to use:

    • ✅ Tables > 1TB (storage cost matters)
    • ✅ Multi-engine ecosystem (Spark + Snowflake)
    • ✅ Need 7+ year audit trails
    • ✅ Want cloud provider flexibility

    When NOT to use:

    • ❌ Snowflake-only ecosystem (native tables are fine)
    • ❌ Performance is critical (both are equal anyway)
    • ❌ Tables < 100GB (setup overhead not worth it)

    Setup time: 15 minutes (create external volume → create table)

    Cost difference: 10TB table = $280/month both ways, but Iceberg eliminates Snowflake storage lock-in

    Bottom line: If you’re paying $500+/month for Snowflake storage or need multi-engine access, migrate to Iceberg. Otherwise, native tables are fine.

    Introduction: The Evolution of Snowflake Table Formats

    In June 2024, Snowflake announced General Availability (GA) of Iceberg table support. Today in 2026, it’s matured into a critical capability for enterprises building modern lakehouses. If you’re still storing all your data in Snowflake-native format, you’re missing the flexibility and interoperability that Managed Iceberg Tables provide.

    This article is a comprehensive guide to understanding, implementing, and optimizing Snowflake Managed Iceberg Tables—based on official Snowflake documentation and real-world best practices.


    What is Apache Iceberg?

    Apache Iceberg is an open-source, high-performance table format designed to manage large-scale analytical datasets. Originally created by Netflix and donated to the Apache Software Foundation, Iceberg has evolved into the industry standard for modern data lakehouses.

    Key difference from traditional data lakes: Iceberg treats data as tables (with ACID guarantees), not just files in folders.

    Why Iceberg Matters

    ProblemTraditional Data LakesIceberg Solution
    Concurrent reads/writesFile-based conflictsACID transactions
    Schema changesManual rewritesSchema evolution
    PerformanceRead entire datasetPartition pruning + predicate pushdown
    Time travelNot possibleFull snapshot history
    Multi-engine accessData duplicationSingle source of truth

    Snowflake Managed Iceberg Tables: What’s Different?

    Snowflake introduced two types of Iceberg table support:

    1. Snowflake-Managed Iceberg Tables ⭐ (Recommended)

    What it means: Snowflake manages the catalog, metadata, and coordination.

    Characteristics:

    • ✅ Full read/write access
    • ✅ Full ACID transactions
    • ✅ Native Snowflake features (time travel, CLONE, etc.)
    • ✅ Performance parity with native Snowflake tables
    • ✅ Automatic metadata management
    • ✅ Supported by all Snowflake features (Cortex AI, Iceberg optimization, etc.)

    Storage: Data lives in your S3, GCS, or Azure Storage (you pay cloud provider)

    2. Externally-Managed Iceberg Tables

    What it means: External system (AWS Glue, Delta Lake, etc.) manages metadata.

    Characteristics:

    • ✅ Read-only access from Snowflake
    • ✅ Can write from external engines
    • ✅ 2x better performance than external tables
    • ❌ Limited Snowflake feature support
    • ❌ Manual refresh required

    Use case: Query datasets managed by Spark/Databricks while others write to them.


    Architecture: How Snowflake Managed Iceberg Tables Work

    Three-Layer Architecture

    ┌─────────────────────────────────────────────┐
    │  Catalog Layer                               │
    │  (Snowflake manages metadata pointers)       │
    │  - Table names & locations                  │
    │  - Current metadata file pointers            │
    │  - Atomic metadata updates                  │
    └────────────────┬────────────────────────────┘
                     │
    ┌─────────────────┴────────────────────────────┐
    │  Metadata Layer                               │
    │  (Stored in External Cloud Storage)          │
    │  - Table snapshots (version history)         │
    │  - Manifest files (which data files used)    │
    │  - Statistics (min/max, row counts)          │
    │  - Schema definitions                        │
    └────────────────┬────────────────────────────┘
                     │
    ┌─────────────────┴────────────────────────────┐
    │  Data Layer                                   │
    │  (Parquet files in your cloud storage)       │
    │  - Actual data in Parquet format             │
    │  - Organized by snapshots/versions           │
    │  - You pay cloud storage provider            │
    └──────────────────────────────────────────────┘

    Key insight: Snowflake manages catalog & metadata. You manage data storage costs (billed by cloud provider).


    Snowflake Managed Iceberg vs. Native Tables: Real Performance Comparison

    Snowflake-managed Iceberg tables perform at parity with Snowflake native tables while storing data in public cloud storage.

    Performance Metrics (2026)

    MetricNative TableSnowflake-Managed IcebergExternal TableExternally-Managed Iceberg
    Query SpeedBaseline98-100%40-50%80-90%
    Write SpeedBaseline98-100%N/AN/A
    Storage LocationSnowflakeYour cloudYour cloudYour cloud
    Storage CostSnowflake (expensive)Cloud provider (cheaper)Cloud provider (cheaper)Cloud provider (cheaper)
    Read-WriteFullFullRead-onlyRead/Limited write

    Reality: If query performance is your only concern, go native. If cost matters, Managed Iceberg wins.


    Setting Up Snowflake Managed Iceberg Tables

    Step 1: Create External Volume

    The external volume is the connection between Snowflake and your cloud storage.

    AWS S3:

    -- Create external volume for Iceberg tables
    CREATE OR REPLACE EXTERNAL VOLUME iceberg_storage
      STORAGE_LOCATIONS = 
        (('s3://my-bucket/iceberg/', 
          ROLE_ARN = 'arn:aws:iam::123456789012:role/snowflake-role'));
    
    -- Verify connection
    DESC EXTERNAL VOLUME iceberg_storage;

    Google Cloud Storage:

    CREATE OR REPLACE EXTERNAL VOLUME iceberg_gcs
      STORAGE_LOCATIONS = 
        (('gs://my-bucket/iceberg/', 
          GCS_ACCESS_TOKEN = 'YOUR_TOKEN'));

    Azure Blob Storage:

    CREATE OR REPLACE EXTERNAL VOLUME iceberg_azure
      STORAGE_LOCATIONS = 
        (('azure://mycontainer/iceberg/', 
          AZURE_SAS_TOKEN = 'YOUR_SAS_TOKEN'));

    Step 2: Create an Iceberg Table

    Option A: Create empty Iceberg table

    sql

    -- Create managed Iceberg table in Snowflake
    CREATE OR REPLACE ICEBERG TABLE my_iceberg_data (
      customer_id INT,
      customer_name VARCHAR,
      email VARCHAR,
      signup_date DATE,
      lifetime_value DECIMAL(10, 2)
    )
    CATALOG = 'SNOWFLAKE'
    EXTERNAL_VOLUME = 'iceberg_storage'
    PARTITION BY (DATE_TRUNC('MONTH', signup_date));

    Option B: Create from existing data

    -- Convert native table to Iceberg
    CREATE OR REPLACE ICEBERG TABLE customer_iceberg AS
    SELECT * FROM snowflake_native_table;

    Option C: Convert existing Iceberg table from external catalog

    -- Convert externally-managed to Snowflake-managed
    -- No data rewrite, just metadata conversion
    ALTER ICEBERG TABLE external_iceberg_table
    CONVERT TO MANAGED CATALOG;

    Step 3: Load Data

    -- Insert data
    INSERT INTO my_iceberg_data VALUES
      (1, 'John Doe', '[email protected]', '2024-01-15', 5000.00),
      (2, 'Jane Smith', '[email protected]', '2024-02-20', 8500.00);
    
    -- Bulk load with COPY INTO
    COPY INTO my_iceberg_data
    FROM @stage_name/file.parquet
    FILE_FORMAT = (TYPE = 'PARQUET')
    MATCH_BY_COLUMN_NAME = CASE_INSENSITIVE;
    
    -- Use Snowpipe for continuous ingestion
    CREATE PIPE customer_ingest AS
      COPY INTO my_iceberg_data
      FROM @stage_name
      FILE_FORMAT = (TYPE = 'PARQUET')
      AUTO_INGEST = TRUE;

    Step 4: Query the Iceberg Table

    -- Standard SQL—no difference
    SELECT 
      customer_name,
      COUNT(*) as purchase_count,
      AVG(lifetime_value) as avg_value
    FROM my_iceberg_data
    WHERE signup_date >= '2024-01-01'
    GROUP BY customer_name;
    
    -- Time travel (snapshot history)
    SELECT * FROM my_iceberg_data
      AT (TIMESTAMP => '2025-12-15 10:00:00'::TIMESTAMP);
    
    -- View snapshots
    SELECT * FROM TABLE(
      INFORMATION_SCHEMA.ICEBERG_TABLE_SNAPSHOTS('my_iceberg_data')
    );

    Real-World Use Cases

    Use Case 1: Multi-Engine Analytics

    Problem: Data team uses Snowflake, ML team uses Spark, Analytics team uses Dbt/SQL.

    Solution: Single Iceberg table, multiple compute engines.

    -- Create table in Snowflake
    CREATE OR REPLACE ICEBERG TABLE ml_features (
      feature_id INT,
      feature_name VARCHAR,
      feature_value FLOAT,
      created_date TIMESTAMP
    )
    CATALOG = 'SNOWFLAKE'
    EXTERNAL_VOLUME = 'shared_storage';
    
    -- Snowflake reads/writes
    INSERT INTO ml_features 
    SELECT * FROM raw_data_snowflake;
    
    -- Spark can read/write same table
    # df.write.mode("append").parquet("s3://bucket/iceberg/ml_features")
    
    -- Dbt can materialize as Iceberg
    -- dbt_project.yml: table_format = 'iceberg'

    Benefits:

    • ✅ Single source of truth
    • ✅ No data duplication
    • ✅ Concurrent reads/writes (ACID guarantees)
    • ✅ 50% storage savings vs. duplicate tables

    Use Case 2: Cost Optimization (Iceberg vs. Native)

    Scenario: 10TB customer data table, mostly queried for recent data.

    Native Snowflake Table:

    • Storage cost: 10TB × $23/TB/month = $230/month
    • Compute (queries): $50/month
    • Total: $280/month

    Managed Iceberg Table:

    • Storage cost: 10TB × $0.023/GB (S3 standard) = $230/month (to cloud provider, not Snowflake)
    • Compute (Snowflake): $50/month
    • Total: $280/month cost, but…
      • Snowflake storage is gone (massive long-term savings)
      • Cloud storage is cheaper if using Intelligent-Tiering
      • Performance is identical

    Real savings: Over 2 years, 30-40% reduction by moving to Iceberg.


    Use Case 3: Time Travel & Compliance

    Scenario: Financial data needs 7-year audit trail with point-in-time reconstruction.

    -- Create Iceberg table with retention
    CREATE OR REPLACE ICEBERG TABLE transactions (
      txn_id INT,
      account_id INT,
      amount DECIMAL,
      txn_date TIMESTAMP
    )
    EXTERNAL_VOLUME = 'compliance_storage'
    PARTITION BY YEAR(txn_date)
    DATA_RETENTION_TIME_IN_DAYS = 2555;  -- 7 years
    
    -- Query specific point in time
    SELECT * FROM transactions
      AT (TIMESTAMP => '2023-06-15 09:00:00'::TIMESTAMP)
    WHERE account_id = 12345;
    
    -- See all snapshots (audit trail)
    SELECT 
      snapshot_id,
      committed_at,
      summary
    FROM TABLE(INFORMATION_SCHEMA.ICEBERG_TABLE_SNAPSHOTS('transactions'))
    ORDER BY committed_at DESC;

    Benefits:

    • ✅ Full audit trail
    • ✅ Regulatory compliance
    • ✅ Immediate point-in-time recovery
    • ✅ No separate backup infrastructure

    Pricing: How Much Do Managed Iceberg Tables Cost?

    What Snowflake Charges You

    ServiceCost
    Compute (queries)Standard warehouse rates (1 credit = $2-4 per second of compute)
    Cloud ServicesTypically 10-20% overhead on compute
    Automatic ClusteringOptional, billed separately if enabled
    SnowpipeCredits for data loading
    Cross-region data transfer$0.02-0.10/GB depending on regions

    What Cloud Provider Charges You

    ProviderCost
    AWS S3 storage$0.023/GB/month (standard tier)
    Google Cloud Storage$0.020/GB/month
    Azure Blob$0.0184/GB/month

    Real Cost Example: 10TB Iceberg Table

    Monthly costs:
    
    Snowflake (compute + services):
      - 1,000 queries × 2 credits avg = 2,000 credits
      - 2,000 credits × $3/credit = $6,000/month
    
    Cloud Storage (S3):
      - 10TB × $0.023/GB = 10,240GB × $0.023 = $235/month
    
    Total: $6,235/month
    
    Compare to native Snowflake table:
      - Compute: $6,000/month (same)
      - Snowflake storage: 10TB × $23/TB = $230/month
      - Total: $6,230/month
    
    Verdict: Same cost short-term, but:
      - Iceberg gives you cloud flexibility (migrate engines)
      - Iceberg allows multi-engine access
      - Iceberg enables cost optimization strategies

    Optimization: Getting the Most Out of Managed Iceberg Tables

    Optimization 1: Set Target File Size

    Snowflake automatically compacts files, but you can guide it:

    -- Optimize for query performance
    ALTER ICEBERG TABLE my_iceberg_data
    SET (ICEBERG_CONFIG = '{
      "write.target-file-size-bytes": 134217728  -- 128MB, default for balance
    }');
    
    -- For smaller frequent updates
    SET (ICEBERG_CONFIG = '{
      "write.target-file-size-bytes": 67108864  -- 64MB, more files but faster updates
    }');
    
    -- For big analytics (fewer files)
    SET (ICEBERG_CONFIG = '{
      "write.target-file-size-bytes": 536870912  -- 512MB, fewer files, better scan
    }');

    Optimization 2: Partitioning Strategy

    -- Good: Partition by frequently filtered column
    CREATE ICEBERG TABLE events (
      event_id INT,
      user_id INT,
      event_type VARCHAR,
      event_date DATE,
      event_time TIMESTAMP
    )
    PARTITION BY (event_date, event_type);  -- Most queries filter by date & type
    
    -- Query on partitioned columns: Scans only relevant files
    SELECT * FROM events
    WHERE event_date = '2026-01-15'
      AND event_type = 'purchase';  -- Fast: only 1 partition scanned

    Optimization 3: Use Automatic Clustering (Optional)

    -- Enable auto-clustering on hot columns
    ALTER ICEBERG TABLE my_iceberg_data
    CLUSTER BY (customer_id, signup_date);
    
    -- Check clustering quality
    SELECT 
      table_name,
      clustering_key,
      ave_depth_per_dimension,
      total_depth_per_dimension,
      depth_improvement_percent
    FROM INFORMATION_SCHEMA.CLUSTERING_INFORMATION
    WHERE table_name = 'my_iceberg_data';

    Cost: Automatic Clustering is billed separately at ~0.5-2 credits per GB/day reorganized. Use only for frequently queried columns.

    Optimization 4: Remove Orphan Files

    Failed transactions sometimes leave orphan Parquet files in cloud storage (tracked but unreferenced).

    -- Check for orphan files (manual process)
    -- Snowflake doesn't auto-remove them yet
    -- Use this to identify storage waste:
    
    SELECT 
      table_name,
      active_bytes,
      retained_bytes,
      (retained_bytes - active_bytes) as orphan_bytes
    FROM ACCOUNT_USAGE.TABLE_STORAGE_METRICS
    WHERE table_schema = 'your_schema'
      AND (retained_bytes - active_bytes) > 0;
    
    -- If discrepancy found, contact Snowflake Support for cleanup

    Snowflake Managed Iceberg vs. Alternatives

    vs. Native Snowflake Tables

    AspectIcebergNative
    PerformanceEqual (parity)Equal (parity)
    Storage locationYour cloudSnowflake owned
    Storage costCloud providerSnowflake (3x more)
    Time TravelSnapshotsUp to 90 days
    Multi-engineYes (Spark, Dbt, etc.)No
    Schema evolutionNative supportRequires ALTER
    Setup complexityMedium (needs external volume)Low
    When to useCost-sensitive, multi-enginePerformance-first, Snowflake-only

    vs. External Tables

    AspectIcebergExternal Tables
    Performance2x betterBaseline
    Write supportFullNo (read-only)
    ACIDYesNo
    Time TravelYesNo
    Supported formatsParquet onlyCSV, Avro, ORC, Parquet
    SetupMediumSimple
    Use caseModern lakehouseLegacy data lake query

    Common Gotchas & Solutions

    Gotcha 1: Cross-Cloud/Cross-Region Not Supported

    Problem: You can’t create Iceberg table with S3 storage while Snowflake account is in Azure.

    -- ❌ This will fail
    CREATE ICEBERG TABLE cross_cloud_table (...)
    EXTERNAL_VOLUME = 'aws_s3_volume';  -- Error if in Azure
    
    -- ✅ Use same cloud as account
    -- If you really need cross-cloud, use catalog integration instead

    Solution: Keep Snowflake and storage in same cloud region, or use Catalog Integration for cross-cloud.

    Gotcha 2: Orphan File Accumulation

    Problem: Failed transactions leave behind Parquet files you still pay storage for.

    -- Monitor storage metrics
    SELECT 
      table_name,
      DATEDIFF(day, last_modified, current_date) as days_since_update,
      active_bytes,
      retained_bytes
    FROM ACCOUNT_USAGE.TABLE_STORAGE_METRICS
    WHERE TABLE_TYPE = 'ICEBERG'
      AND database_name = 'your_db';
    
    -- If gap between active_bytes and retained_bytes, contact Snowflake Support

    Solution: Snowflake is working on auto-cleanup. Until then, monitor and contact support if discrepancies appear.

    Gotcha 3: Refresh Required for Externally-Managed Tables

    Problem: Changes from external systems (Spark, Delta) aren’t immediately visible.

    -- For externally-managed tables only:
    ALTER ICEBERG TABLE external_table REFRESH;
    
    -- Set up automated refresh
    CREATE TASK refresh_external_table
      WAREHOUSE = compute_wh
      SCHEDULE = '5 MINUTES'
    AS
      ALTER ICEBERG TABLE external_table REFRESH;

    Solution: Always refresh before querying externally-managed Iceberg tables. Or use Snowflake-managed (no refresh needed).


    FAQ: Answering Common Questions

    Should I convert all my native tables to Iceberg?

    Not necessarily. Convert if:

    • ✅ You need multi-engine access
    • ✅ Storage cost is significant (>$500/month)
    • ✅ You want cloud provider flexibility
    • ✅ You need better compliance/audit trails

    Keep native if:

    • ✅ Performance is critical (though Iceberg matches)
    • ✅ All usage is Snowflake-only
    • ✅ Snowflake storage is included in your contract

    How do I migrate from native to Iceberg?

    -- Option 1: Zero-copy (create table as select)
    CREATE OR REPLACE ICEBERG TABLE new_iceberg AS
    SELECT * FROM native_table;
    
    -- Then rename
    ALTER TABLE native_table RENAME TO native_table_old;
    ALTER TABLE new_iceberg RENAME TO native_table;
    
    -- Option 2: ALTER (if you have external volume setup)
    -- Current Snowflake doesn't support direct ALTER, use Option 1

    Can Spark write to Snowflake-managed Iceberg tables?

    Not directly via Spark. Snowflake-managed catalog is Snowflake-exclusive. But Spark can read them:

    # Spark read (supported)
    df = spark.read.iceberg("iceberg/snowflake_managed_table")
    
    # Spark write (not supported to Snowflake-managed tables)
    # Use externally-managed tables instead for multi-write scenarios

    What’s the performance overhead of Iceberg?

    Zero. Snowflake-managed Iceberg tables perform at parity with native Snowflake tables.


    Real-World Implementation Checklist

    1: Planning (Week 1)

    • Identify tables for Iceberg migration (large, multi-access)
    • Calculate current storage costs
    • Choose cloud storage (S3, GCS, Azure)
    • Plan partition strategy
    • Identify multi-engine requirements

    2: Setup (Week 2-3)

    • Create cloud storage bucket
    • Set up IAM roles/permissions
    • Create external volume in Snowflake
    • Create test Iceberg table
    • Load sample data (1% of production)
    • Run performance benchmarks

    3: Migration (Week 4-6)

    • Create Iceberg tables (use CREATE AS SELECT)
    • Validate data integrity
    • Update ETL pipelines
    • Update queries (usually no changes needed)
    • Monitor performance & costs
    • Archive old native tables (don’t delete yet)

    4: Optimization (Ongoing)

    • Monitor storage costs
    • Review partition effectiveness
    • Enable automatic clustering if needed
    • Set up orphan file monitoring
    • Plan for multi-engine access

    Key Takeaways

    1. Snowflake-managed Iceberg tables are production-ready – GA since June 2024, widely adopted
    2. Performance is identical to native tables – No trade-off
    3. Storage costs are lower – Cloud provider rates beat Snowflake
    4. Multi-engine access enabled – Spark, Dbt, other engines can use same data
    5. Time travel & ACID built-in – Full transaction guarantees
    6. External volume is required – Setup takes 15 minutes
    7. Pricing is predictable – Compute (Snowflake) + Storage (cloud provider)
    8. Not a magic bullet – Only migrate if you have specific use cases (cost, multi-engine, flexibility)

    External References (Official Snowflake Docs)


    Next Steps

    1. Assess your tables – Which ones would benefit from Iceberg?
    2. Create an external volume – Takes 15 minutes
    3. Run a pilot – Create Iceberg table from 1% of production data
    4. Benchmark – Compare performance with native table
    5. Plan migration – Identify production timeline
    6. Scale gradually – Don’t convert everything at once

    Disclaimer: Information current as of January 2026. Always verify with official Snowflake documentation for latest features and capabilities. Pricing and features subject to change.