Query QuestDB data from Spark, Trino and Snowflake via Iceberg and Delta Lake
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 25/100
- Issue type
- Feature
- Clarity
- Needs clarification
- Activity status
- Active
- Tech stack
- spark
- Domain
- data-engineering, databases
Research direction
Start with the cold-storage manager and the Cold Storage v2 work in #130, then trace the partition state exposed by table_cold_partitions(). Define the publish, catalog, schema-evolution, retention, and Delta Lake boundaries from the scope; done means the checklist is implemented and external readers can observe consistent snapshots.
Written by the indexing model from the issue text.
Description
Summary
QuestDB Enterprise cold storage already writes each partition to the object store as standard Parquet. This item makes that data readable outside QuestDB by keeping lakehouse table metadata in sync with the partition state QuestDB already tracks for cold storage. Every upload, compaction and drop becomes an atomic snapshot of an Apache Iceberg table, and in a second phase a Delta Lake table, so Spark, Trino, DuckDB, Snowflake, Databricks and PyIceberg query QuestDB's data in place with no copy and no scheduled job.
QuestDB remains the only writer. The lakehouse metadata is a projection of QuestDB's partition transaction log, not a second write path.
Current Limitations
- Registering cold partitions as Iceberg tables is manual: users run PyIceberg or the Iceberg Java API and schedule it to pick up new partitions
- Manual registration cannot follow QuestDB rewriting a partition's Parquet: today a late write re-uploads a live partition in place, and Cold Storage v2 (#130) compaction publishes a new base under a new object key and retires the one an external table still references
- Remote garbage collection knows nothing about external snapshots, so a retired file can disappear under a reader
- Registering with PyIceberg writes Iceberg format v2, so nanosecond timestamps are downcast to microseconds and UUIDs are exposed as
fixed[16]rather than the Iceberguuidtype
Features
| Feature | Description |
|---|---|
| Automatic Iceberg tables | Each cold-storage table is published as an Iceberg table; new partitions appear without any external job |
| Snapshot per publish | Every upload, compaction and partition removal (DROP REMOTE, DROP PARTITION, DROP TABLE) is one atomic commit: files added and files removed land together, so readers never see a torn table |
| Snapshot-aware garbage collection | Retired files are kept until the snapshots that reference them expire, under a configurable retention |
| Catalogs | Iceberg REST catalog (Apache Polaris, Lakekeeper, Nessie) and AWS Glue Data Catalog first; others to follow |
| Type fidelity | Iceberg format v2 by default, with microsecond timestamps and native uuid; tables with nanosecond timestamps use format v3, which needs a v3-capable reader. Explicit mappings for SYMBOL, GEOHASH, LONG256, IPv4, ARRAY and DECIMAL, where precision above 38 needs its own mapping |
| Schema evolution | Parquet field IDs aligned with the Iceberg schema; column add, drop and rename in QuestDB become Iceberg schema updates in the same commit; partitions uploaded before this feature are covered by a name mapping until compaction rewrites them |
| Partition transforms | HOUR, DAY, MONTH and YEAR partitions map to the matching Iceberg transforms; WEEK registers unpartitioned and relies on min/max pruning |
| Delta Lake | Second phase: a _delta_log published alongside the data. Delta readers see the same snapshots; Delta has no nanosecond timestamp or uuid type, so those columns appear at microsecond precision and as binary, and renames use Delta column mapping |
How it works
- The cold-storage manager, one per cluster, uploads or compacts a partition as it does today.
- In the same step it commits an Iceberg snapshot through the Iceberg catalog: data files added and removed, column statistics from the Parquet footers, and schema or partition spec updates if the table changed.
- External engines query the Iceberg table straight from the object store. QuestDB is not involved in the read.
- Remote garbage collection deletes a retired file only after the last snapshot referencing it has expired.
- External readers see data as of the last publish. Late rows held in local Delta Runs (#130) become visible once they are compacted into the remote base.
Example
Illustrative syntax:
-- QuestDB: tier partitions to the object store and publish them as an Iceberg table
CREATE TABLE trades (...)
STORAGE POLICY (TO PARQUET 1d, TO REMOTE 3d, DROP LOCAL 30d)
ICEBERG CATALOG 'lakehouse' TABLE 'market.trades';
-- Trino, Spark or DuckDB: query the same files with no copy
SELECT symbol, count(*)
FROM lakehouse.market.trades
WHERE ts >= DATE '2026-01-01'
GROUP BY symbol;
Benefits
- Readable outside QuestDB — any Iceberg engine reads cold data in place, with partition pruning and column statistics; Delta engines follow in the second phase
- No pipelines — no export job, no cron, no second copy of the data
- Always consistent — atomic snapshots mean no torn reads across uploads or compaction
- Full fidelity — nanosecond timestamps, UUIDs and decimals up to precision 38 are exposed with their native Iceberg types
- Time travel — Iceberg snapshots expose earlier versions of cold data within the retention window
Scope
- Iceberg metadata writer in the cold-storage manager: snapshots for upload, compaction and partition removal
- REST and AWS Glue catalog support
- Type mappings, field ID alignment, name mapping for existing partitions, format v3 for nanosecond tables
- Snapshot-aware remote garbage collection with a retention setting
- Schema evolution mapping
- Published snapshot per partition in
table_cold_partitions()and publish lag per table - Second phase: Delta Lake
_delta_logpublishing
Enterprise feature: requires cold storage. Catalog order, the format version policy and the snapshot retention default remain work in progress.
Builds on #130 (Cold Storage v2). Supersedes #91.
- Dominant language
- No language data
- Stars
- 7
- Forks
- 0
- PR merge metrics
- No merged PRs in 30d
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from questdb/roadmap
-
enterprise security
-
open source SQL
Difficulty 5/5 Over a week Newbie friendliness 25/100
-
open source SQL
Difficulty 5/5 Over a week Newbie friendliness 20/100
-
enterprise high availability
Difficulty 5/5 Over a week Newbie friendliness 25/100
-
enterprise open source UI
Difficulty 5/5 Over a week Newbie friendliness 25/100
Similar issues
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
-
Schema-level dtype cannot be serialized: to_yaml raises RepresenterError, to_json raises TypeError Open
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
unionai-oss/pandera#2511 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
unitaryfoundation/qldpc-challenge#1651 ·
-
Difficulty 1/5 Under an hour Newbie friendliness 90/100
statsmodels/statsmodels#10271 ·