[ 
https://issues.apache.org/jira/browse/CASSANDRA-21683?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18116521#comment-18116521
 ] 

Jon Haddad commented on CASSANDRA-21683:
----------------------------------------

The builder now encodes each partition key's byte-comparable form once, into a 
reusable scratch buffer, and reuses those bytes for every step that used to 
re-encode the key.

The changes in \{{PartitionIndexBuilder}}:

- \{{materialize()}} writes a key's comparable bytes into a scratch buffer and 
returns the length. The buffer grows on demand and is reused across partitions, 
so in steady state this path allocates nothing.

- \{{diffPoint}} is now an array compare (\{{Arrays.mismatch}} over the two 
buffers, plus one to match the old convention) instead of encoding the keys 
again.

- The prefix handed to the trie is a \{{ByteComparable.fixedLength}} view over 
the captured bytes, not a freshly cut byte source.

- A three-slot ring of buffers holds the three keys that are live at the same 
instant: the current key, the previous key, and the previous key the trie reads 
again when the next key is added. The ring advances one slot per key. Two 
buffers would overwrite the previous key before the trie was done with it.

Correctness

- \{{fixedLength}} does not stop at the end of a key's bytes the way the old 
\{{ByteComparable.cut}} did, so the code limits the prefix length explicitly to 
build the exact same prefix.

- An assertion guards the ring so a slot is never reused while it still holds 
the last key or the key the trie reads again.

- The on-disk index is byte-identical to before.

 

Verification

 

- \{{PartitionIndexBuilderReencodeTest}} runs the patched builder with a 
recording trie writer, then asserts every prefix and payload matches the 
original \{{diffPoint}}/\{{cut}} logic byte for byte. It also checks the 
previous-key re-read, so a wrong ring size (three slots down to two) fails the 
test. It covers adversarial byte shapes and a non-default partitioner.

- JMH \{{PartitionIndexBuildBench}} with \{{-prof gc}}: allocation per 
partition drops about 288 bytes, -59.3% at one million keys, with no change to 
build time.

The builder now encodes each partition key's byte-comparable form once, into a 
reusable scratch buffer, and reuses those bytes for every step that used to 
re-encode the key.

The changes in \{{PartitionIndexBuilder}}:

- \{{materialize()}} writes a key's comparable bytes into a scratch buffer and 
returns the length. The buffer grows on demand and is reused across partitions, 
so in steady state this path allocates nothing.
- \{{diffPoint}} is now an array compare (\{{Arrays.mismatch}} over the two 
buffers, plus one to match the old convention) instead of encoding the keys 
again.
- The prefix handed to the trie is a \{{ByteComparable.fixedLength}} view over 
the captured bytes, not a freshly cut byte source.
- A three-slot ring of buffers holds the three keys that are live at the same 
instant: the current key, the previous key, and the previous key the trie reads 
again when the next key is added. The ring advances one slot per key. Two 
buffers would overwrite the previous key before the trie was done with it.

Correctness

- \{{fixedLength}} does not stop at the end of a key's bytes the way the old 
\{{ByteComparable.cut}} did, so the code limits the prefix length explicitly to 
build the exact same prefix.
- An assertion guards the ring so a slot is never reused while it still holds 
the last key or the key the trie reads again.
- The on-disk index is byte-identical to before.

Verification

- \{{PartitionIndexBuilderReencodeTest}} runs the patched builder with a 
recording trie writer, then asserts every prefix and payload matches the 
original \{{diffPoint}}/\{{cut}} logic byte for byte. It also checks the 
previous-key re-read, so a wrong ring size (three slots down to two) fails the 
test. It covers adversarial byte shapes and a non-default partitioner.
- JMH \{{PartitionIndexBuildBench}} with \{{-prof gc}}: allocation per 
partition drops about 288 bytes, -59.3% at one million keys, with no change to 
build time.

> Reduce allocations in BTI writes
> --------------------------------
>
>                 Key: CASSANDRA-21683
>                 URL: https://issues.apache.org/jira/browse/CASSANDRA-21683
>             Project: Apache Cassandra
>          Issue Type: Improvement
>          Components: Local/SSTable
>            Reporter: Jon Haddad
>            Assignee: Jon Haddad
>            Priority: Normal
>
> When Cassandra writes a BTI SSTable, it builds a partition index: a trie that 
> maps each partition key to its position on disk. To place a key in the trie, 
> the builder first converts it to a byte-comparable form, a byte string that 
> sorts in the same order as the key.
> That conversion runs repeatedly. For each partition, the builder re-encodes 
> the same one or two keys about four times: once to find where a key differs 
> from its neighbour, once to cut the shared prefix, and again when the trie 
> re-reads the previous key. Each encode allocates a chain of throwaway objects 
> (ByteSource, Multi, varargs arrays, escapers), roughly nineteen short-lived 
> objects per partition. Across a flush or compaction with millions of 
> partitions, this is a large amount of garbage, and it pressures the garbage 
> collector



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to