Skip to content

The collection holding a record stops holding a second copy of its key - #220

Merged
matrixarkai merged 1 commit into
mainfrom
perf/the-collection-is-ordered-by-the-records-own-key
Oct 24, 2026
Merged

matrixarkai merged 1 commit into
mainfrom
perf/the-collection-is-ordered-by-the-records-own-key

Conversation

@matrixarkai

Copy link
Copy Markdown
Owner

records was a map from a key to a record that already held that key — two sixteen-byte
handles on one allocation, once per object. A record knows its own key, so the collection is now
ordered by that key rather than beside it.

was now
meta.records.struct_inline 72 56
the metadata service's share 128 112
the metadata line 346 330
the fixed term 164,256 164,240 — the bucket's own record is one more entry

The compiler priced the other route and it was the wrong one

The obvious change is to drop MetadataRecord::key. Done as a probe: 66 errors over 29
files
— but the error count is not the cost. The cost is that about thirty-two signatures
carry a record without its key
(validate_meta_key(&record.key),
shard_contains_key(&record.key), the proof encoder, the footprint audit), and each would have
to carry a key beside it; handoff envelopes would become pairs. This keeps every one of those
sites compiling and changes the collection instead.

What it costs is an Ord/Eq contract, and that is why there is a wrapper

BTreeSet requires Ord and Eq to agree: if a == b then a.cmp(&b) must be Equal,
or the set's ordering invariant is unspecified rather than merely wrong. The ordering has to be
the key, because the key order is what records_range serves prefix scans from. So equality is
the key too.

KeyedRecord therefore says nothing about whether two records hold the same value, and its
record is private so nothing outside the collection can compare two of them by accident. A
cross-type PartialEq<MetadataRecord> was deliberately not added: it would make
self.records.get(k) != Some(record) read as "equal" for two records holding different values —
a drift detector that detects nothing.

MatrixMetaService does not derive PartialEq, and MetaSnapshot keeps its map (it is a
transient encode/decode structure, not resident state), so no container's derived equality
becomes a key-only comparison.

And one hazard that is not in the ordering

BTreeSet::insert keeps the element already there and returns false. A map's insert
overwrites. So filing a new version of a record with insert would leave the old version in
the collection and return a value that reads like success — with every byte figure, every count
and every ordering assertion still correct. A write would simply not take effect.

file_record uses replace, and filing_a_record_over_an_existing_key_replaces_it is the
guard: a second write at one key must not add a record, the filed version must be the new one,
and it must agree with the object as it now stands. It also asserts what the set makes
impossible — a record is always found under the key it holds, through the same allocation — and
that a rewrite keeps the allocation already there rather than copying the characters again.

Three helpers, so the call sites keep their shape

A set yields wrappers, so every for (key, record) and every get would have to unwrap.
record(key), records_iter() and records_range(..) put the map's own shapes back, which is
what kept this to about fifty call sites instead of a hundred. records.len() and
records.is_empty() did not change at all.

get_mut has no equivalent and should not: changing a record in place could change where it
belongs. Its one caller is a test, which now takes the record out, changes it, and files it back.

The leg that weighs it and the leg that breaks it down are different files

I moved the breakdown in footprint_audit.rs and remeasured: the slope was still 128 and
every detail figure was right. The total that feeds the published line is a different function in
services/meta/footprint.rs, still charging size_of::<SharedText>() + size_of::<MetadataRecord>()
per entry. Moving that took the slope to 112.

A round-trip of the breakdown against its own total cannot catch this — the breakdown shrank and
the total did not, and the sum assertion is not what runs first. Grep the type's name in every
size_of before believing a figure did not move.

What became structural rather than measured

meta.records.record_key_capacity, key_text_duplicated and key_text_shared measured that the
map key and the record's own key were one allocation. There is no second holder to share with any
more, so those three are true by construction. They are kept — they are what the document
publishes, and reintroducing a second holder would move them — but a type is doing the work a
tripwire used to, and the document now says that instead of claiming a sharing discipline that no
longer exists.

measure_what_a_one_word_text_handle_would_save lost its fourth leg for the same reason — the
third time that measurement has gone stale. It was still adding a map-key handle to
meta.records.struct_inline, which would have published a saving of sixteen where eight is
available. Six holders an object now, not seven: 48 inline, net 32, or 16 without unsafe
code.

The figures, each re-measured

amplification rows 1 KiB 3.38x → 3.36x; 4 KiB and 64 KiB unchanged
striped against replicated 2.03x → 2.01x; 3.51x → 3.50x
metadata an object at 100 / 1,000 / 4,000 / 8,000 538 / 494 / 371 / 350
delete trade holding 1,791,633, emptied 2,260,646, 26.0% → 26.1%
the share table objects 210 (63.6%), records 112 (33.9%), keys 8 (2.4%)

What is left

112  today
104  the deleted flag into the version's top bit -- one bit in its own word
 96  and the key's characters owned rather than shared -- the one rung that carries a
     trade: one copy per retained change, about 40,960 bytes of fixed term, paying
     above 2,560 objects

Measured

  • Suite: 412 passing on the default feature set, 433 with every feature, the same 21 failures in both and name for name the baseline in tools/known_failures.txt
  • cargo build --all-targets --locked and --all-features, RUSTFLAGS=-D warnings — green
  • cargo doc --no-deps --locked and --all-features, RUSTDOCFLAGS=-D warnings — green

🤖 Generated with Claude Code

`records` was a map from a key to a record that already held that key: two
sixteen-byte handles on one allocation, once per object. A record knows its own
key, so the collection is now ordered BY that key rather than beside it.

  meta.records.struct_inline     72 -> 56
  the metadata service's share  128 -> 112
  the metadata line             346 -> 330, and the FIXED term 164,256 -> 164,240
                                     -- the bucket's own record is one more entry

THE COMPILER PRICED THE OTHER ROUTE AND IT WAS THE WRONG ONE

The obvious change is to drop `MetadataRecord::key`. Done as a probe, that is 66
errors over 29 files -- but the error count is not the cost. The cost is that
about thirty-two signatures carry a record WITHOUT its key
(`validate_meta_key(&record.key)`, `shard_contains_key(&record.key)`, the proof
encoder, the footprint audit), and each would have to carry a key beside it.
Handoff envelopes would become pairs. This keeps every one of those sites
compiling and touches the collection instead.

WHAT IT COSTS IS AN ORD/EQ CONTRACT, AND THAT IS WHY THERE IS A WRAPPER

`BTreeSet` requires `Ord` and `Eq` to AGREE: if `a == b` then `a.cmp(&b)` must be
`Equal`, or the set's ordering invariant is unspecified rather than merely
wrong. The ordering has to be the key, because the key order is what
`records_range` serves prefix scans from. So equality is the key too.

`KeyedRecord` therefore says NOTHING about whether two records hold the same
value, and its record is PRIVATE so that nothing outside the collection can
compare two of them by accident. A cross-type `PartialEq<MetadataRecord>` was
deliberately NOT added: it would have made
`self.records.get(k) != Some(record)` read as "equal" for two records holding
different values, which is a drift detector that detects nothing.

`MatrixMetaService` does not derive `PartialEq`, and `MetaSnapshot` keeps its
map -- it is a transient encode and decode structure, not resident state -- so
no container's derived equality becomes a key-only comparison.

AND ONE HAZARD THAT IS NOT IN THE ORDERING

`BTreeSet::insert` KEEPS the element already there and returns `false`. A map's
`insert` overwrites. So filing a new version of a record with `insert` would
leave the OLD version in the collection and return a value that reads like
success -- with every byte figure, every count and every ordering assertion
still correct. A write would simply not take effect.

`file_record` uses `replace`, and
`filing_a_record_over_an_existing_key_replaces_it` is the guard: a second write
at one key must not add a record, the filed version must be the NEW one, and it
must agree with the object as it now stands. It also asserts what the set makes
impossible -- that a record is always found under the key it holds, through the
same allocation -- and that a rewrite keeps the allocation already there rather
than copying the characters again.

THREE HELPERS, SO THE CALL SITES KEEP THEIR SHAPE

A set yields wrappers, so every `for (key, record)` and every `get` would have
to unwrap. `record(key)`, `records_iter()` and `records_range(..)` put the map's
own shapes back, which is what kept this to about fifty call sites instead of a
hundred. `records.len()` and `records.is_empty()` did not change at all.

`get_mut` has no equivalent and should not: changing a record in place could
change where it belongs. Its one caller is a test, which now takes the record
out, changes it, and files it back.

THE LEG THAT WEIGHS IT, AND THE LEG THAT BREAKS IT DOWN, ARE DIFFERENT FILES

I moved the breakdown in `footprint_audit.rs` and remeasured: the slope was
still 128 and every detail figure was right. The total that feeds the published
line is a different function in `services/meta/footprint.rs`, and it was still
charging `size_of::<SharedText>() + size_of::<MetadataRecord>()` per entry.
Moving that took the slope to 112.

A round-trip of the breakdown against its own total cannot catch this: the
breakdown shrank and the total did not, and the sum assertion is not what runs
first. Grep the type's name in every `size_of` before believing a figure did not
move.

WHAT BECAME STRUCTURAL RATHER THAN MEASURED

`meta.records.record_key_capacity`, `key_text_duplicated` and `key_text_shared`
measured that the map key and the record's own key were one allocation. There is
no second holder to share with any more, so those three are true BY
CONSTRUCTION. They are kept -- they are what the document publishes, and
reintroducing a second holder would move them -- but a type is doing the work a
tripwire used to, and the document says so now instead of claiming a sharing
discipline that no longer exists.

`measure_what_a_one_word_text_handle_would_save` lost its fourth leg for the
same reason, the THIRD time that measurement has gone stale: it was still
adding a map-key handle to `meta.records.struct_inline`, which would have
published a saving of sixteen where eight is available. Six holders an object
now, not seven: 48 inline, net 32 or 16 without unsafe code.

THE FIGURES, EACH RE-MEASURED

  1 KiB row  3.38x -> 3.36x       4 KiB and 64 KiB rows unchanged
  striped    2.03x -> 2.01x       replicated 3.51x -> 3.50x
  metadata an object at 100 / 1,000 / 4,000 / 8,000   538 / 494 / 371 / 350
  delete trade   holding 1,791,633, emptied 2,260,646, 26.0% -> 26.1% larger
  the share table    objects 210 (63.6%), records 112 (33.9%), keys 8 (2.4%)

WHAT IS LEFT

  112  today
  104  the deleted flag into the version's top bit -- one bit in its own word
   96  and the key's characters owned rather than shared, which is the one rung
       that carries a trade: one copy per retained change, about 40,960 bytes of
       fixed term, paying above 2,560 objects

MEASURED

Suite: 412 passing on the default feature set and 433 with every
feature, with the same 21 failures in both, name for name the baseline in
tools/known_failures.txt. Green on cargo build --all-targets for both feature sets with
warnings denied, and on cargo doc for both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@matrixarkai
matrixarkai merged commit 26612b2 into main Oct 24, 2026
2 of 3 checks passed
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.

2 participants