Query QuestDB data from Spark, Trino and Snowflake via Iceberg and Delta Lake

Open
#135 0 comments 0 reactions 0 assignees View on GitHub

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

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

enterprise open format storage

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 Iceberg uuid type

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

  1. The cold-storage manager, one per cluster, uploads or compacts a partition as it does today.
  2. 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.
  3. External engines query the Iceberg table straight from the object store. QuestDB is not involved in the read.
  4. Remote garbage collection deletes a retired file only after the last snapshot referencing it has expired.
  5. 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_log publishing

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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from questdb/roadmap

All issues in questdb/roadmap

Similar issues

More Data Engineering issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.