Iceberg Lens

Desktop app · IntelliJ tool window · CLI · Kotlin · Apache-2.0

The table, the way the engine walks it.

Point it at a table directory and it draws the structure every query traverses — metadata files, snapshots, manifest lists, manifests, data and delete files, the rows inside — as a graph you click through, beside an inspector with every field the format records. Nothing is written back: it answers questions about a table, it never changes one.

3formats: Iceberg v1–v3, Paimon and Delta Lake
3faces: desktop app, IntelliJ tool window, icelens CLI
0writes, by construction
Iceberg Lens: the workspace and structure panels on the left, the metadata graph in the middle, the inspector on the right

Table-format figures are easy to state and easy to get wrong, because one manifest is carried forward by every snapshot that keeps its files. Every figure the metadata records is shown beside the same figure counted from the bytes — and the two sets, current snapshot and all retained history, are always labelled.

The whole tree, as a graph

Metadata files, snapshots, manifest lists, manifests, data and delete files, sample rows. Branches in their own columns; long sibling runs folded into pages that say what they hide. Four layouts, a snapshot filter, find, arrow keys, export as SVG, PNG or JSON.

Checked against the bytes

One click compares every recorded figure with the same figure counted — ids and lengths, manifest counts, commit summaries, snapshot totals, each file's partition against its own bounds — over the whole table. icelens check exits 1 on a disagreement.

What a scan would skip

A filter — WHERE clause or form — evaluated against partition summaries and column bounds, with the term that proved each skip. What a commit did, what differs between two snapshots, which snapshot a read as of a time lands on.

Rows and their fate

The rows a filter matches, read from the files it leaves, each marked live or deleted — by which vector, positional or equality delete on Iceberg, by which vector on Delta, by which retraction or later write on Paimon — and traced through the retained snapshots.

Maintenance, planned the engine's way

What expire_snapshots would remove and what keeps the rest; what rewrite_data_files would rewrite; a Paimon bucket as its LSM tree and what the next compaction picks; what Delta's VACUUM and OPTIMIZE would take — planned as the engines' own planners decide it.

Read-only, local, offline

No catalog, no service: a table is opened by its location — a folder, s3://, gs:// or r2:// with a session-only key, or HDFS over WebHDFS as a named user. The remote filesystem refuses writes by type.

What it answers, level by level

The same tree the formats define, and the question each level answers.

The metadata tree from table to rows, with the questions Iceberg Lens answers at each level
The graph alone: table, metadata files, snapshots, manifests, files and sample rows, laid out left to right
The graph alone, layered left to right: every node is a card, a click opens it in the inspector.
  1. Table

    Which format, which version, what the current snapshot holds and what history still keeps on disk; the maintenance summary and both directions of the orphan question.

  2. Snapshots

    What each commit did, folded from the manifests it wrote and checked against its own summary; rollbacks marked on the log; refs, tags, branches and consumers with their retention.

  3. Manifests

    Partition summaries decoded against each manifest's own spec, live and deleted entries, and the snapshots that still carry a manifest forward.

  4. Files and rows

    Recorded bounds, counts, size and split offsets beside the file itself; delete files and deletion vectors paired with the rows they reach; sample rows with their fate.

Download

Installers for macOS (.dmg), Windows (.msi) and Linux (.deb) on GitHub Releases, with their own runtime. From source you need a JDK 17 or newer.

Installers

# macOS   IcebergLens.dmg
# Windows IcebergLens.msi
# Linux   iceberglens.deb

# the command line ships beside the app:
ln -s /Applications/IcebergLens.app/Contents/MacOS/icelens /usr/local/bin/icelens

Every build is on GitHub Releases; the IntelliJ plugin puts the same view in a tool window.

From source

git clone https://github.com/mmdemirbas/ice-lens.git && cd ice-lens
./gradlew run                  # the desktop app
./gradlew :cli:installDist     # cli/build/install/icelens/bin/icelens
./gradlew test

Then Add to Workspace: a warehouse folder or a single table folder — metadata/ for Iceberg, snapshot/ + schema/ for Paimon, _delta_log/ for Delta.

What the numbers mean

Two sets of figures, side by side, each labelled — because a manifest is referenced by every snapshot that carries its files forward.

GroupAnswers
Current snapshotWhat the table holds now — the manifest closure of current-snapshot-id, live entries only. Its record count is what a query returns.
All retained historyWhat is still on disk — every manifest and data file reachable from any retained snapshot, deduplicated. This is what expiry and orphan cleanup reason about.

The same engine from a terminal

For a script, a cron job or a CI step. --json prints the same as an object; the answer goes to stdout, anything the engine logs to stderr.

icelens summary /wh/db/orders                   # versions, snapshots, the current figures
icelens tree /wh/db/orders --depth 2            # metadata → snapshots → manifests → files
icelens show /wh/db/orders snap_8331894 --json  # one node's rows, deferred ones read
icelens check /wh/db/orders                     # every recorded figure against the same figure counted;
                                                # exit 1 on a disagreement, so a pipeline can gate on it
icelens check /wh/db/orders --files             # and every live data file and statistics file opened too
icelens lookup /wh/db/orders "id = 42"          # the rows a filter matches, each with its fate
icelens plan /wh/db/orders                      # what each maintenance procedure would do if run now
icelens plan /wh/db/orders expire_snapshots     # one of them in full: every snapshot and file it removes
icelens export /wh/db/orders --format csv --out files.csv

icelens summary webhdfs://namenode:9870/wh/db/orders --hdfs-user etl
                                                # a table on HDFS, read over WebHDFS as a named user

Format coverage

IcebergPaimonDelta Lake
Detectionmetadata/ with *.metadata.jsonsnapshot/ + schema/_delta_log/ with a commit or a checkpoint
Metadatametadata.json v1–v3, snapshot log with rollbacks marked, refs with retention, partition specs, sort orders, statistics files, row lineagesnapshot-N, schema-N, tags, branches, consumers, the index manifest, ANALYZE statisticsCommits and checkpoints — classic, multi-part, V2 with sidecars — and _last_checkpoint; column mapping, row tracking, in-commit timestamps, UniForm's Iceberg metadata
ManifestsManifest list → manifests, data and delete split, partition summaries decoded against each manifest's own specBase, delta and changelog manifest lists, replayed delta-over-base; the changelog per key as a consumer received itNone: each version is replayed from the newest checkpoint by the protocol's rules; the change data feed per version, as table_changes returns it
FilesData, positional-delete and equality-delete files, v3 deletion vectors decoded from Puffin, inherited sequence numbers and row idsData files with LSM level and bucket, file indexes decoded, external paths, data-evolution patch files, deletion vectors from the index fileAdd, remove and change-data files with statistics and partition values; deletion vectors decoded — relative, absolute or inline
RowsParquet and Avro via DuckDB, 50 per file, deleted rows marked, read by field id with v3 defaults and the name mappingSame, with _ROW_ID, the +I/-U/+U/-D kind of each row, and the projection by the file's own schema idParquet via DuckDB, partition values from the log, _row_id and _row_commit_version under row tracking, vector-marked rows struck

Limitations

Stated plainly, because a tool you inspect internals with has to be honest about its own.

  • No catalog integration. Hive, Glue, REST, Nessie and Polaris are not spoken to; a table is opened by its location. S3, GCS and R2 are read through DuckDB with a key that is never persisted; HDFS over WebHDFS under simple authentication, and a Kerberos-secured cluster not at all; ADLS is not read.
  • Iceberg v3 is modelled up to what the fixture engines write. Deletion vectors, row lineage, added-rows, column defaults and variant are read from real tables; geometry, geography and timestamp_ns parse without error but have no fixture yet.
  • Delta Lake is modelled against delta-spark 3.2.1 — its commit metrics, its maintenance rules and its checkpoint namings are the ones checked against real tables.
  • A v2 positional delete file is not mapped to row cards — its targets are known only after reading the file, so the rows it removes are counted behind a click; a v3 deletion vector marks the cards.
  • What an equality delete removes is known only by reading. The metadata rules a data file out, never in; the count opens every data file it may reach, behind a click.
  • Sample rows are best-effort: capped at 50 per file, five drawn per data file, and slow on tables with many data files when Show Rows is on.