-
Notifications
You must be signed in to change notification settings - Fork 246
[Proposal] Iceberg subsystem for datalake_fdw — design proposal #1683
ProposersProposal StatusUnder Discussion Abstract1. AbstractCloudberry does not have a complete set of plug-in tools for accessing various data sources. I plan to design a data lake approach to access these data sources, and evolve Cloudberry toward a data lake–enabled architecture.
This document focuses on the second part — the design, the key decisions, and the open questions — and is meant for community review. Motivation2. Motivation & Goals2.1 Why we need thisAs an MPP data warehouse, Cloudberry has long lacked a transactional read / write entry point for data-lake formats, Iceberg in particular:
The Iceberg subsystem aims to introduce Iceberg tables as first-class "lake tables" in CB without breaking PostgreSQL / Cloudberry transactional semantics:
2.2 GoalsThe first release of this design aims to deliver:
2.3 Non-goals (outside the first release)
Implementation3. Overall ArchitectureThe proposed design has four layers, split into a metadata path and a data path:
4. The Core Abstraction: Catalog ×ばつ Volume ×ばつ TableThe design splits an Iceberg table into three independently configurable, freely composable pieces:
A Volume can be shared by multiple tables (different paths under the same bucket); a Catalog can reference multiple Volumes (different tables on different storage). Polaris is a special case — the storage configuration is dispatched by the Polaris service, so a user-side Volume is optional. Why Catalog and Volume are separatedIn real deployments they are orthogonal:
Making Catalog and Volume two separate FDWs, each with its own Server / UserMapping, lets us cover every combination without inventing a new FDW for each. Builtin CatalogFor users with no external Catalog (Polaris / Hive) available, the design offers a Builtin option: the Why we need it: it removes the hard dependency on a Catalog service and lowers the barrier to entry. It also gives a zero-dependency option for the "CB is the only writer" single-writer scenario. 5. Components & Design DecisionsThe following lists the design choice — and the reason behind it — for each key component. 5.1 Iceberg Table AM: why not a pure FDWThe most direct approach would be to keep using FDW, but two hard limitations get in the way:
Table AM ( The proposed approach is therefore: register Iceberg tables as a dedicated Table AM, and have the AM delegate data I/O to the Volume FDW internally (reusing the existing S3 / HDFS read / write code). We get the SQL consistency of tableam and avoid reimplementing the storage layer. The core code will live in
5.2 Catalog FDW: abstracting three backends
The Server's
Upwards, the AM only sees Why FDW instead of a plain C function: it lets us reuse PG's 5.3 Volume FDW: the data-file I/O abstraction
Its responsibilities:
The Server's 5.4 datalake_agent: why a separate Java serviceThis is the single most important design trade-off. Iceberg's metadata semantics are complex: manifest lists, snapshot logs, partition-spec evolution, schema field-id mapping, optimistic CAS commit, and so on. The community's most invested, most mature implementation is Reimplementing all of this on the C / C++ side would cost us:
Therefore the design delegates all metadata operations to a dedicated
Upside:
Cost: one extra network hop — but only on the metadata path; data I/O still goes straight from C++ to storage, so throughput is unaffected. Process lifecycle: managed by the
|
| Scenario | When | Purpose |
|---|---|---|
| per-statement | End of each DML | Read-Your-Own-Writes + early concurrent-conflict detection |
| at-scan | SELECT on an already-modified table | Let SELECT see concurrent committed data |
| at-commit | PRE_COMMIT | Final merge, reduces CAS failure probability |
The resulting semantics:
- Read Committed: every statement sees committed concurrent transactions;
- Read-Your-Own-Writes: a SELECT within the transaction sees its own prior INSERTs;
- ACID: the CAS to Catalog happens only at COMMIT. On rollback, intermediate metadata.json files and the data files already written become orphans and are reclaimed by the background cleanup queue;
- SAVEPOINT: the tracker maintains an internal
level_historystack, recording the metadata and file counts before each nested-transaction modification.
5.7 Deletion Queue: why asynchronous cleanup
DROPping an Iceberg table, replacing old files during VACUUM, orphans left behind by a rolled-back transaction — all of these need deletions against object storage.
Why not delete synchronously: a single Iceberg table can reference tens of thousands to millions of files. Synchronous deletion inside the transaction would make DDL block for a long time, and a mid-way failure would leave the system in a "metadata gone, files stranded" inconsistent state.
The design: an iceberg.pg_iceberg_deletion_queue system table plus a background task.
- DROP: just enqueue the metadata_location (
DELETION_TYPE_METADATA); - VACUUM: enqueue the paths of old data files that were replaced (
DELETION_TYPE_FILE); - The background task polls the queue, expands the referenced files from metadata, and deletes them in batches;
- Failed entries get
retry_count++and are retried later, giving idempotency.
6. End-to-End Flows
Execution paths for each key SQL under this design.
CREATE ICEBERG TABLE
- PG core performs the CREATE, inserting into
pg_class / pg_attribute / pg_lake_table; - An
OAT_POST_CREATEhook on the QD calls the agent's/iceberg/tablesto produce the initial metadata.json; - The returned metadata_location is written into
iceberg.pg_iceberg_metadata.
SELECT
- The planner calls AM's
scan_get_am_privateand obtains the metadata_location "that this scan should see" (an already-modified table triggers one rebase); - The QD calls the agent's
/fragments(with pushdown predicates) and receivesList<FileScanTask>; - The fragment list is passed through ForeignScan plan; QEs pick up their share by
segindex; - Each QE calls the Provider to read Parquet, applying the delete index to skip marked-deleted rows.
INSERT / UPDATE / DELETE
- QE calls Volume FDW + Provider to write data files (and, for UPDATE / DELETE, position-delete files);
- QE returns file-metadata JSON to the QD;
- QD calls
tracker.apply_updates_with_rebase:- Read latest metadata from Catalog; decide whether rebase is needed;
- Accumulate into the tracker's
data_files / delete_files; - Call the agent's
/modifyto generate a new intermediate metadata.json.
- At COMMIT,
tracker_commit_allperforms the CAS for every modified table.
VACUUM
- QD calls the agent's
/plan-rewriteand receives a rewrite plan (groups built from min-input-files + target-file-size); - QEs each process one group: read old files + write one larger file;
- QD collects results and calls the agent's
/commit-rewriteto commit a RewriteFiles snapshot; - The paths of the replaced old files are enqueued into the deletion queue.
DROP
- The
OAT_DROPhook enqueues the metadata_location into the deletion queue; - The row in
pg_iceberg_metadatais removed; - The background cleanup task expands all files referenced by the metadata and deletes them in batches.
7. MPP Execution Model
The responsibilities are divided as follows under MPP.
7.1 QD vs QE responsibilities
| Responsibility | QD | QE |
|---|---|---|
| Call the agent (create / plan / commit) | ✓ | |
| Metadata Tracker | ✓ | |
| Fragment dispatch | ✓ | |
| Data-file read / write | ✓ | |
| Position-delete read / write | ✓ | |
| Writes to the deletion queue | ✓ |
Principle: only the QD talks to the agent. Letting N QEs hit the agent in parallel would both make the agent a bottleneck and introduce concurrent writes to Iceberg snapshot state, which brings its own complexity. The parallel part is the data I/O.
7.2 Fragment dispatch
The QD places List<FileScanTask> into the plan tree; it is serialized and dispatched to QEs. Each QE picks its fragments round-robin by segindex % segcount.
The GUC datalake.external_table_limit_segment_num can cap the number of segments that participate in a scan — useful when joining with small tables to reduce dispatch overhead.
7.3 Global file-id consistency
UPDATE / DELETE plans may include a Redistribute Motion that ships a row from QE-i to QE-j. QE-j, when it later dereferences the ctid, must still be able to resolve it back to its original file.
Under this design, ctids are encoded as <file_id, row_pos>. To let any QE resolve a ctid from any origin, BeginForeignModify pre-populates a global file-id map using the full fragment list (not just the subset assigned to the current QE).
8. Pushdown & Optimization
WHERE clauses are translated through deparse.c into the agent's FilterNode tree; the agent then converts that into an Iceberg Expression, applying partition pruning + manifest min/max filtering at planFiles time. Operators planned for pushdown: =, !=, >, <, >=, <=, IS [NOT] NULL, LIKE, IN, AND, OR.
The Provider C++ layer then applies row-group filtering + residual predicates + column projection.
A fragment cache (GUC datalake.enable_iceberg_fragment_cache, default on) caches metadata_location + filter → plan result within a single backend, avoiding repeated trips to the agent.
9. Concurrency with External Engines
Community Iceberg engines (Spark / Trino / ...) may write the same table concurrently. Under this design:
- When an external engine commits, it changes the Catalog's metadata_location;
- The next CB DML's rebase will notice
global != last_baseand replan (accumulated files are reapplied on top of the new global); - If replay hits an incompatible evolution (e.g. column-type conflict) → the agent raises an error → PG aborts the transaction and asks the user to retry.
10. Extensibility
New Catalog type (Nessie / Glue / in-house):
- Add the corresponding Iceberg
Catalogconstruction on the agent side; - Add a new
typebranch on the PG side.
Because all Iceberg semantics live in the agent, the PG-side change is minimal.
New storage backend:
- Add a FileSystem implementation;
- Have the Volume FDW recognize the new
typeand handle its connection parameters.
New DML shapes (MERGE / UPSERT): mostly planner work; the underlying "write data file + write position-delete" primitives can be reused.
11. Outside the First Release (follow-up work)
Items the first release will not cover and that will be discussed in later iterations:
- Only identity partitioning is planned; bucket / truncate / hour transforms are not supported;
- No partition-spec evolution;
- No Branch / Tag / Time Travel queries;
- Equality deletes are read-only;
- Concurrency only at Read Committed;
- The agent is single-instance by design; production deployments are expected to front it with a reverse proxy and multiple instances themselves;
- ANALYZE relies on record_count / bytes returned by the agent and is not deeply integrated with PG's column statistics;
- When an entire data file is deleted, the first release still writes a position-delete file and relies on a later VACUUM for cleanup — there is room for optimization here.
12. Appendix
12.1 Key GUCs (planned)
| GUC | Default | Description |
|---|---|---|
iceberg_default_catalog |
'' |
default Catalog |
iceberg_default_volume |
'' |
default Volume |
datalake_agent_server_url |
— | agent endpoint |
datalake.enable_iceberg_fragment_cache |
on |
enable fragment cache |
datalake.iceberg_vacuum_compact_min_input_files |
10 |
min input files to trigger VACUUM compaction |
datalake.iceberg_vacuum_rewrite_target_file_size_mb |
512 |
VACUUM target file size (MB) |
datalake.iceberg_postion_deletes_threshold |
100000 |
position-delete threshold |
datalake.external_table_limit_segment_num |
0 |
cap on segments participating in a scan (0 = no cap) |
datalake.disable_filter_pushdown |
off |
disable predicate pushdown (for debugging) |
datalake.iceberg_autovacuum |
off |
enable autovacuum (requires restart) |
datalake.iceberg_autovacuum_naptime |
600 |
autovacuum interval (seconds) |
12.2 New system tables (planned)
iceberg.pg_iceberg_metadata — current metadata location for each Iceberg table
| Column | Type | Description |
|---|---|---|
relid |
oid | LakeTable OID (primary key) |
metadata_location |
text | current metadata.json path |
previous_metadata_location |
text | previous version (used for CAS) |
is_internal |
bool | whether this is a Builtin Catalog table |
default_spec_id |
int4 | default partition spec |
iceberg.pg_iceberg_deletion_queue — queue of files to be cleaned up
| Column | Type | Description |
|---|---|---|
path |
text | path to delete (primary key) |
table_name |
oid | originating table OID |
orphaned_at |
timestamptz | time enqueued |
retry_count |
int4 | retry count |
deletion_type |
int4 | 0 = FILE / 1 = METADATA |
12.3 Planned code layout
contrib/datalake_fdw/
├── src/am_iceberg/ Iceberg Table AM + Metadata Tracker + DDL hook
├── src/iceberg_catalog_fdw/ Catalog FDW (Polaris / Hive / Builtin)
├── src/iceberg_volume_fdw/ Volume FDW (S3 / HDFS)
├── src/provider/iceberg/ Provider C++ (Parquet I/O, delete handling)
├── src/components/agent_cli/ agent gRPC client
└── docs/ this document
contrib/datalake_proxy/ PG bgworker that launches and supervises the agent jar
contrib/datalake_agent/ Java Spring Boot, wraps iceberg-java
Suggested review focus:
- Whether the four-layer split (AM / Catalog FDW / Volume FDW / Agent) is sound;
- The trade-off of a dedicated Java service for metadata vs. a pure C implementation;
- Whether the
datalake_proxybgworker process model is the right way to host the Java agent; - The evolution path and compatibility story of the RPC protocol;
- Correctness of the Metadata Tracker's rebase + CAS strategy under Read Committed and SAVEPOINT;
- The MPP division of responsibilities: "agent is only talked to by the QD; data I/O is parallelized on QEs";
- The necessity of the Builtin Catalog as a metadata fallback;
- Whether splitting Catalog and Volume into two FDWs is over-abstraction;
- The extension path for partition evolution / Branch / equality deletes.
Rollout/Adoption Plan
No response
Are you willing to submit a PR?
- Yes I am willing to submit a PR!
All reactions
-
👍 5
Replies: 10 comments 18 replies
All reactions
Making Catalog and Volume two separate FDWs, each with its own Server / UserMapping, lets us cover every combination without inventing a new FDW for each.
If I am not mistaken - there is no way to CREATE USER MAPPING for ROLE (group of users) - only PUBLIC or explicitly for each user in the group. This may force DBAs to conduct more work than you would expect.
There were discussion for Multi-Catalog support for Cloudberry (may be there are some useful thoughts)
#1297
All reactions
That's good question. Initially, we envisioned unifying the FDW and Table AM layers. That's why we use FDW to manage catalogs and volumes.
Here are potential solutions:
- We could store credentials in PUBLIC and achieve isolation via GRANT USAGE ON FOREIGN SERVER / schema permissions.
- Providing helper functions to expand pg_auth_members for a given role, and batch creating/syncing USER MAPPING for members of that role would also alleviate the issue.
How about this?
All reactions
Hi!
Thank you very much for sharing your thoughts and ideas! We here in Moscow have struggled with the same issue. I must say that everyone is crazy about lakehouse architecture, especially since no one fully understands what it actually means. But anyway, I have formulated their wishes for myself as "using various databases to work with well-structured transactional data"
I really appreciate efforts and willing to participate in development process.
That's why it's very important for me to understand why we are doing it, which parts are important, and what types of work should be done first and what can be postponed until later stages. I'd like to focus on:
The best SELECT performance
Where the Cloudberry place in lakehouse world? Everyone know about Trino and Apache Doris/Starrocks, and most probably the kernel of feature lakehouse system will be one of them, not Cloudberry. We could try to catch up with them and achieve feature parity, doing the same as these products, not worse for a start, and preferably better. It's real, but it also takes a lot of effort and time. And still does not succeed.
If we only accept that there are other databases and they do a better job and they are more likely to be used for it. We can set priorities and start by doing something better than everyone else. I mean we in MPP get used to everything should be properly distributed. And use one of the best cost-based optimizers to produce execution plan.
Let's:
A. Define the amount of work for each QE on a QD on a planning phase, using statistics and cost models.
B. Replan query/reassign the list of reading parquet files to worker if we missed with selectivity estimation
C. Use various optimizations on QE like threads/SIMD instructions etc. We have iceberg-cxx - the same as iceberg-cpp but right now has a better performance.
D. Use special proxy to get data from S3. Proxy could be used for IO-control and as a caching layer, see Simplified workflow used in yezzey
Native Polaris intergration
Why place metadata catalog outside the Cloudberry cluster? Let's make it a first-class citizen. One could configure the Apache Cloudberry cluster with the Polaris catalog. The Cloudberry can store data, and it can also be used for storing Polaris catalog data. And so, Cloudberry is once again the central element of the lakehouse.
All reactions
Thank you very much. First, let me respond to several questions:
-
Why use Java Iceberg instead of iceberg-cpp
Currently, iceberg-cpp cannot meet our requirements. Although we have made some efforts, iceberg-cpp is still far from mature. By using the Java implementation of Iceberg, we can support the latest features such as Iceberg V3, V4, etc., in later stages. The Java-side Iceberg always maintains the latest version. -
datalake_agent will be integrated into Cloudberry
datalake_agent will include the Java Iceberg JAR package. It will be mainly responsible for parsing Iceberg metadata on the QD node, then dispatching and passing the metadata information to the segments. The segments will only be in charge of loading data.
The advantages of this approach are:
- The Java Iceberg JAR package is always up-to-date, allowing us to easily follow the latest code to implement features and support Iceberg V3, V4.
- It reduces the pressure of metadata access.
-
Optimal performance
We plan to use QE to perform unified data reading, which is faster than parsing by a single PXF process alone. For further performance optimization, we can refer more to optimizations for Parquet in projects such as Apache Arrow or DataFusion.
I believe pure performance optimization is not an issue; the higher priority is to ensure complete functionality. -
Caching for object storage and Hadoop
Caching does significantly impact overall performance. However, we plan to reserve a dedicated read/write IO layer for users to implement their own best practices. This depends on how users define their own file IO.
We will provide basic methods for accessing object storage and HDFS. Users can also implement their own optimized IO methods if needed. -
Regarding Polaris
This is a good question. However, I would like to clarify what integrating Polaris into Cloudberry specifically means.
Does it mean hosting the Polaris service directly on Cloudberry? Or hosting Polaris metadata on Cloudberry? @leborchuk
All reactions
I don’t think simply loading, reading or writing data is an issue for Cloudberry—we can optimize performance to match that of local PAx tables.
When compared with Apache Doris/Starrocks, the difference may lie in the execution engine: we are based on the PostgreSQL engine, which is different from a pure columnar engine.
It might be better to build a separate dedicated columnar analytical engine?
I recall there was a previous discussion topic about pushing down queries to DuckDB; I wonder if this could be helpful later on.
hi @yjhjstz Do you have any good suggestions on this part?
All reactions
Thank you very much. First, let me respond to several questions:
- Why use Java Iceberg instead of iceberg-cpp
Currently, iceberg-cpp cannot meet our requirements. Although we have made some efforts, iceberg-cpp is still far from mature. By using the Java implementation of Iceberg, we can support the latest features such as Iceberg V3, V4, etc., in later stages. The Java-side Iceberg always maintains the latest version.- datalake_agent will be integrated into Cloudberry
datalake_agent will include the Java Iceberg JAR package. It will be mainly responsible for parsing Iceberg metadata on the QD node, then dispatching and passing the metadata information to the segments. The segments will only be in charge of loading data.
The advantages of this approach are:
- The Java Iceberg JAR package is always up-to-date, allowing us to easily follow the latest code to implement features and support Iceberg V3, V4.
- It reduces the pressure of metadata access.
- Optimal performance
We plan to use QE to perform unified data reading, which is faster than parsing by a single PXF process alone. For further performance optimization, we can refer more to optimizations for Parquet in projects such as Apache Arrow or DataFusion.
I believe pure performance optimization is not an issue; the higher priority is to ensure complete functionality.- Caching for object storage and Hadoop
Caching does significantly impact overall performance. However, we plan to reserve a dedicated read/write IO layer for users to implement their own best practices. This depends on how users define their own file IO.
We will provide basic methods for accessing object storage and HDFS. Users can also implement their own optimized IO methods if needed.- Regarding Polaris
This is a good question. However, I would like to clarify what integrating Polaris into Cloudberry specifically means.
Does it mean hosting the Polaris service directly on Cloudberry? Or hosting Polaris metadata on Cloudberry? @leborchuk
-
Yes, it sounds wise to use mature project. Iceberg java is great and so no need to write all functions once again just to make sure it launches inside main process.
-
Yes, datalake_agent sounds good. But is it possible to define stable serializable RPC interface for interacting with the datalake_agent. What it should be? protobuf + GRPC?
-
I cannot say if optimal performance is crucial or not but I'm afraid we will have a strong demand for the performance. Not optimal but fast enough to make it sense to use the extension.
What is the primary purpose for which you are considering using Iceberg?
Our scenario is as follows.
(1) Sharing data
There is a lot of data that does not fit into a single greenplum cluster, so we need to create several smaller clusters, say up to 10, each with around 1-2 racks size. The problem is how to upload the data to these clusters. Copying the same data across 10 different clusters is not practical, time-consuming and leads to the growth of the clusters. Instead, we can load the data into an iceberg, and then use extensions to read it from different clusters. We need to make sure that the reading is no slower than reading from local files. No recording is required for this scenario, as the data can be generated by other databases, such as Spark/Trino/StarRocks.
You can see a code for the GP6 extension in the tea project. ( https://github.com/lithium-tech/tea )
(2) Archive data
Write data from GP to S3 and store catalog info for later re-read them. Allows you to reduce the cluster size. Right now there are no write functionality in GP extensions. But performance here is not so crucial, you could write data to archive in a background. Though you shouldn't spend CPU aimlessly, GP clusters usually have little free CPU and memory.
I'd like to participate in all activities. But want to assess my capabilities soberly. I will be able to focus now primarily on scenario (1) Sharing data. I think I can test this code on a production-like installation. And only if succeed there it would be wise to move further. If not - we will need to continue working on the architecture.
Yes, the current approach is fdw, but TableAm approach looks more promising.
There is also an interesting aspect: how exactly to work with metadata? First, it would be great if we could import a schema so as not to have to create objects ourselves. Secondly, we need to figure out how to handle columns and their data types. Ideally, I would like to have something like a view, where you create an iceberg table and not say which columns you want - just select everything. And then depending on the (iceberg) transaction you can see different column set and their types in the table.
- Sorry for the direct question, but do you have any evidence? We tried to cache data in yezzey project - https://github.com/open-gpdb/yezzey - no performance benefits. And while testing starrocks (iceberg caching is enabled in it by setting) - again no significant differences in TPC-H queries (Datacache in tpc-h provides about 10% performance compared to reading directly from S3.).
We use yproxy (https://github.com/open-gpdb/yproxy) mainly for limit input/output, memory and CPU consumption. This turned out to be more important than caching.
- Polaris
I am not sure, we're still discussing it. Should it be Polaris or maybe https://github.com/apache/gravitino ? Does Cloudberry really good at oltp workload from catalog or something else should be used. No answers right now.
All reactions
Thanks for the detailed feedback, @leborchuk — happy to dive into this topic with you.
Personally, I see this as one of the inevitable directions for the next generation of data
infrastructure. Open table formats — Iceberg, Lance, Hudi — are already emerging as a
foundational layer, and storage–compute separation is, in my view, the architectural endpoint
almost every serious analytics system is converging toward. We're also seeing these formats
increasingly adopted as the data substrate for embodied-AI and multimodal workloads, which only
reinforces the case for Cloudberry to be a first-class citizen here.
Responding to your points one by one:
- RPC interface
Yes — datalake_agent will expose a Protobuf + gRPC interface, treated as a stable, versioned
contract so that the QD and the agent can evolve independently.
- Our primary motivation
Our main motivation aligns with your scenario (1): cross-cluster data sharing, together with
the storage–compute separation that the Iceberg architecture naturally enables. We're very
optimistic about this direction overall.
- On scenario (2) — archive
A genuine question back: if the end state is data sitting on object storage with Iceberg
metadata, why not write directly to object storage from day one, rather than landing it in GP
first and archiving later? That would collapse the archive case into the same code path as data
sharing.
- Schema import / view-like tables
Because we're going with the Table AM approach, every Iceberg table must have a corresponding
relation in the catalog, so a CREATE TABLE is unavoidable — you will still need to create a
table. That said, making the column set dynamic (tracking Iceberg schema evolution at read
time) is entirely feasible and not particularly hard, and we plan to support it.
- Caching
We do believe caching is effective. The first pull from remote storage is unavoidably slow, but
once blocks are cached on local disk, reads are essentially indistinguishable from local
files. On the cache side, prefetching and parallel download are both worth considering.
The common reasons caching appears to underperform are, in our view:
- cache capacity too small → low hit rate
- network bottleneck during background fetch
- cache block size too large → poor efficiency
- insufficient concurrency → can't keep up with the consumer
In principle, with proper sizing and tuning, a well-configured cache can reach near-local
performance.
- Polaris
Polaris is not a blocker. Cloudberry will manage (mirror) all Iceberg metadata internally;
Polaris is only consulted at read time to fetch the latest Iceberg metadata pointer. Even if
Polaris goes down, we can still read the Iceberg data.
Zooming out: I think getting Iceberg right inside Cloudberry isn't just a feature — it's
positioning the project for where the ecosystem is actually going (lakehouse + open formats +
AI-native workloads). Looking forward to keeping this conversation going, and very open to
collaborating on scenario (1) with you in a production-like setting.
All reactions
-
👍 2
Schema import / view-like tables
Because we're going with the Table AM approach, every Iceberg table must have a corresponding
relation in the catalog, so a CREATE TABLE is unavoidable — you will still need to create a
table. That said, making the column set dynamic (tracking Iceberg schema evolution at read
time) is entirely feasible and not particularly hard, and we plan to support it.
When I was researching how to make PXF's CRETE FOREIGN TABLE easier to use I ended up with an idea of IMPORT FOREIGN SCHEMA as first step + and background worker that refreshes state. apache/cloudberry-pxf#69
However I am not sure that FOREIGN TABLE is the best solution from UX point of view. Multi-catalog approach sounds like better solution.
Caching
We do believe caching is effective. The first pull from remote storage is unavoidably slow, but
once blocks are cached on local disk, reads are essentially indistinguishable from local
files. On the cache side, prefetching and parallel download are both worth considering.
@leborchuk , I read (somewhere) following approach for caching - cache Parquet/ORC's file footers - this should eliminate extra roundtrip and can help storage engine to skip files without fetching them.
All reactions
-
👍 1
Comparing the performance of Greenplum, Starrocks and Trino when reading Iceberg tables
Greenplum cluster for the test
The cluster consist of 5 hosts: master, standby and 3 segment hosts. Each host has 4 CPU cores and 16GB RAM. There are 4 primaries on each segment host - one primary per CPU core. The same equipment has been used to run TPC-H queries on Trino и StarRocks. I used TEA (https://github.com/lithium-tech/tea) to read Iceberg tables from Greenplum 6.
Results
The horizontal axis shows the numbers of the TPC-H test queries, and the vertical axis shows their execution time in seconds. You can find out the exact numbers in the attached html. The dark red color means that the query failed.
There are explain analyze verbose-s for each query in this file.
Greenplum executed only the q06 query faster, other queries are executed significantly slower.
All reactions
I want to add that it's just internal tests, for internal comparison only. Just to see where we are right now and how to improve our code further. They cannot be repeated and referenced, they are just some of our internal benchmarks.
All reactions
|
The Java agent concern goes beyond "one extra hop". Fragment planning (
The right answer is Apache iceberg-cpp . The gaps (CAS commit, catalog backends, snapshot/manifest writing) are well-specified engineering work — one-time investment. The Java agent's architectural debt is paid forever. StarRocks and Doris — currently the strongest Iceberg MPP readers — are pure C++, no Java metadata sidecar. The TPC-H numbers shared above already show Cloudberry behind. Adding a Java Cloudberry is an Apache incubating project. Co-investing in Apache iceberg-cpp is a better community story and a better technical foundation than wrapping iceberg-java behind gRPC. I'd strongly advocate for not shipping a Java agent as part of Cloudberry core, and instead contributing the missing pieces upstream to Apache iceberg-cpp together. |
All reactions
-
👍 6
Iceberg-Cpp has now been split into two parts: metadata processing and data reading. Theoretically, we only need to replace the Java-based metadata module in the future.
Admittedly, this is still a compromise solution. If we fully commit to Iceberg-Cpp, the time and effort required will be hard to estimate. A more pragmatic way is to implement basic experimental functionalities first, and then make further optimizations and improvements in the later stage.
All reactions
Iceberg-cpp is still incomplete and only supports limited features for now. We may adopt a compromise solution: implement core basic functions temporarily via Java proxy first, and refactor the code to use Iceberg-Cpp once it is fully functional.
A phased implementation approach seems more realistic at the moment.
We could forking iceberg-cpp, making changes and then gradually merging the good solutions back into the main repository. Just writing code isn't a problem anymore. It's difficult to implement a good working solution.
If Java is a good example, let's rewrite Java to C++ using LLM as an experiment.
All reactions
-
👍 2
I’ve recently been working on iceberg-cpp development. Even with LLM assistance, the workload remains substantial. I’m unsure if investing so much time is worthwhile, and implementing feature enhancements for the Iceberg core also demands massive effort.
A more practical approach is to design standardized interfaces. Java boasts superior compatibility and maturity. Hence, I suggest building unified standard interfaces that support integration with various Iceberg metadata engines.
All reactions
I have presented the current workload progress:
https://github.com/MisterRaindrop/iceberg-cpp/commits/feat/hive-catalog/
https://github.com/MisterRaindrop/iceberg-cpp/commits/feat/hadoop-catalog
For iceberg-cpp, these only implement basic functionalities and do not cover features such as Kerberos authentication. To make it production-grade, the time required could be several times longer—or even far more.
All reactions
I’ve recently been working on iceberg-cpp development. Even with LLM assistance, the workload remains substantial. I’m unsure if investing so much time is worthwhile, and implementing feature enhancements for the Iceberg core also demands massive effort.
A more practical approach is to design standardized interfaces. Java boasts superior compatibility and maturity. Hence, I suggest building unified standard interfaces that support integration with various Iceberg metadata engines.
Yes, unification makes sense! For example, we could use protobuf to get and send data from/to a metadata library (or a metadata service). It doesn't matter what language the service is written in. This will isolate the services, and we can change the architecture if necessary.
All reactions
Here I want to add a little bit about why I think Java is the right choice for a unification/universal iceberg catalog proxy.
When building a proxy layer that needs to interoperate with the broad ecosystem of Apache Iceberg catalog services, Java is the clear implementation choice over C/C++.
The primary reason is native client SDK coverage. Every major catalog service in the Iceberg ecosystem — including Apache Hive Metastore, AWS Glue Data Catalog, Project Nessie, Apache Polaris, Apache Gravitino, Databricks Unity Catalog, and JDBC-based catalogs — provides a first-class, officially maintained Java client SDK. The Apache Iceberg project itself is Java-native, and its official library (iceberg-java) ships dedicated integration modules for each of these catalogs out of the box: iceberg-hive-metastore, iceberg-aws (for Glue), iceberg-jdbc, and a built-in REST catalog client that covers any catalog implementing the Iceberg REST specification.
C/C++ support, by contrast, is significantly more limited. The apache/iceberg-cpp library currently only provides a native client for the Iceberg REST Catalog protocol. This means it can reach catalogs that have adopted the REST specification — such as Polaris, Unity Catalog, and Gravitino — but it has no native support for Hive Metastore's Thrift protocol, JDBC-based catalogs, or the Hadoop catalog, all of which require JVM-based clients. There is no official C++ SDK for AWS Glue at all. Legacy and widely-deployed catalogs like Hive Metastore remain inaccessible from C++ without resorting to unmaintained third-party libraries.
In short, a Java-based proxy can speak natively to every catalog in the market today using well-maintained, officially supported SDKs. A C/C++ proxy would be fundamentally incomplete, requiring workarounds or re-implementations for a significant portion of the catalog landscape. For a proxy whose core value is broad catalog compatibility, Java is the only pragmatic foundation.
All reactions
-
👍 1
Iceberg Roadmap
- Complete the Iceberg core syntax implementation in July
- Finish the development of datalake_agent between August and September
- Complete the development of datalake_fdw in October
- Finalize the entire Iceberg operational framework in November
This is the current tentative schedule and not a finalized timeline.
All reactions
Thank you, we're focusing on creating first prototype as fast as possible. The first MVP should read data and have some functionality to manually control extension. We want to check if users have interest in it or not. And if yes, what type of functionality they need, what catalog type they use an what else is worth improving. Maybe we should improve something in Cloudberry core (executor/optimizer), not in extension - this is one of the hypotheses that we want to test.
All reactions
My current objective is to integrate Iceberg as a built-in table implementation within Cloudberry and deliver its core functionalities. I’m fairly confident I can finish this within the scheduled timeline.
Separately, when you mentioned optimizing the executor and optimizer, are you referring to performance tuning for Iceberg itself?
All reactions
Another question: in your opinion, would it be better for datalake_agent and datalake_fdw to exist as plugins or be integrated natively into Cloudberry? It seems you still need to carry out additional verification and modifications for them.
All reactions
Do you plan implementing ALTER queries: ALTER FOREIGN CATALOG, ALTER FOREIGN VOLUME, ALTER LAKE TABLE?
All reactions
I plan to implement this, but I suggest adding ALTER operations after finishing the basic SQL statements.
All reactions
-
👍 1
Have you seen https://github.com/pgiceberg/pgiceberg ?
All reactions
Yes, I've been familiar with this project; it was initiated by contributors to iceberg-cpp.