Skip to content

feat(cli): explore, a data-first view of a file for business users - #608

Merged
dfa1 merged 3 commits into
mainfrom
feat/vortex-explore
Oct 25, 2026
Merged

dfa1 merged 3 commits into
mainfrom
feat/vortex-explore

Conversation

@dfa1

@dfa1 dfa1 commented Oct 25, 2026

Copy link
Copy Markdown
Owner

inspect answers how a file is stored; explore answers what is in it.

vortex explore data.vortex                      # same as --serve: live page in the browser
vortex explore --html report.html data.vortex   # one self-contained page, every column analyzed

What it shows

  • Instantly, from the recorded statistics (nothing decoded): null %, exact min/max, mean where the file records a sum, and a sparkline of each zone's min-max band over the rows. A 2.3 GB, 100M-row file opens in well under a second.
  • Analyze (per column, or all at once) reads the values once: count, null %, total, mean, distinct count, string byte lengths, and the spread: every value with its share up to 30 distinct values, else a 30-bin histogram (numeric, date, timestamp), else the 10 most frequent values.

How

  • Totals reuse the fused aggregate behind Calcite's SUM push-down and value counts its GROUP BY fold, both off dictionary codes.
  • Columns are analyzed in parallel: a reader's arena is confined to its thread, so the file is opened once per core on its own IoWorker, and the server answers on virtual threads. Analyze all on the 100M-row file: 2.2 s on 10 cores vs 9-11 s one column at a time.
  • --serve binds 127.0.0.1 only, under a random path (as Jupyter does).
  • --html: January 2024 NYC taxi (3M rows, 19 columns) in 1.5 s, 111 KB.
  • The page lives in ExplorePage; HtmlReport (inspect --html) is unchanged.

Commits

  1. fix(inspector): sizes past 1 GiB printed as e.g. 2179.1 MB; units now derive from the highest set bit, up to EB (Long.MAX_VALUE → 8.0 EB).
  2. feat(reader): ScanIterator.chunkStats public; a dict chunk reports only its pool's min/max.
  3. feat(cli): explore.

Tests

Unit tests for folding, chart choice, escaping and the server's paths/concurrency; vortex-jni cross-checks: a Rust-written file's profile, total and histogram match the data written, and a dict column's value counts are exact.

🤖 Generated with Claude Code

https://claude.ai/code/session_01SwhK4W5cgj7y7s4ZPCif5x

dfa1 and others added 3 commits October 25, 2026 08:40
ByteSize stopped at MB, so a 2.3 GB file read as "2179.1 MB" in inspect,
tui, import, export and explore output. Rather than add one more tier,
derive the unit from the count's highest set bit (each unit is 10 more
bits), with units up to EB: a long tops out just under 8 EiB, so no byte
count can outgrow the list again. Units stay binary, like the KB and MB
ones: that file now prints as 2.1 GB, and Long.MAX_VALUE as 8.0 EB.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SwhK4W5cgj7y7s4ZPCif5x
Each chunk's embedded array stats carry what a vortex.zoned zone map does
not: exact string min/max (the zone map only bounds them) and, where the
writer records it, a sum. Tooling outside the reader (the explore
statistics table) needs them, so the method becomes public.

A dictionary chunk now reports only its values pool's min/max. The pool's
sum and null count describe the dictionary, not the chunk's rows; no
caller read them before, but a public method must not hand them out as
the chunk's.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SwhK4W5cgj7y7s4ZPCif5x
`inspect` answers how a file is stored; `explore` answers what is in it.

explore [--serve] <file|url> serves a page on 127.0.0.1, under a random
path (as Jupyter does), and opens it in the browser. It leads with one
row per column, folded from the statistics the file already records, so
nothing is decoded and a 2.3 GB, 100M-row file opens in well under a
second: null %, exact min/max, mean where the file records a sum, and a
sparkline of each zone's min-max band over the rows (sorted, clustered or
random at a glance).

Analyze on a column reads its values once for the count, null %, total,
mean, distinct count, string byte lengths, and its spread: every value
with its share up to 30 distinct values, else a 30-bin histogram for a
numeric, date or timestamp column (bins from the recorded min/max, or a
min/max pass when the file has none), else the 10 most frequent values,
and nothing past 10,000 distinct. Totals reuse the fused aggregate behind
Calcite's SUM push-down and value counts its GROUP BY fold, both working
off dictionary codes.

Columns are analyzed in parallel. A reader's arena is confined to its
thread, so the file is opened once per core, each on its own IoWorker,
and the server answers on virtual threads. Analyze all on that 100M-row
file takes 2.2 s on 10 cores against 9-11 s one column at a time.

explore --html out.html <file|url> analyzes every column up front and
writes the page as one self-contained file to publish: 1.5 s and 111 KB
for the January 2024 NYC taxi file (3M rows, 19 columns).

The data page lives in ExplorePage, apart from the storage-first
HtmlReport, which is unchanged (inspect --html keeps its output).
vortex-jni cross-checks: a Rust-written file's profile, total and
histogram match the data written, and a dictionary column's value counts
are exact.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SwhK4W5cgj7y7s4ZPCif5x
@dfa1
dfa1 force-pushed the feat/vortex-explore branch from 5993415 to b456d5e Compare October 25, 2026 07:40
@dfa1
dfa1 merged commit 3495d7b into main Oct 25, 2026
6 checks passed
@dfa1
dfa1 deleted the feat/vortex-explore branch October 25, 2026 07:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant