Skip to content

perf(frag-reuse): build the index-open path on RowAddrRemap (release-9, for redap 0.16) - #40

Open
rerun-rmack wants to merge 3 commits into
release-9.0.0from
rmack/fri-compact-r9
Open

perf(frag-reuse): build the index-open path on RowAddrRemap (release-9, for redap 0.16)#40
rerun-rmack wants to merge 3 commits into
release-9.0.0from
rmack/fri-compact-r9

Conversation

@rerun-rmack

Copy link
Copy Markdown

Release-9 counterpart of #38 and #39, carrying the same behaviour forward for redap 0.16.

Why there is still work to do here

RowAddrRemap already exists on this branch, from upstream lance-format#7237. But upstream applied it to the scalar-index remap consumers and never converted open_frag_reuse_index, which is the path that OOMs us. That is still true on this branch, on upstream/main, and on release/v10.0.

So moving to release-9 does not fix the OOM on its own. This is the residual delta.

Commits

  1. Build the index-open path on RowAddrRemap. Everything in perf(frag-reuse): build the index-open path on RowAddrRemap instead of a per-row HashMap #38 except the backport, which this branch already has.
  2. Run-optimize the row-address set before writing it. perf(frag-reuse): run-optimize the compaction row-address set before writing it #39, forward-ported.

Measured, on a real 88 MB production payload

HashMap (today) RowAddrRemap::Compact
entries / structure 676,592,102 O(#fragments)
peak RSS 40.45 GB 0.27 GB
resident delta 26.84 GB 0.084 GB
build 144.1 s 0.1 s
lookup 164 ns 135 ns

compact accepted all 428 groups, so neither of GroupRemap::new's hard-error conditions (ascending new-fragment ids, rewritten-row count matching) is violated by data we have already committed. That was the risk worth checking, since on the read path a rejection would fail every query on the dataset.

Verified against the map over 676,595,694 addresses: zero unexpected divergences. The 3,592 differences are all one predicted class, offsets past a fragment's physical_rows where the map says absent and compact says deleted. Those are not rows, and it is compact's documented behaviour.

Fork-local additions

Marked as such above the test module in row_addr_remap.rs, so the delta against upstream stays visible:

  • Debug and DeepSizeOf for RowAddrRemap, plus num_groups/num_fragments. FragReuseIndex needs both; upstream does not yet because it still holds hashmaps there. They will need these when they convert the open path.
  • remap_column_index composed a map by enumerating keys, and the compact form deliberately cannot enumerate. That path now rebuilds per-row maps from the details: O(rows) again, but only tests reach it in-tree and no redap caller does.

Why #39 matters more here than it looked

Compact keeps the roaring bitmaps resident, so its 88 MB in-memory footprint is those same dense containers. Run-optimizing shrinks the stored object, the deserialize on every index open, and the compact remap's own memory.

Testing

lance-core row_addr_remap 5, lance-table 127, lance frag_reuse 7, optimize:: 111. Clippy and fmt clean.

Unlike #38 and #39, this branch gets real CI: rust.yml on release-9 matches release-*, which release-8.0.0's does not.

rerun-rmack and others added 2 commits August 18, 2026 08:43
Forward-port of #38 to release-9. `RowAddrRemap` is already here
(upstream lance-format#7237), but `open_frag_reuse_index` was never
converted to it, on this branch or on upstream main and v10.0.

It built a `HashMap<u64, Option<u64>>` per reuse version with one entry per
remapped row, on every index open, cached, so readers pay it. Measured on a
production payload: 676,592,102 entries, 88 MB on disk becoming 26.8 GB
resident and 40.5 GB peak, 144 s to build. It OOMs a 60 GiB pod.

Against the same payload, `RowAddrRemap::compact` builds in 0.1 s and 0.27 GB
peak, and agrees with the map on all 676,595,694 addresses probed. The only
differences are 3,592 offsets past a fragment's `physical_rows`, where the map
says absent and compact says deleted; that is compact's documented behaviour
and those addresses are not rows.

This is the residual delta for redap 0.16: everything in #38 except the
backport, which this branch already has.

Three fork-local additions to the upstream module, marked as such above the
test module: `Debug` and `DeepSizeOf` for `RowAddrRemap`, plus the
`num_groups`/`num_fragments` accessors those use. `FragReuseIndex` needs both;
upstream does not yet, because it still holds hashmaps there.

`remap_column_index` composed a map by enumerating keys, and the compact form
deliberately cannot enumerate: it stores per-fragment bitmaps and treats any
unlisted offset in a rewritten fragment as deleted, so its key set is not
finite. That path now rebuilds the per-row maps from the details. Still
O(rows), but only tests reach it in-tree and no redap caller does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Forward-port of #39 to release-9.

The fragment-reuse payload records which row addresses a compaction moved, as a
serialized RoaringTreemap. Compaction rewrites long contiguous spans, but
nothing calls `RoaringTreemap::optimize()`, so those spans are written as dense
bitmap containers at one bit per row. On a real payload that is 88 MB for 676M
addresses, essentially all bitmap containers: 676M bits is 84.6 MB.

This matters more than it looks. `RowAddrRemap::Compact` keeps those same
bitmaps resident, so its 88 MB in-memory footprint is the same containers.
Run-optimizing shrinks the stored object, the deserialize on every index open,
and the compact remap's own memory.

No format change: run containers are already produced today by the binary-copy
path via `insert_range`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Lockfile only, no manifest change; both are patch bumps within the same minor.

  h2   0.4.15 -> 0.4.16   RUSTSEC-2026-0258
  rkyv 0.8.16 -> 0.8.18   RUSTSEC-2026-0233, -0234, -0235

Pre-existing on the branch and unrelated to the change this PR carries. The
last green run here was 2026-07-29, before any of the four reached the advisory
database: the rkyv entries landed 2026-08-04 and the h2 one 2026-08-18, which
is why identical inputs started failing.

Neither is urgent on its own merits. rkyv is in the lockfile but not the
resolved graph (`cargo tree -i rkyv` matches nothing); the only thing that
pulls it is `lindera-dictionary`, behind the optional `tokenizer-lindera`
feature, so we never compile it. The h2 advisory is a denial of service driven
by a peer sending empty DATA frames, and we are an HTTP/2 client to AWS, so
the peer is S3 or DynamoDB behind TLS.

Bumping anyway because the fix is free and leaving CI red hides the next one.

`cargo deny check` now reports advisories ok, bans ok, licenses ok, sources ok.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@rmack

rmack commented Aug 19, 2026

Copy link
Copy Markdown

@rerun-rmack, hello, please know I somehow got tagged and CC'd on this PR. I received an e-mail about it. I just wanted to let you know that somehow my rmack Github ID was tagged. Cheers

From Adam Cimarosti to rerun-io/lance and CC'd rmack (me).

@rerun-rmack

Copy link
Copy Markdown
Author

@rerun-rmack, hello, please know I somehow got tagged and CC'd on this PR. I received an e-mail about it. I just wanted to let you know that somehow my rmack Github ID was tagged. Cheers

From Adam Cimarosti to rerun-io/lance and CC'd rmack (me).

Hah, thank's for letting me know.

@emilk
emilk requested a review from amunra August 21, 2026 08:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants