Create and Manage Apache Iceberg Tables
This document describes how to create, query, and manage Apache Iceberg tables in SynxDB. Apache Iceberg is an open table format widely used to build data lakes on object storage (such as Amazon S3). The datalake_fdw extension lets SynxDB manage Iceberg tables as native relations rather than foreign tables, so you can use standard SQL — CREATE, SELECT, INSERT, UPDATE, DELETE, and VACUUM — directly against them. Iceberg data files stay on the underlying object storage; SynxDB reads and writes them through the catalog and volume configured below.
This document covers only Iceberg tables. To query file-based external data (Parquet, ORC, Avro, Text, CSV) on object storage or HDFS, see Load Data from Object Storage and HDFS. To synchronize non-Iceberg tables from Hive Metastore, see Load Data from Hive.
Overview
Key capabilities
Manage Iceberg tables as SynxDB native relations, not foreign tables; all operations run through the native executor.
Support full DML on Iceberg tables:
INSERT,UPDATE, andDELETEuse Iceberg v2 merge-on-read semantics.Compact data files through
VACUUM.Read Iceberg tables whose schema was evolved by external engines (Spark, Trino, or Flink) without rewriting old data files.
Prune columns automatically to reduce I/O, reading only the columns a query references.
Support multiple catalog backends: builtin, Apache Polaris, Hadoop, and S3.
Support S3-compatible object stores and HDFS as the storage backend.
When to use this
You host a data lake on S3 in Iceberg format and want to query and update it from SynxDB using SQL.
You share Iceberg tables across engines (Spark, Trino, Flink) and need SynxDB as a participating engine.
You need ACID writes to Iceberg from SynxDB rather than read-only access.
You want a single SQL surface that covers both native MPP tables and lake-format tables.
For purely read-only access to file-based data, the FDW external table flow described in the OSS/HDFS document is simpler and does not require an Iceberg catalog.
Three-layer architecture
To access and manage Iceberg tables, SynxDB uses a three-layer model. Iceberg data lives on object storage. SynxDB connects to the Iceberg metadata service through a FOREIGN CATALOG, connects to the underlying storage through a FOREIGN VOLUME, and then binds the two as an ICEBERG TABLE — a native relation visible inside the database. Each layer is configured separately, and the three are combined when you create an Iceberg table.
Layer |
Database object |
Role |
Available types |
|---|---|---|---|
Metadata |
|
Resolves Iceberg metadata through |
builtin / polaris / hadoop / s3 |
Storage |
|
Accesses physical storage through |
s3 / hdfs |
Data table |
|
Binds the metadata and storage layers as a native SynxDB relation |
Table access method |
Catalogs and volumes are reusable: many Iceberg tables can share the same catalog and volume. After you set the session-level defaults (iceberg_default_catalog, iceberg_default_volume), subsequent CREATE ICEBERG TABLE statements can omit the catalog and volume clauses.
Workflow
Before you can create and manage Iceberg tables in SynxDB, you must complete the catalog and volume setup. The full procedure has five steps:
Step |
Goal |
Section |
|---|---|---|
1 |
Install the extension |
|
2 |
Configure a foreign catalog |
|
3 |
Configure a foreign volume |
|
4 |
Create an Iceberg table |
|
5 |
Query and maintain Iceberg tables |
Steps 1 through 3 are typically configured once per cluster. Step 4 and Step 5 apply to each new Iceberg table.
For a single runnable walkthrough that touches every step, see End-to-end example: S3 with builtin catalog. For option tables, supported data types, limitations, and error messages, see Reference.
Step 1. Install the extension
This step loads the datalake_fdw extension into your database and registers the Iceberg-related FDWs and table access method.
CREATE EXTENSION IF NOT EXISTS datalake_fdw;
Installing datalake_fdw registers the iceberg_catalog_fdw and iceberg_volume_fdw foreign data wrappers and the iceberg table access method. Using a Hive Metastore as the Iceberg catalog does not require installing hive_connector — that extension is only for synchronizing non-Iceberg tables in Load Data from Hive.
Step 2. Configure a foreign catalog
This step creates the connection objects that let SynxDB reach an Iceberg metadata service. You create one SERVER, one USER MAPPING, and one FOREIGN CATALOG object. The FOREIGN CATALOG is the database object that represents the Iceberg metadata source inside SynxDB.
Choose the catalog type that matches your existing metadata service:
Type |
When to use |
|---|---|
|
Self-contained SynxDB-managed metadata. Good for testing or simple deployments. |
|
Hive Metastore manages Iceberg table metadata. Good for environments that already run a Hive Metastore. |
|
Apache Polaris REST catalog provides centralized metadata. |
|
File-system-based catalog (Iceberg |
|
S3-only variant of |
For the compatibility matrix between catalog and volume types, see Catalog and volume compatibility in the reference.
Use the builtin catalog
CREATE SERVER my_cat_srv FOREIGN DATA WRAPPER iceberg_catalog_fdw;
CREATE USER MAPPING FOR current_user SERVER my_cat_srv;
CREATE FOREIGN CATALOG my_catalog SERVER my_cat_srv;
Use a Hive Metastore catalog
The Hive catalog stores Iceberg table metadata in an external Hive Metastore service.
Note
Point warehouse_location_prefix at an HDFS location. A hive catalog cannot reach an S3 backend in this release: the bare s3:// scheme is unregistered, and s3a:// fails because the catalog agent never registers the S3A file system, so creating a table reports a missing S3AFileSystem class.
CREATE SERVER hive_cat_srv FOREIGN DATA WRAPPER iceberg_catalog_fdw
OPTIONS (
type 'hive',
url 'thrift://hive-metastore:9083'
);
CREATE USER MAPPING FOR current_user SERVER hive_cat_srv;
CREATE FOREIGN CATALOG hive_catalog SERVER hive_cat_srv
OPTIONS (
catalog_name 'hive_location',
default_namespace 'default',
warehouse_location_prefix 'hdfs://mycluster/warehouse/hive/'
);
Hive catalog user mapping options:
Option |
Description |
|---|---|
|
OS user |
|
Authentication method ( |
|
Hive service principal (Kerberos) |
|
Client principal (Kerberos) |
|
Keytab file path (Kerberos) |
Use a Polaris catalog
Use Apache Polaris as a centralized REST-based catalog.
Note
A Polaris catalog still requires a foreign volume. Even though Polaris returns the table location, SynxDB reads and writes data files through the volume’s own endpoint and credentials, so you must complete Step 3 and keep the VOLUME clause (or iceberg_default_volume) when creating the table.
CREATE SERVER polaris_cat_srv FOREIGN DATA WRAPPER iceberg_catalog_fdw
OPTIONS (
type 'polaris',
url 'http://polaris:8181/api/catalog',
polaris_server_realm 'default' -- omit to use the POLARIS realm
);
CREATE USER MAPPING FOR current_user SERVER polaris_cat_srv
OPTIONS (
client_id 'my_client_id',
client_secret 'my_client_secret',
scope 'PRINCIPAL_ROLE:ALL'
);
CREATE FOREIGN CATALOG polaris_catalog SERVER polaris_cat_srv
OPTIONS (
catalog_name 'production',
default_namespace 'public',
enable_metadata_cache 'true',
metadata_cache_ttl '300'
);
Set polaris_server_realm to the realm your Polaris server is configured with. When you leave it out, SynxDB sends the POLARIS realm. A mismatched realm makes the server reject the OAuth token request, and CREATE FOREIGN CATALOG fails with 404 MissingOrInvalidRealm.
CREATE FOREIGN CATALOG attaches to catalog_name on the Polaris server, creating it there if it does not exist. Creation takes the new catalog’s storage configuration from iceberg_default_volume, so set that first or the statement fails with storageConfig cannot be null or empty for catalog creation. The new catalog claims the volume server’s whole bucket rather than the volume’s base_path, so Polaris rejects a second catalog on the same bucket with One or more of its locations overlaps with an existing catalog. To put several catalogs on one bucket, create them through the Polaris management API, giving each catalog its own location, then attach by name.
Use a Hadoop catalog
The Hadoop catalog maintains Iceberg metadata directly in the storage file system. It requires no external metadata service.
-- Hadoop catalog backed by S3-compatible OSS storage
CREATE SERVER hd_cat_srv FOREIGN DATA WRAPPER iceberg_catalog_fdw
OPTIONS (type 'hadoop');
CREATE USER MAPPING FOR current_user SERVER hd_cat_srv;
CREATE FOREIGN CATALOG hd_cat SERVER hd_cat_srv
OPTIONS (warehouse_location_prefix 's3a://warehouse/iceberg/');
Notes:
warehouse_location_prefixmust include the protocol (s3a://). The option is not validated atCREATE FOREIGN CATALOGtime, but omitting it causes errors when you create or access an Iceberg table through this catalog.A table’s metadata.json and data files live under
<warehouse_location_prefix>/<namespace>/<table>/.Tables created here can be read by Spark, Trino, or Flink through Iceberg
HadoopCatalog.
Use an S3 catalog
The S3 catalog is a specialization of the Hadoop catalog. It wraps the storage configuration as Iceberg IcebergS3Catalog, splitting warehouse_location_prefix into fs.defaultFS and fs.prefix. This is the option to choose when you need S3 vended credentials or separate internal/external endpoints. The companion volume must use type='s3'.
CREATE SERVER s3_cat_srv FOREIGN DATA WRAPPER iceberg_catalog_fdw
OPTIONS (type 's3');
CREATE USER MAPPING FOR current_user SERVER s3_cat_srv;
CREATE FOREIGN CATALOG s3_cat SERVER s3_cat_srv
OPTIONS (warehouse_location_prefix 's3a://warehouse/iceberg_s3/');
How the namespace is resolved
Catalog types that maintain a namespace directory (hive, hadoop, and polaris) need a namespace for every table. SynxDB resolves it from two sources, in this order:
The table’s own
OPTIONS (namespace '...')inCREATE ICEBERG TABLEThe catalog’s
OPTIONS (default_namespace '...')inCREATE FOREIGN CATALOG
Give every table a namespace: set namespace on the table, or default_namespace on the catalog. Setting default_namespace on the catalog spares you from repeating the namespace in every CREATE ICEBERG TABLE, and an individual table can still override it.
When the resolved namespace has no matching database in the underlying catalog, the error reports the precedence and every way to correct it:
org.apache.iceberg.exceptions.NoSuchNamespaceException: Iceberg catalog (hive) has
no database matching namespace "no_such_namespace_xyz". Resolution precedence is:
(1) CREATE ICEBERG TABLE ... OPTIONS (namespace '<ns>') [per-table override],
(2) CREATE FOREIGN CATALOG ... OPTIONS (default_namespace '<ns>') [per-catalog
default], (3) PostgreSQL schema name [fallback]. Either CREATE the namespace in the
underlying catalog (e.g. via spark-sql / beeline), or set OPTIONS namespace on the
table, or set OPTIONS default_namespace on the foreign catalog, or move the table
into a PostgreSQL schema whose name matches an existing namespace.
SynxDB does not create namespaces in an external catalog. Create the database there first, through the catalog’s own tooling.
The builtin catalog has no queryable namespace directory, so these rules do not apply to it: omit the namespace and table options and let the catalog manage table locations.
Step 3. Configure a foreign volume
This step creates the connection objects that let SynxDB reach the underlying object storage. You create the corresponding SERVER, USER MAPPING, and FOREIGN VOLUME objects. The FOREIGN VOLUME points to the physical storage that holds Iceberg data files.
Configure an S3 or S3-compatible OSS volume
CREATE SERVER s3_vol_srv FOREIGN DATA WRAPPER iceberg_volume_fdw
OPTIONS (
type 's3',
endpoint 'http://oss-endpoint:9000',
region 'us-east-1',
bucket_name 'warehouse',
path_style_access 'true' -- required by many S3-compatible OSS implementations
);
CREATE USER MAPPING FOR current_user SERVER s3_vol_srv
OPTIONS (access_key_id 'AKIAEXAMPLE', secret_access_key 'SECRET');
CREATE FOREIGN VOLUME my_volume SERVER s3_vol_srv
OPTIONS (base_path '/warehouse/', allow_writes 'true');
Configure an HDFS volume
Important
Supply the NameNode address through the SERVER options shown below.
Do not use an endpoint option for HDFS. It applies only to object storage (S3 or S3-compatible OSS).
For a single NameNode with simple authentication:
CREATE SERVER hdfs_vol_srv FOREIGN DATA WRAPPER iceberg_volume_fdw
OPTIONS (
type 'hdfs',
hdfs_namenodes '192.168.1.10:9000',
hdfs_auth_method 'simple',
hadoop_rpc_protection 'authentication'
);
CREATE USER MAPPING FOR current_user SERVER hdfs_vol_srv
OPTIONS (username 'gpadmin');
CREATE FOREIGN VOLUME hdfs_vol SERVER hdfs_vol_srv
OPTIONS (base_path '/iceberg-warehouse/', allow_writes 'true');
For HA HDFS with Kerberos:
CREATE SERVER hdfs_ha_vol_srv FOREIGN DATA WRAPPER iceberg_volume_fdw
OPTIONS (
type 'hdfs',
hdfs_namenodes 'mycluster',
hdfs_auth_method 'kerberos',
krb_principal 'gpadmin/master@REALM.COM',
krb_principal_keytab '/home/gpadmin/gpadmin.keytab',
krb_service_principal 'hdfs/namenode@REALM.COM',
hadoop_rpc_protection 'privacy',
is_ha_supported 'true',
dfs_nameservices 'mycluster',
dfs_ha_namenodes 'nn1,nn2',
dfs_namenode_rpc_address '192.168.1.10:9000,192.168.1.11:9000',
dfs_client_failover_proxy_provider
'org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider'
);
CREATE USER MAPPING FOR current_user SERVER hdfs_ha_vol_srv
OPTIONS (username 'gpadmin');
Step 4. Create an Iceberg table
This step creates an Iceberg table bound to the catalog and volume you configured. After the table exists, you can run standard SQL against it.
Set session defaults to avoid repeating the catalog and volume in every CREATE ICEBERG TABLE:
SET iceberg_default_catalog = 'my_catalog';
SET iceberg_default_volume = 'my_volume';
Then create the table:
CREATE ICEBERG TABLE orders (
id BIGINT NOT NULL,
product VARCHAR(100),
region TEXT,
amount DECIMAL(15,2) NOT NULL,
order_date DATE NOT NULL
);
To override the defaults for a single table, name the catalog and volume explicitly and pass OPTIONS:
CREATE ICEBERG TABLE sales (
id BIGINT, region TEXT, amount NUMERIC(12,2)
)
CATALOG hive_catalog
VOLUME s3_volume
OPTIONS (
namespace 'analytics',
table 'sales_2024'
);
Catalog and volume specification rules:
Both can be omitted if session defaults (
iceberg_default_catalog,iceberg_default_volume) are set.All catalog types, including Polaris, require a volume (see the note in Step 2).
The
namespaceandtableoptions attach the table to an existing entry in an external catalog. Onlyhive,hadoop, andpolariscatalogs support this mapping. Thebuiltincatalog does not maintain a queryable(namespace, table)directory; use it withoutnamespace/tableand let it create and manage the table (its location is generated under the volume’sbase_path). Supplyingnamespace/tablewith a builtin catalog fails withmetadataLocation is required for builtin catalog.Omitting
namespacefalls back to the catalog’sdefault_namespace. See How the namespace is resolved for the precedence.
For the full option list, see CREATE ICEBERG TABLE options in the reference.
For supported column types, see Supported data types.
PRIMARY KEY, UNIQUE, and FOREIGN KEY constraints are rejected at CREATE ICEBERG TABLE time because Iceberg tables are always distributed RANDOMLY, which is incompatible with key-based constraints. CHECK constraints are accepted and enforced at runtime, but are not propagated to Iceberg metadata.
Iceberg tables are always distributed RANDOMLY because data fragments live on object storage rather than being hash-partitioned across segments. Specifying DISTRIBUTED REPLICATED or DISTRIBUTED BY (...) produces a warning and SynxDB ignores the clause. Running ALTER TABLE ... SET DISTRIBUTED BY on an Iceberg table is rejected with an error.
Step 5. Query and maintain Iceberg tables
After the previous four steps, you can operate on Iceberg tables like any other table in SynxDB. This section groups the day-to-day tasks:
Run DML statements
INSERT INTO orders VALUES (1, 'Widget', 'US-West', 99.99, '2024-01-15');
INSERT INTO orders SELECT * FROM staging_orders;
UPDATE orders SET amount = amount * 1.1 WHERE region = 'US-West';
DELETE FROM orders WHERE order_date < '2023-01-01';
SELECT region, SUM(amount) FROM orders
WHERE order_date >= '2024-01-01'
GROUP BY region;
UPDATE and DELETE use Iceberg v2 position-delete (merge-on-read). A single statement does not rewrite data files; it produces position-delete records that an anti-join eliminates during reads. VACUUM compaction reduces position-delete accumulation.
Empty a table with TRUNCATE
TRUNCATE empties an Iceberg table managed by the builtin catalog:
TRUNCATE orders;
The statement writes a new, empty snapshot and queues the table’s previous data and metadata files for deletion; the background consumer then removes them from the object storage. Points worth knowing:
It is transactional: rolling it back leaves the rows and data files unchanged. It does leave one unreferenced
metadata.json, manifest, and snapshot list on the storage, which the deletion queue does not cover; remove them with an external orphan-file tool.Running it on an already empty table does nothing and reports no error.
SynxDB rejects it for tables on an external catalog, because that catalog owns the table’s storage:
ERROR: TRUNCATE is only supported for builtin-catalog iceberg tables DETAIL: table "ext_trunc" is on an external catalog that owns its storage
To empty such a table, use
DELETE FROM <table>.
Evolve the schema
ALTER TABLE is not supported on Iceberg tables. Any ALTER TABLE statement — including ADD COLUMN, DROP COLUMN, and SET DISTRIBUTED BY — is rejected with the error ALTER TABLE is not supported on Iceberg tables. RENAME COLUMN is rejected with the error RENAME COLUMN is not supported on Iceberg tables.
To evolve the schema of an Iceberg table, use an external engine (Spark, Trino, or Flink) to perform the Iceberg-side schema change, then drop and recreate the SynxDB Iceberg table definition to pick up the new schema.
Compact tables with VACUUM
VACUUM triggers Iceberg data file compaction.
VACUUM orders;
Tune compaction thresholds at the session level:
SET datalake.iceberg_vacuum_compact_min_input_files = 5;
SET datalake.iceberg_vacuum_rewrite_target_file_size_mb = 256;
VACUUM orders;
VACUUM cannot run inside a transaction block or a PL/pgSQL function.
After compacting a table managed by the builtin catalog, VACUUM also reclaims the storage the rewritten files occupied: each file that compaction replaced is queued for deletion and removed from the object storage in the background. Row counts and query results are unaffected. Because the queueing is transactional, an aborted VACUUM leaves the original files in place. For tables on an external catalog, compaction runs but the superseded files stay, because the external catalog owns that storage.
VACUUM performs compaction and reclaims the files it rewrote. Snapshot expiration and stale metadata cleanup are handled separately (controlled by datalake.iceberg_max_snapshot_age and datalake.iceberg_max_file_removals_per_vacuum) and by external tools. See VACUUM scope for details.
Enable autovacuum
Autovacuum is enabled by default (datalake.iceberg_autovacuum = on) and checks tables every 10 minutes. Adjust the interval or disable autovacuum (requires superuser; reload required):
ALTER SYSTEM SET datalake.iceberg_autovacuum_naptime = 1200; -- check every 20 minutes
ALTER SYSTEM SET datalake.iceberg_autovacuum = off; -- disable if needed
SELECT pg_reload_conf();
Column projection and filtering
When you query an Iceberg table, the plan reads only the columns referenced by the query and shows the query filter on the Custom Scan (Iceberg Scan) node. Use EXPLAIN to inspect the plan:
EXPLAIN SELECT * FROM orders WHERE id = 12345;
-- "Filter: (id = 12345)" appears on the Iceberg Scan node.
Before it reads a data file, the scan prunes by the statistics of that file: it skips a data file entirely, reading none of it, when the value range of the predicate column does not overlap the predicate. Both equality and range predicates take part in pruning.
To observe how much a query prunes, compare the rows read by the scan node in EXPLAIN ANALYZE:
-- A predicate that statistics cannot serve reads the whole table.
EXPLAIN (ANALYZE) SELECT count(*) FROM orders WHERE note IS NOT NULL;
-- Custom Scan (Iceberg Scan) on orders (actual rows=500000 ...)
-- An equality predicate on a column loaded in increasing batches reads only the files that can match.
EXPLAIN (ANALYZE) SELECT count(*) FROM orders WHERE id = 350000 AND note IS NOT NULL;
-- Custom Scan (Iceberg Scan) on orders (actual rows=1 ...)
-- Rows Removed by Filter: 99999
When the predicate overlaps the value range of no data file, the scan skips every file, and Rows Removed by Filter no longer appears in EXPLAIN ANALYZE.
How much a query prunes depends on the physical layout of the data. Pruning works best when writes cluster on the predicate column, for example when you load data in batches by time or by an increasing ID, because the value ranges of the files do not overlap. When every file covers the full value range of the column, no pruning is possible.
Clean up objects
Drop dependent objects in the reverse order of creation:
DROP TABLE my_iceberg_table;
DROP VOLUME my_volume;
DROP USER MAPPING FOR current_user SERVER vol_server;
DROP SERVER vol_server;
DROP CATALOG my_catalog;
DROP USER MAPPING FOR current_user SERVER cat_server;
DROP SERVER cat_server;
To drop dependent objects in one command, use CASCADE:
DROP SERVER cat_server CASCADE;
See Object dependency order for the full ordering.
What happens to the data files
Dropping a table managed by the builtin catalog also removes its data and metadata files from the object storage. DROP TABLE records the table’s metadata tree in a deletion queue as part of the transaction, and a background consumer driven by autovacuum deletes the files it lists. The files therefore disappear shortly after the drop commits rather than during the statement itself, and a rolled-back DROP TABLE deletes nothing.
Dropping a table bound to an external catalog (one created with OPTIONS (namespace ..., table ...)) removes only the SynxDB table definition. The files stay, because the external catalog owns that storage and other engines might still reference them.
Tune the background consumer with these parameters. All four require a configuration reload: change them with gpconfig and then run gpstop -u.
Parameter |
Default |
Description |
|---|---|---|
|
|
Whether the consumer processes the queue. Turning it off leaves queued files on the storage; the consumer processes them after you turn it back on |
|
|
Minimum seconds between two consumer runs |
|
|
Maximum queue entries processed per run |
|
|
Attempts per entry before it is set aside as failed |
Entries that keep failing (for example, because the storage credentials no longer work) are retried up to datalake_fdw.deletion_queue_max_retry times and then set aside, so one unreachable file cannot block the rest of the queue. Because deletion depends on autovacuum, keep autovacuum enabled on databases holding Iceberg tables.
Rolled-back transactions are handled too: SynxDB removes the staging data files that an aborted INSERT, UPDATE, or DELETE had already written, so a failed statement leaves no unreferenced files behind.
End-to-end example: S3 with builtin catalog
The following script walks through every workflow step against an S3-compatible OSS endpoint, using the builtin catalog.
-- 1. Extension
CREATE EXTENSION IF NOT EXISTS datalake_fdw;
-- 2. Catalog (builtin, no external dependency)
CREATE SERVER cat FOREIGN DATA WRAPPER iceberg_catalog_fdw;
CREATE USER MAPPING FOR current_user SERVER cat;
CREATE FOREIGN CATALOG my_cat SERVER cat;
SET iceberg_default_catalog = 'my_cat';
-- 3. Volume (S3 / S3-compatible OSS)
CREATE SERVER vol FOREIGN DATA WRAPPER iceberg_volume_fdw
OPTIONS (type 's3', endpoint 'http://oss-endpoint:9000', region 'us-east-1',
bucket_name 'warehouse', path_style_access 'true');
CREATE USER MAPPING FOR current_user SERVER vol
OPTIONS (access_key_id 'admin', secret_access_key 'password');
CREATE FOREIGN VOLUME my_vol SERVER vol OPTIONS (base_path '/data/');
SET iceberg_default_volume = 'my_vol';
-- 4. Create and use the table
CREATE ICEBERG TABLE users (id int, name text, score numeric(10,2));
INSERT INTO users VALUES (1, 'Alice', 95.5), (2, 'Bob', 87.0);
SELECT * FROM users WHERE score > 90;
UPDATE users SET score = 96.0 WHERE id = 1;
VACUUM users;
DROP TABLE users;
-- 5. Clean up
DROP VOLUME my_vol;
DROP USER MAPPING FOR current_user SERVER vol;
DROP SERVER vol;
DROP CATALOG my_cat;
DROP USER MAPPING FOR current_user SERVER cat;
DROP SERVER cat;
Reference
This section consolidates option tables, type mappings, dependency rules, limitations, and error messages.
Catalog and volume compatibility
Catalog |
Compatible volume |
Notes |
|---|---|---|
|
|
Embedded metadata service |
|
|
Metadata managed by Hive Metastore |
|
|
A volume is required (see the note in Step 2) |
|
|
|
|
|
Object storage only |
Catalog options
Option |
Where |
Description |
|---|---|---|
|
SERVER |
|
|
SERVER (hive, polaris) |
Hive: |
|
SERVER (polaris) |
Realm sent to the Polaris server (default |
|
FOREIGN CATALOG (hive, polaris) |
Remote catalog name |
|
FOREIGN CATALOG |
Namespace used by tables in this catalog that do not set their own. See How the namespace is resolved |
|
FOREIGN CATALOG |
Enable metadata cache |
|
FOREIGN CATALOG |
Cache TTL in seconds |
|
FOREIGN CATALOG |
Auto-refresh metadata |
|
FOREIGN CATALOG (polaris, hadoop, s3) |
Warehouse path prefix (must include protocol). Not validated at creation time, but required for |
Volume options
iceberg_volume_fdw accepts its options without checking them: an unknown option name or an out-of-range value goes through silently and only surfaces later as a failed connection. Check names and values against the tables below.
Server options:
Option |
Applies to |
Description |
|---|---|---|
|
All |
|
|
s3 |
Object storage endpoint (with protocol) |
|
s3 |
Region |
|
s3 |
Bucket name |
|
s3 |
Path-style access (required by many S3-compatible OSS implementations) |
|
s3 (Polaris) |
Internal endpoint for Polaris-side access |
|
s3 (Polaris) |
STS endpoint and disable switch |
|
s3 (Polaris) |
Vended-credentials role information |
|
s3 (Polaris) |
SSE-KMS keys |
|
hdfs |
NameNode address. Supports |
|
hdfs |
NameNode RPC port (default 8020; not needed when using |
|
hdfs |
|
|
hdfs (Kerberos) |
Client Kerberos credentials |
|
hdfs (Kerberos) |
NameNode service principal |
|
hdfs |
Quality of protection for the RPC connection: |
|
hdfs |
Enable SASL data transfer ( |
|
hdfs |
Quality of protection for DataNode data transfer: |
|
hdfs |
Enable HA |
|
hdfs (HA) |
HA routing configuration |
|
hdfs |
Connect to DataNodes by hostname rather than IP address ( |
User mapping options:
Option |
Applies to |
Description |
|---|---|---|
|
All |
OS user |
|
s3 |
Static AK/SK |
CREATE FOREIGN VOLUME options:
Option |
Description |
|---|---|
|
Storage base path |
|
Allow write operations (default |
|
Enable caching (default |
CREATE ICEBERG TABLE options
Option |
Type |
Description |
|---|---|---|
|
string |
Catalog name (equivalent to the |
|
string |
Namespace. See How the namespace is resolved |
|
string |
Table name registered in the catalog (defaults to the SynxDB table name) |
|
string |
Not settable. Records the table’s data directory after creation: the volume’s |
|
string |
Codec for Parquet writes: |
|
int |
Level for codecs that support one: |
|
bool |
Whether the table participates in |
SynxDB silently ignores unrecognized options. A misspelled option name (for example, table_name instead of table, or base_location instead of location) produces no error but has no effect.
SynxDB validates compression and compression_level when you write to the table rather than at CREATE ICEBERG TABLE time, so an unusable combination surfaces as an INSERT error:
ERROR: invalid iceberg write compression "brotli"
HINT: supported parquet codecs are uncompress, snappy, gzip, zstd, lz4
ERROR: compression codec "snappy" does not support a compression level
ERROR: zstd compression level 99 out of range
HINT: valid zstd levels are 1..22
Both options are fixed after you create the table: Iceberg tables reject ALTER TABLE, so switching codec or level means recreating the table.
For a table managed by the builtin catalog, the files are laid out directly under the volume’s base_path:
<protocol>://<bucket>/<base_path>/metadata/...
<protocol>://<bucket>/<base_path>/data/...
All builtin tables on the same volume therefore share one data and one metadata directory. File names carry UUIDs, and each table’s metadata location is recorded in the catalog, so tables stay isolated without relying on separate directories. Two consequences:
You cannot choose the path: passing
locationto abuiltintable fails withlocation option is not allowed for builtin iceberg tables. On an external catalog the option is accepted but has no effect, because that catalog assigns the location.Avoid maintenance tools that operate on a directory prefix, such as an Iceberg remove-orphan-files action pointed at the shared directory. Those tools cannot tell which files belong to which table and might delete live data of a different table.
Tables created earlier keep the path recorded at their creation time, so the two layouts coexist and no migration is needed. Tables on an external catalog are unaffected: that catalog assigns their locations.
Supported data types
SynxDB type |
Iceberg type |
Notes |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Precision is required |
|
|
|
|
|
Iceberg has no fixed-length character type. Trailing spaces and right-padding are not preserved |
|
|
|
|
|
Without time zone |
|
|
Stored as a timezone-less timestamp in Iceberg metadata. SynxDB reads and writes values correctly, but external engines (Spark, Trino, Flink) see the column as a timezone-less timestamp |
|
|
numeric without explicit precision can be used in CREATE ICEBERG TABLE (the metadata registers the column as decimal(38,9) by default), but any INSERT into such a column fails with The precision of numeric in foreign tables with parquet format should be specified explicitly. Always declare numeric(p,s).
Types not listed above (such as uuid or json) produce a WARNING at CREATE ICEBERG TABLE time and are mapped to string. Use them with caution.
VACUUM scope
VACUUM compacts data files and reclaims the files it rewrote. Other Iceberg maintenance operations are handled separately:
Operation |
Performed by |
Owner |
|---|---|---|
Compact small data files into larger files |
Yes, when input files reach |
|
Remove physical rows marked by position-delete |
Indirectly, through compaction rewrites |
|
Delete the data files that compaction replaced |
Yes, for |
|
Delete files of a dropped or truncated table |
Not applicable |
|
Expire old snapshots ( |
No |
Background deletion queue ( |
Remove orphan files ( |
No |
External tools (Spark or Trino |
Clean up superseded |
No |
Iceberg itself: new tables are created with |
Object dependency order
Iceberg objects depend on each other and must be created and dropped in order. The drop order is the reverse of the create order.
Order |
Create |
Drop |
|---|---|---|
1 |
Install the |
Drop the |
2 |
Create the |
Drop the |
3 |
Create the |
Drop the |
4 |
Create the |
Drop the |
5 |
Create the |
Drop the |
ALTER limitations
ALTER TABLE is not supported on Iceberg tables. ADD COLUMN, DROP COLUMN, ALTER COLUMN TYPE, and SET DISTRIBUTED BY are rejected with the error ALTER TABLE is not supported on Iceberg tables. RENAME COLUMN is rejected with the error RENAME COLUMN is not supported on Iceberg tables. To evolve the schema, use Spark, Trino, or Flink to perform the Iceberg-side change, then drop and recreate the Iceberg table in SynxDB.
Limitations
Not yet implemented:
Feature |
Notes |
|---|---|
|
|
|
Tables are organized by file distribution. Partitioned tables written by Spark or other engines can be read |
Time travel ( |
Only the latest snapshot is read |
Table-level Iceberg properties ( |
Determined by catalog defaults; no SQL entry point overrides them |
Cross-table distributed transactions |
Single-table ACID only; multi-table writes are not atomic |
Row-level / column-level security applied to the underlying Iceberg data |
Enforced only at the SynxDB entry point; physical files have no access control |
Generated columns and expression |
Accepted by the SynxDB parser but not reflected in the Iceberg schema |
Silent no-ops (no error, but the operation has no effect on Iceberg state):
Operation |
Actual behavior |
|---|---|
|
The build returns immediately with no entries; the index object exists but is empty |
|
Refreshes |
|
Accepted by the DDL parser and enforced at runtime, but not propagated to Iceberg metadata. |
Explicitly rejected:
Operation |
Error |
|---|---|
|
|
TID range scan ( |
Returns unpredictable results (may return unrelated rows) |
Internal |
|
Concurrent writes: Concurrent commits to the same Iceberg table use compare-and-swap with up to 10 retries (TRACKER_MAX_COMMIT_RETRIES). When all retries fail, the transaction aborts with failed to commit iceberg metadata for table <oid> after <N> retries due to concurrent updates. To reduce the chance of conflict, batch multiple rows per INSERT and limit the number of concurrent writers.
For features that SynxDB does not yet support, including ALTER TABLE, PARTITION BY, time travel, and table properties, run the Iceberg-side operations through Spark, Trino, or Flink and let datalake_fdw consume the data.
Runtime errors
Error message |
Meaning |
Resolution |
|---|---|---|
|
The table is not registered in the catalog |
Verify the remote catalog name, namespace, and table name. For Polaris, check |
|
The catalog cannot return the table location |
Check catalog connectivity, |
|
metadata.json cannot be read |
Check volume credentials, |
|
The catalog returned an empty location |
Catalog bug or namespace registration issue. Verify the table is registered with a valid location in the external catalog (for example through Spark, Trino, or the catalog’s own client) |
|
The builtin catalog parsed an empty path |
Table name or namespace contains special characters; normalize and recreate |
|
A QD-only metadata API was called on a QE |
Avoid calling metadata functions in PL/pgSQL that runs on QEs |
|
Stale catalog reference |
Check whether the catalog was dropped without being recreated |
|
Stale volume reference |
Same as above |
|
A non-superuser triggered first-time metadata initialization |
Have a superuser run any Iceberg DDL first to perform initialization |
|
Concurrent CAS retries exhausted |
Batch writes or reduce concurrent writers |
|
|
Use Spark/Trino/Flink for schema evolution, or drop and recreate the table |
|
|
Recreate the table with the desired column name, or rename via Spark/Trino/Flink |
|
|
Specify |
|
|
Specify |