ClickHouse can query an Apache Iceberg table sitting in S3 in a fraction of a second – no Spark job, with no ETL step and no need to load the data into ClickHouse’s own storage first. Using the ClickHouse Iceberg table function, you point ClickHouse at the table’s location, it reads the Iceberg metadata, figures out which files it actually needs, and returns rows.
This article focuses on exactly that: how to query Apache Iceberg tables from ClickHouse. If you still need to get events into an Iceberg table on S3, I covered the full Kafka → Kafka Connect → Iceberg pipeline in a separate walkthrough. Here we assume the table already exists and focus on querying Iceberg tables in S3 directly from ClickHouse.
This article describes an example architecture for educational purposes. Actual configurations, performance, and availability depend on customer environment, selected services, and applicable NetApp Instaclustr service terms.
Why query Apache Iceberg tables from ClickHouse instead of loading data first
The traditional pattern is to copy data into your query engine’s own storage before you can analyze it. That means a loading step, a second copy of the data, and a job to keep it in sync.
ClickHouse skips all of that. It’s a columnar OLAP engine built for analytical speed, and ClickHouse apache Iceberg integrations allow direct access to Iceberg tables stored in object storage. Iceberg’s metadata layer tells ClickHouse which files are relevant before it touches a single byte of data, so it reads only what the query needs. The result is warehouse-style query performance against data that never left S3.
The architectural payoff: S3 stays the single source of truth, and any engine that speaks Iceberg – ClickHouse, Spark, Trino, DuckDB – can query the same table without coordination.
Learn more: Apache Iceberg table format
What you need before you start:
- An Iceberg table already written to S3. If the table was registered in a catalog like AWS Glue then that’s fine. However, the queries in this article point ClickHouse at the S3 path directly, so ClickHouse reads the Iceberg metadata files from S3 without a catalog lookup.
- A running ClickHouse cluster in the same AWS region as your bucket. Same region S3 is reachable from the cluster by default; if your bucket lives in a different region, then you will also need the AWS S3 Region Integration under Integrations -> S3 Region.
- AWS permissions that let ClickHouse read the table.
The path matters here. Glue appends .db to the namespace name, so a table created in the analytics namespace lands at warehouse/analytics.db/user_events/. That’s the path you’ll point ClickHouse at.
Step 1: Grant ClickHouse read access
Querying Iceberg needs a much smaller set of permissions than writing to it. A read engine like ClickHouse just needs to list the bucket and fetch objects. The Iceberg metadata sits in S3 alongside the data, so no catalog calls are involved either.
For query engines reading Iceberg, the role or credentials used for querying need:
- Permission – Purpose
s3:GetObject– fetch Iceberg metadata files (metadata.json, manifest lists, manifest Avro files) and the underlying Parquet files.s3:ListBucket– List files in the warehouse
Then add a bucket policy so ClickHouse can read the data:
|
1 2 3 4 5 6 7 8 9 10 |
{ "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Principal": "*", "Action": ["s3:GetObject", "s3:ListBucket"], "Resource": ["arn:aws:s3:::<bucket>", "arn:aws:s3:::<bucket>/*"], "Condition": {"StringEquals": {"aws:PrincipalAccount": "<clickhouse-cluster-aws-account-id>"}} }] } |
Despite the "Principal": "*", this is not a public bucket. The Condition restricts access to a single AWS account – the account that the ClickHouse cluster runs in. if you’re on Bring Your Own Cloud (BYOC), that’s your own AWS account ID. If your cluster runs in Instaclustr’s AWS account, use Instaclustr‘s account ID instead.
Step 2: Query Apache Iceberg tables from ClickHouse with iceberg() table function
Open the ClickHouse Web UI and log in with your icclickhouse credentials. Now point the iceberg() table function at the table’s S3 location:
|
1 2 3 4 5 6 |
SELECT * FROM iceberg( 'https://<bucket>.s3.us-east-1.amazonaws.com/warehouse/analytics.db/user_events/', '<aws_access_key_id>', '<aws_secret_access_key>' ) LIMIT 10; |
That returns in well under a second. ClickHouse reads the Iceberg metadata, identifies which files to fetch, and returns results – no ETL, no loading step.
Aggregations work the same way, and this is where columnar storage earns its keep:
|
1 2 3 4 5 6 7 |
SELECT event_type, count() AS total FROM iceberg( 'https://<bucket>.s3.us-east-1.amazonaws.com/warehouse/analytics.db/user_events/', '<aws_access_key_id>', '<aws_secret_access_key>' ) GROUP BY event_type ORDER BY total DESC; |
Five rows, one per event type, in a fraction of a second. You’re querying data lake files as if they were a warehouse table.
Passing credentials inline works, but repeating them on every query gets old fast. ClickHouse supports named collections – a stored bundle of connection settings you reference by name – so a query becomes FROM iceberg(my_iceberg_creds) instead. See the Instaclustr Open Table Format integration docs for the full setup.
Step 3: Create a persistent Apache Iceberg table for repeated queries
The iceberg() function is perfect for ad-hoc exploration, but typing the full S3 URL every time gets old. For queries you run often, ClickHouse can read Iceberg tables through its IcebergS3 table engine, which lets you give the table a name and query it like any other ClickHouse table.
|
1 2 3 4 5 |
CREATE TABLE user_events ENGINE = IcebergS3( 'https://<bucket>.s3.us-east-1.amazonaws.com/warehouse/analytics.db/user_events/', '<aws_access_key_id>', '<aws_secret_access_key>' ); |
Same direct-read behavior underneath. ClickHouse still reads from S3 and prunes files using Iceberg metadata, but now the table has a stable name your dashboards and saved queries can reference.
Why it’s this fast
The speed of querying Apache Iceberg tables from ClickHouse isn’t magic; it’s metadata. Iceberg’s manifest files carry per-file statistics – min/max values, null counts, row counts. When ClickHouse runs a query with a WHERE clause, it reads those manifests, compares your predicate against the stats, and skips entire files that can’t contain matching rows. According to Apache Iceberg’s own performance docs, that pruning can deliver up to a 10x improvement on queries against clustered data – without reading a byte of the underlying Parquet files.
So ClickHouse isn’t brute-forcing a scan of your whole bucket. It asks Iceberg which files could possibly matter first, then reads only those. ClickHouse answers the question fast, ensuring Iceberg only has to look in the right place.
Summary
Querying ClickHouse with Apache Iceberg comes down to two things: grant read access (IAM permissions plus a scoped bucket policy), and point ClickHouse at the table with the iceberg() function or the IcebergS3 engine. After that, your data lake behaves like a warehouse – queryable at analytical speed while the data itself never leaves S3.
That’s the real shift: you’re not copying data into a query engine, you’re querying it where it already lives. S3 stays the source of truth, your stack stays portable, and any Iceberg-aware engine can read the same tables.
NetApp Instaclustr provides fully managed ClickHouse and Kafka with 100% open source technologies, no vendor lock-in, and 24×7 expert support. Start a free trial on Instaclustr to query Apache Iceberg on S3 with ClickHouse – no ETL or data duplication required.
Frequently Asked Questions
-
How do you query an Iceberg table from ClickHouse? +
Use the
iceberg()table function with the table’s S3 location, or define a named table with the IcebergS3 engine. ClickHouse reads the Iceberg metadata, determines which files it needs, and queries them directly from S3 – no loading step required. -
Does ClickHouse copy Iceberg data into its own storage? +
No. ClickHouse reads Iceberg tables directly from object storage. The data stays in S3 in open formats like Parquet, and ClickHouse fetches only the files a given query needs.
-
Why does my ClickHouse Iceberg query fail to reach S3? +
Same-region S3 is reachable from an Instaclustr ClickHouse cluster by default. If your bucket lives in a different region than the cluster, you’ll need to enable egress to that region under Integrations -> AWS S3 Region.
-
What permissions does ClickHouse need to read Iceberg? +
Read access only:
s3:GetObjectands3:ListBucketon the query role, plus a bucket policy allowings3:GetObjectands3:ListBucket, scoped to your AWS account with anaws:PrincipalAccountcondition. -
Why is querying Iceberg from ClickHouse so fast? +
Iceberg’s manifest files store per-file statistics, so ClickHouse can skip files that can’t match a query’s WHERE clause before reading any data. Combined with ClickHouse’s columnar engine, this keeps most analytical queries in the sub-second range.