Skip to content

feat(bundle): parquetbundle v3, 2x faster 1/3 memory usage - #477

Open
peymanvahidi wants to merge 31 commits into
mainfrom
perf/parquetbundle-v3
Open

feat(bundle): parquetbundle v3, 2x faster 1/3 memory usage#477
peymanvahidi wants to merge 31 commits into
mainfrom
perf/parquetbundle-v3

Conversation

@peymanvahidi

Copy link
Copy Markdown
Collaborator

Summary

Loading a large dataset is about twice as fast and uses about a third of the memory.

User side changes

Noting dramatically. They use the same pipeline and python package as before, the way that we save the parquetbundle and later process it in UI is different, which is the reason of these improvements.

Numbers below are the tests on the old and new parquetbundle dataset of SwissProt (around 570k data points):

Old New
File chosen to usable plot 13.3 s 7.6 s
Peak tab memory ~2.5 GB ~0.8 GB
Memory held after loading 460 MB 221 MB
File size on disk 44.9 MB 36.4 MB
Longest frozen moment 4.5 s 4.5 s
Hover, pan, zoom after load unchanged unchanged
Switch to a multi-valued annotation 2.2 s 3.0 s

Nothing about the visualisation changed. A screenshot comparison of the fully loaded app found the images identical.

What changed in the .parquetbundle format

Piece Before Now
Categorical value string per row int32 code plus a label dictionary
Multi-value cell one string, delimiter-joined per-row hit count, codes in a side buffer
Score / evidence code appended into the same string float64 and int32 buffers, per-hit counts
Projection coordinates long table, identifier repeated per row wide float column pair per projection
Column meaning inferred by scanning values at load declared in a manifest in the file

Compatibility

Existing files still work. Every dataset already published loads exactly as before, so nothing needs regenerating and this can ship without coordination.

Open questions

  1. I think we could drop support for the old format at some point, and I would like opinions rather than agreement.
  2. I implemented these changes via Claude and by providing it the embedding atlas repo, this is why it was more interested in making these changes instead of using DuckDB:
**Why not DuckDB**
We looked at it closely, since Apple's Embedding Atlas ships it and handles millions of points. Three things settled it.

It is not what makes them fast. Their point data does not come from SQL at render time: one query runs, the results are
copied into typed arrays, and the GPU draws those. Their scale comes from the renderer, not the database.

It does not fix our bottleneck. Our slow step was parsing packed annotation strings, and a query engine parses the same
strings. At this size a plain loop over an integer column filters in about a millisecond, while a round trip to an in-browser
database costs more than that before any work starts.

It costs real money. Around 5 MB of extra download, a second full copy of the dataset in memory, and a single-threaded
engine with known trouble above a couple of million rows. We were trying to reduce memory, not double it.

Where it would genuinely help is ad-hoc analytics over arbitrary columns: cross-filtered side charts, sortable paged tables,
joins across extra tables. If we want those later, it can be loaded on demand behind that panel and kept off the load and
render paths entirely.

Columnar codes, wide float32 projections and a CSR payload part.
Paint depth is bucketed by composePaintDepth, not continuous, so 573K slots sort in 34 ms instead of 90 ms; the comparator stays as a fallback.
573K rebuild drops from 209 ms to 13 ms; query result ordering is not contractual and does change, which reorders spiderfy angular layout.
The full bundle byte copy no longer blocks first render for user imports.
…ffsets

Zero-fill absent projection coords, guard projection identity and payload names.
They are numbered by global hit, so a predicted row shifted every later one.
…longside it

Restores the crash-recovery window without putting the byte copy before first paint.
Counting per protein index also fixes isolation-mode count misalignment.
CSR counts, dictionaries and wide projections back to v2-shaped tables.
Lane-based FNV-1a 64 plus a protein_ids-keyed memo; hash values unchanged.
Prefix-sums the wire's per-row counts into CSR offsets; manifest is validated, not trusted.
…types

Bound num_rows, range-check evidence codes, reject id and payload-name collisions.
Name the one in-place dataset writer in the hash memo's soundness note.
An unstamped v2 output reads back as v1, so the next caller double-escaped it.
float32 flushed E-values to 0 or inf; 'none'/'NA' cells are labels, not gaps.
Also make the int64 spelling guard per value and drop the dead zero-chunk arm.
Six slots always, re-encode the core when annotations change, read_tables.
…to NA

Widen CsrScores.values to match the wire format.
Retitle the v2 cell grammar as the logical layer and add version detection for 1, 2 and 3.
Generated by scripts/generate_v3_fixture.py; a superset of v2-sample.
transfer, inspect and the toxprot script take read_tables directly.
Also pins the v3-to-v2 export round trip and the shared v2 anchors.
… claims

Also scope the 19%/21% figures and record the one-way version cross-check.
…rt hazards

Cover the annotations re-stamp, warn on an unstamped encode, drop empty-string legend buckets, and correct three stale v3 comments.
Encoded-part drift guard plus fixture cells that separate dictionary order, both payload families, non-ASCII labels and the EAT overlay.
@peymanvahidi

Copy link
Copy Markdown
Collaborator Author

What I'd do next is to check if we can implement the density plots to the current implementation that we have.

@peymanvahidi

Copy link
Copy Markdown
Collaborator Author

I'll not fix the CI errors yet, I'll do if after we decided about the backward compatibility.
@tsenoner could you tell me what you think about the backward compatibility? are you ok if we remove it?

@peymanvahidi peymanvahidi changed the title feat(bundle): parquetbundle v3, twice as fast to load and a third of the memory feat(bundle): parquetbundle v3, 2x faster to load and a 1/3 memory usage Sep 6, 2026
@peymanvahidi peymanvahidi changed the title feat(bundle): parquetbundle v3, 2x faster to load and a 1/3 memory usage feat(bundle): parquetbundle v3, 2x faster 1/3 memory usage Sep 6, 2026
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