Skip to content
Stribog

Data

All writing

Sovereign Lakehouse: ClickHouse, Iceberg, and DuckLake Past Snowflake

Self-hosted ClickHouse vs Snowflake: build a sovereign lakehouse with ClickHouse, Apache Iceberg, and DuckLake for teams past the warehouse cost ceiling.

Stribog13 min read
sovereigntyoptionalityopen source

Snowflake did something genuinely valuable: it made a data warehouse feel like a URL. Separate storage from compute, hide the cluster, bill by the second, and analytics teams stopped thinking about infrastructure at all. That convenience is also the trap. The same architecture that makes the first query effortless makes the ten-thousandth a line item, and by the time a data platform is load-bearing — dashboards the business runs on, pipelines that feed models, analysis a hundred analysts lean on daily — the credit burn has become one of the largest and least controllable costs in engineering. The question that starts every exit is not "is Snowflake good?" — it plainly is. It is "why is our warehouse bill growing faster than our data, and what do we own at the end of it?"

The honest answer: at the end of a Snowflake relationship you own your data — in Snowflake's format, in Snowflake's account, queryable by Snowflake's engine. That is the lock-in that matters, and it is not malicious; it is architectural. Storage, table format, catalog, and compute are one fused system, so leaving means re-platforming all four at once. For years there was no credible alternative that preserved the ergonomics. In 2026 there is, and it is not a single product — it is a small set of open components that compose into a lakehouse you assemble yourself: ClickHouse for fast serving, Apache Iceberg as the open table format, and the newly production-ready DuckLake as a SQL-native catalog. It is the analytical counterpart to the transactional-data sovereignty in running Postgres on Kubernetes with CloudNativePG: OLTP you own, and now OLAP you own too.

This is not a rip-and-replace pitch but an architecture and a decision framework for teams that have hit Snowflake's cost ceiling and want their analytics platform to be an asset they control, not a meter they feed — and, because audit-grade rigor means saying so, it marks where self-hosting is the wrong call.

Where Snowflake's Cost Curve Bends

Consumption pricing is honest when usage is spiky and dishonest about steady state. Snowflake bills credits per second of active warehouse time, and its great trick is auto-suspend: an idle warehouse costs nothing. For a few analysts and a nightly batch, that elasticity is genuinely cheaper than owning idle iron — a fair comparison concedes this. The curve bends when warehouses stop idling. Real-time dashboards that must stay warm, streaming ingestion, reverse-ETL, and a growing analyst population all push toward always-on — and then you are renting compute by the second that runs by the hour, with a meter that never suspends.

The magnitudes are now well documented. In published 2026 comparisons, ClickHouse runs roughly 5–20× cheaper than Snowflake for continuously-running analytics, with self-hosted ClickHouse around 3–5× cheaper for equivalent capacity. One frequently-cited example puts a Snowflake X-Large warehouse running eight hours a day near $5,600 per month against self-hosted ClickHouse on a handful of commodity 16-core servers in the low hundreds. On ingestion, a benchmark loaded a large dataset into ClickHouse in about 5,400 seconds for roughly $41, versus a Snowflake 2X-Large at about 11,400 seconds for roughly $202 — twice as slow and five times the cost. These are attributable figures, not marketing rounding, and they point the same way whenever utilization is high.

The Anatomy of a Sovereign Lakehouse

A fused warehouse locks you in because it collapses four independent concerns into one product. A lakehouse pulls them back apart into named, replaceable seams — understanding those seams is the whole game. There are four:

  • Storage — columnar files (Parquet) on S3-compatible object storage you own. This is the sovereignty foundation: the bytes live on MinIO, Ceph, or SeaweedFS in your own racks, not in a vendor account. Choosing that store is itself a decision we cover in the sovereign object-storage guide.
  • Table format — the metadata layer that turns a pile of Parquet files into a table with schema, snapshots, and ACID semantics. Apache Iceberg is the open standard; DuckLake is the 2026 challenger. This is what makes the files a database instead of a directory.
  • Catalog — the registry that tracks which tables exist and where their current metadata lives. An Iceberg REST catalog or DuckLake's SQL catalog plays this role. It is the piece every engine must agree on.
  • Compute — the query engines. ClickHouse for low-latency serving, Trino for federated ad-hoc SQL, DuckDB for single-node and pipeline work. Crucially, plural: many engines over one copy of the data.
Storage, table format, catalog, and compute are separate seams — each independently replaceable. A fused proprietary warehouse (right) welds all four into one exit-resistant box.

The payoff of the split is that every seam is a swap, not a migration. Outgrow ClickHouse for a workload and you point Trino at the same tables — no data movement. When Iceberg gives way to a better format, you rewrite metadata, not petabytes. That is the anti-lock-in property a fused warehouse structurally cannot offer — the reason the lakehouse is a sovereignty architecture, not merely a cheaper one.

ClickHouse: The Serving Engine That Earns Its Keep

ClickHouse makes the cost story real, because it is genuinely fast on the workloads that keep Snowflake warehouses warm: aggregations and filters over large columnar datasets at interactive latency. In head-to-head 2026 benchmarks it completes hot aggregation queries in a fraction of the time — figures around 0.28 seconds versus 0.75 seconds for a comparable Snowflake query recur — roughly two to three times faster on the queries dashboards issue thousands of times a day. Its MergeTree engine sorts data on disk by a key you choose, so range and filter queries skip most of the data they would otherwise scan.

The pattern that replaces a Snowflake serving warehouse is a MergeTree table plus a materialized view that pre-aggregates on ingest: the rollup happens once at write time, not on every dashboard load. That is why ClickHouse serves the same dashboard for a fraction of the compute:

sql
-- Raw events, sorted for the queries the dashboard actually runs.
CREATE TABLE events
(
    event_time  DateTime,
    tenant_id   UInt32,
    event_type  LowCardinality(String),
    revenue     Decimal(18, 2)
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(event_time)
ORDER BY (tenant_id, event_type, event_time);

-- Pre-aggregate on ingest: the rollup is computed once, at write time.
CREATE MATERIALIZED VIEW events_daily_mv
ENGINE = SummingMergeTree
PARTITION BY toYYYYMM(day)
ORDER BY (tenant_id, event_type, day)
AS SELECT
    tenant_id,
    event_type,
    toStartOfDay(event_time) AS day,
    sum(revenue)             AS revenue,
    count()                  AS events
FROM events
GROUP BY tenant_id, event_type, day;

-- The dashboard reads the tiny rollup, not the raw firehose.
SELECT day, sum(revenue) FROM events_daily_mv
WHERE tenant_id = 42 AND day >= today() - 30
GROUP BY day ORDER BY day;
A ClickHouse serving table with an incremental materialized view

ClickHouse can also read Iceberg tables directly through its iceberg table function, which matters architecturally: hot, high-QPS data lives in native MergeTree for speed, while colder history stays in open Iceberg tables on object storage and is queried in place — native performance where latency is the product, open-format portability where volume is the cost, without copying cold data into a proprietary store to read it.

Apache Iceberg and the REST Catalog Contract

Iceberg is the open table format that makes multi-engine access safe. It layers manifests and snapshots over Parquet to provide schema evolution, hidden partitioning, time travel, and — critically — atomic commits, so two engines writing the same table do not corrupt each other. Trino's Iceberg connector is among the most mature in the ecosystem, supporting time travel, row-level deletes, and every major catalog backend. The format is not the hard part in 2026; the catalog is.

The Iceberg REST catalog specification is the quiet breakthrough. Historically every engine needed a bespoke connector for every catalog — an N×M integration matrix that made catalogs a lock-in point of their own. REST collapses it: implement the client once per engine and the server once per catalog, and any compliant engine talks to any compliant catalog over plain HTTP. For a sovereign deployment that is decisive, because the self-hosted, open-source REST catalogs are now real and boring in the best way:

  • Lakekeeper — written in Rust, ships as a single binary with no JVM or Python dependency; point it at a Postgres database and it starts serving the Iceberg REST spec. The lowest-overhead option for a small team.
  • Apache Polaris — a Quarkus/JVM server backed by Postgres, MySQL, or CockroachDB; the choice when you want a governed, multi-catalog control plane with role-based access built in.
  • Gravitino — a native Iceberg REST endpoint that doubles as a broader metadata lake for multiple table formats, if your estate is more than Iceberg alone.
properties
# etc/catalog/lakehouse.properties
connector.name=iceberg
iceberg.catalog.type=rest
iceberg.rest-catalog.uri=https://catalog.internal:8181
iceberg.rest-catalog.warehouse=s3://analytics/warehouse

# Object storage you own — S3-compatible, on your own network.
fs.native-s3.enabled=true
s3.endpoint=https://minio.internal:9000
s3.path-style-access=true
s3.region=us-east-1
# Credentials injected from your secrets manager, never in the file.
A Trino catalog pointed at a self-hosted Iceberg REST catalog and owned S3

Note what that configuration does not contain: any cloud provider, any managed service, any account you do not control. The catalog URL is an internal hostname, the storage endpoint is your own MinIO, and the credentials come from your secrets manager — the same External Secrets and Vault discipline you use everywhere else. The seams are HTTP and S3, both terminated inside your own perimeter.

DuckLake: SQL as the Catalog

DuckLake is the newest piece and the most interesting, because it reconsiders where lakehouse metadata should live. Iceberg keeps metadata as JSON and Avro manifest files in object storage; DuckLake keeps all of it in a plain SQL database — SQLite, Postgres, or DuckDB itself — while keeping the data as Parquet in object storage exactly like Iceberg. It reached a production-ready 1.0 in April 2026 with a backward-compatibility guarantee, its reference implementation shipping as a DuckDB extension. The bet: a transactional SQL database is a better substrate for catalog operations than a pile of files.

The practical wins follow. A snapshot commit is a database transaction, not a multi-file dance with a separate locking service, eliminating a class of consistency problems and small-file overhead. DuckLake 1.0 added data inlining, so tiny inserts and updates land in the catalog database instead of spawning thousands of small Parquet files. Attaching one is a single SQL statement:

sql
-- Metadata lives in Postgres; data files live in object storage you own.
INSTALL ducklake;
INSTALL postgres;

ATTACH 'ducklake:postgres:dbname=catalog host=pg.internal'
    AS lake (DATA_PATH 's3://analytics/ducklake/');

USE lake;

-- ACID DDL/DML — the commit is a Postgres transaction, not a file shuffle.
CREATE TABLE orders (id BIGINT, ts TIMESTAMP, amount DECIMAL(18,2));
INSERT INTO orders VALUES (1, now(), 42.00);

-- Time travel comes free from the SQL-tracked snapshots.
SELECT count(*) FROM orders AT (VERSION => 1);
Attaching a DuckLake catalog: Postgres holds metadata, object storage holds data

DuckLake is not a replacement for Iceberg in every case — Iceberg has the broader engine ecosystem and is the safer default for a large, heterogeneous estate. But DuckLake's clients already span Apache Spark, Trino, DataFusion, and pandas, and for a team centered on DuckDB and Postgres it is a strikingly simple, sovereign catalog: no extra JVM service, no separate metastore, just a database you already run and back up. Both keep the data in open Parquet, so the choice between them is reversible.

Routing Queries to the Right Engine

The multi-engine model only pays off if you route deliberately: match each query pattern to the right engine while keeping exactly one copy of the data underneath all of them. Three patterns cover most of what a warehouse does:

Match the engine to the query pattern, but keep one copy of the data. Every engine reads the same Iceberg tables on storage you own — no duplication, no per-query credits, no egress.
  • Real-time serving — high-QPS dashboards and APIs with tight latency budgets go to ClickHouse, where MergeTree and materialized views deliver sub-second responses under concurrency.
  • Federated ad-hoc SQL — exploratory analysis and joins across many tables (including tables from other systems) go to Trino, which reads Iceberg through the REST catalog and federates across sources.
  • SQL-native and pipeline work — a data engineer's local exploration, transformation jobs, and embedded analytics go to DuckDB with DuckLake, single-node and dependency-light.

The shared substrate is what makes this cheap: all three engines read the same Parquet on the same object store, so routing is a configuration choice with zero data movement — where Snowflake would charge egress to move a workload elsewhere. You are not running three warehouses; you are running one dataset with three lenses, each the cheapest fast path for its query shape.

Governance and Audit-Grade Rigor

A self-hosted warehouse is only serious if its governance is at least as strong as the managed service it replaces — otherwise you have traded a bill for a liability. Disaggregation makes governance more legible, not less, because each seam has an explicit control point instead of a vendor console you cannot audit end to end. Access control lives at the catalog: Polaris ships role-based access over catalogs, namespaces, and tables, so "who can read which table" is a policy you write and inspect, with object-storage bucket policies and network policy as independent layers beneath it.

Auditability is where owning the stack genuinely wins. Iceberg's snapshots are an immutable, queryable history of every change to a table — time travel is not a convenience feature but an audit log that proves exactly what a dataset contained at any past instant, and because the metadata is open (JSON manifests for Iceberg, SQL rows for DuckLake) your compliance tooling reads it directly rather than asking a vendor API for a summary. When data residency is a legal requirement rather than a preference — the terrain of NIS2, DORA, and the EU AI Act for self-hosted infrastructure — a warehouse whose bytes provably never leave your jurisdiction is not a cost optimization; it is the only compliant architecture.

The Exit Ramp: Migrating Off Snowflake Without a Forklift

The strongest argument for this architecture is that it lets you leave it too: the migration off Snowflake is incremental, not a big-bang cutover. Move storage first and compute last, running both systems in parallel until the new path has earned trust. The sequence is four steps:

  1. Land the data openly — unload to Parquet on owned object storage; register as Iceberg tables in a self-hosted REST catalog. The data is now portable whatever happens next.
  2. Validate with Trino — run the existing query suite against both systems and diff the results, so correctness is proven before anything cuts over.
  3. Move the hot path to ClickHouse — migrate the always-on serving workloads first, where the cost curve bends most and the wins fund the rest.
  4. Decommission on your schedule — wind Snowflake down gradually, keeping it as a paid fallback until confidence is total.

Contrast that with the exit Snowflake offers: none that is cheap, because leaving a proprietary format means re-platforming all four layers at once. The lakehouse exit is incremental precisely because the layers are separate — you can be half-migrated for months and both halves work. It is the same repatriation discipline, applied to the warehouse tier, that the cloud-repatriation engineering playbook lays out: move one seam at a time, keep a fallback, never bet the business on a single cutover.

The Long Game: Own the Format, Not Just the Data

The deepest reason to build this way is temporal. Query engines are fast-moving and disposable — ClickHouse today, something better in five years — but the table format is the durable asset: the contract between your data and every engine that will ever read it. Betting on an open format (Iceberg, DuckLake) rather than a proprietary one means the decades of data you are accumulating stay readable by whatever engine wins next, without a migration. You are choosing not a warehouse for this decade's workload but a format for the next several decades of them.

This is the long game made concrete: the fused warehouse optimizes for the convenience of the next query, the open lakehouse for the optionality of the next decade. The team that owns its storage, table format, and catalog adopts every new engine as a free upgrade — point it at the same tables and go — while the team on a proprietary warehouse re-buys its freedom with every new tool. Sovereignty here is not ideology; it is a balance-sheet fact about which costs are fixed and falling versus recurring and rising.

Engines are disposable; the format is forever. Choose a warehouse for this year's query and you re-migrate every few years; choose an open table format and your data outlives every engine you ever run against it.
Stribog Engineering Practice

§FAQ/Common questions

Frequently asked

Is self-hosted ClickHouse actually cheaper than Snowflake?

For continuously-running analytics workloads, yes — published 2026 comparisons put ClickHouse roughly 5–20× cheaper than Snowflake, with self-hosted ClickHouse around 3–5× cheaper for equivalent capacity, because you pay for owned hardware rather than per-second compute credits. One worked example puts a Snowflake X-Large warehouse running eight hours a day near $5,600 per month against self-hosted ClickHouse on commodity servers in the low hundreds. The important caveat: for genuinely intermittent workloads that auto-suspend most of the day, Snowflake's consumption model can win. Measure your actual warehouse-active hours before modeling the exit — self-hosting pays at high utilization, not low.

What is a sovereign lakehouse and how does it differ from Snowflake?

A sovereign lakehouse disaggregates the four concerns a fused warehouse welds together: storage (Parquet on object storage you own), table format (Apache Iceberg or DuckLake), catalog (a self-hosted Iceberg REST catalog or DuckLake's SQL catalog), and compute (ClickHouse, Trino, DuckDB). Snowflake fuses all four into one proprietary product, so leaving means re-platforming everything at once. In a lakehouse each layer is an independent, replaceable open component, and the data never leaves infrastructure you control — which makes it both cheaper at scale and structurally exit-friendly.

What is DuckLake and how is it different from Apache Iceberg?

DuckLake is a lakehouse format that reached production-ready 1.0 in April 2026. Like Iceberg it stores data as Parquet files in object storage, but it keeps all catalog metadata in a SQL database (SQLite, Postgres, or DuckDB) rather than as JSON and Avro manifest files. That makes snapshot commits ordinary database transactions, eliminates small-file metadata overhead, and adds features like data inlining for tiny writes. Iceberg has the broader engine ecosystem and is the safer default for large heterogeneous estates; DuckLake is strikingly simple for teams centered on DuckDB and Postgres. Both keep data in open Parquet, so the choice is reversible.

Do I have to copy my data to use multiple query engines?

No — that is the point of the open table format. ClickHouse, Trino, and DuckDB all read the same Iceberg or DuckLake tables on the same object storage, so you keep exactly one copy of the data and route queries to whichever engine fits the pattern. ClickHouse serves latency-critical dashboards, Trino handles federated ad-hoc SQL, and DuckDB covers SQL-native single-node work. Because there is no per-engine copy and no egress, adding or swapping an engine is a configuration change with zero data movement — unlike a proprietary warehouse, where moving a workload to another tool means extracting data and paying egress.

How do I migrate off Snowflake without a risky big-bang cutover?

Move storage first and compute last, running both systems in parallel. Unload Snowflake tables to Parquet on your own object storage and register them as Iceberg tables; stand up a self-hosted REST catalog and validate correctness with Trino by diffing query results against Snowflake; migrate the hot, always-on serving workloads to ClickHouse where the cost wins are largest; then wind Snowflake down gradually, keeping the account as a fallback until confidence is total. Because the lakehouse layers are separate, you can be half-migrated for months with both halves working — there is no forced flag-day.

self-hosted ClickHouse vs Snowflakeexit Snowflake ClickHouse on-premApache Iceberg self-hosted data lakeDuckLake lakehouse format 2026ClickHouse cost savings vs Snowflakeself-hosted Iceberg REST catalog

Executive Briefing

Thirty minutes to clarify your infrastructure risk

Walk us through your vendor footprint and regulatory constraints. We will tell you honestly where sovereignty creates leverage — and where it does not. No pitch deck. No obligation.