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.

2formats: Iceberg v1–v3 and Paimon
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 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 — 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, or s3://, gs://, r2:// with a session-only key. The object-store 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.

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 export /wh/db/orders --format csv --out files.csv

Format coverage

IcebergPaimon
Detectionmetadata/ with *.metadata.jsonsnapshot/ + schema/
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 statistics
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 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 file
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 id

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 and ADLS are not.
  • Iceberg v3 is modelled up to what Spark 3.5 can write. Deletion vectors, row lineage and added-rows are read from real tables; variant, geometry, geography and timestamp_ns parse without error but have no fixture yet.
  • 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.
  • Equality deletes are evaluated only for a row in hand. The delete panel says which data files one may reach; the row lookup, which has the row, says whether it does.
  • 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.