[ 
https://issues.apache.org/jira/browse/CASSANDRA-14611?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Joseph Lynch updated CASSANDRA-14611:
-------------------------------------
    Description: 
While I was analyzing flame graphs as part of investigating CASSANDRA-14605 I 
noticed that digest overhead for quorum reads of partitions with many small 
columns is much higher than I would expect. 

In this particular use case of ({{LOCAL_QUORUM}}) reads of partitions with  
(p50, p95, p99) = (60, 5k, 20k) small columns (a {{set<int>}}), digest requests 
make up 45%+ of the CPU time that Cassandra is using. About 20% is reading the 
columns into the iterator abstraction (bummer) but surprisingly about 20% is 
_calculating the digests themselves_. I would expect digest calculation itself 
to be close to 2% total ... I've attached a flame graph showing this behavior.

The part that is particularly concerning to me is that ~17% of CPU time (~1/3 
of the overall digest time) is spent in calculating {{[AbstractCell::digest 
|https://github.com/apache/cassandra/blob/d52c7b8c595cc0d06fc3607bf16e3f595f016bb6/src/java/org/apache/cassandra/db/rows/AbstractCell.java#L54-L56]}}
 but only ~2% is digesting the actual cell values. Looking deeper the 
implementation of 
{{[FBUtilities::updateWithInt|https://github.com/apache/cassandra/blob/cassandra-3.0/src/java/org/apache/cassandra/utils/FBUtilities.java#L834]}}
 and other related functions ({{updateWithBoolean}}, {{updateWithLong}}, 
etc...) are very inefficient as they call update on the underlying 
{{MessageDigest}} on each byte individually. When you have lots of small cells 
the overhead of this approach is apparently significant. In particular it looks 
like the underlying MD5 implementation is not as efficient as it should be for 
lots of single byte updates as it does attempt to buffer them into a 64 byte 
buffer, but that's too small to get maximum throughput. In my local 
benchmarking I'm seeing just grouping the metadata updates into a 13 byte array 
yields a 3x performance improvement but it has 13 bytes of allocated memory per 
cell during the hash that previously we didn't allocate.

I've been playing around with a patch which changes out the normal MD5 digest 
for a buffered one (e.g. has a fixed 1kb byte array per read thread which is 
used to roll up digest updates and then whenever the buffer fills it does an 
underlying update). So far such a buffered MessageDigest is showing 2x better 
performance on hashing 10k column metadata in a jmh microbenchmark I wrote. If 
I combine that with metadata grouping into a 13 byte array I get 10x more 
throughput. I think I need to do more profiling but I think that buffering our 
digest calls could really help for wide partitions of smaller columns (as 
opposed to fat single columns, where the metadata hashing overhead is probably 
pretty negligible).

  was:
While I was analyzing flame graphs as part of investigating CASSANDRA-14605 I 
noticed that digest overhead for quorum reads of partitions with many small 
columns is much higher than I would expect. 

In this particular use case of ({{LOCAL_QUORUM}}) reads of partitions with  
(p50, p95, p99) = (60, 5k, 20k) small columns (a {{set<int>}}), digest 
calculation makes up 45%+ of the CPU time that Cassandra is using. About 20% is 
reading the columns into the iterator abstraction (bummer) but surprisingly 
about 20% is _calculating the digests_. I would expect digest calculation 
itself to be close to 2% total ... I've attached a flame graph showing this 
behavior.

The part that is particularly concerning to me is that ~17% of CPU time (~1/3 
of the overall digest time) is spent in calculating {{[AbstractCell::digest 
|https://github.com/apache/cassandra/blob/d52c7b8c595cc0d06fc3607bf16e3f595f016bb6/src/java/org/apache/cassandra/db/rows/AbstractCell.java#L54-L56]}}
 but only ~2% is digesting the actual cell values. Looking deeper the 
implementation of 
{{[FBUtilities::updateWithInt|https://github.com/apache/cassandra/blob/cassandra-3.0/src/java/org/apache/cassandra/utils/FBUtilities.java#L834]}}
 and other related functions ({{updateWithBoolean}}, {{updateWithLong}}, 
etc...) are very inefficient as they call update on the underlying 
{{MessageDigest}} on each byte individually. When you have lots of small cells 
the overhead of this approach is apparently significant. In particular it looks 
like the underlying MD5 implementation is not as efficient as it should be for 
lots of single byte updates as it does attempt to buffer them into a 64 byte 
buffer, but that's too small to get maximum throughput. In my local 
benchmarking I'm seeing just grouping the metadata updates into a 13 byte array 
yields a 3x performance improvement but it has 13 bytes of allocated memory per 
cell during the hash that previously we didn't allocate.

I've been playing around with a patch which changes out the normal MD5 digest 
for a buffered one (e.g. has a fixed 1kb byte array per read thread which is 
used to roll up digest updates and then whenever the buffer fills it does an 
underlying update). So far such a buffered MessageDigest is showing 2x better 
performance on hashing 10k column metadata in a jmh microbenchmark I wrote. If 
I combine that with metadata grouping into a 13 byte array I get 10x more 
throughput. I think I need to do more profiling but I think that buffering our 
digest calls could really help for wide partitions of smaller columns (as 
opposed to fat single columns, where the metadata hashing overhead is probably 
pretty negligible).


> Quorum reads of wide partitions are expensive due to inefficient hashing in 
> AbstractCell
> ----------------------------------------------------------------------------------------
>
>                 Key: CASSANDRA-14611
>                 URL: https://issues.apache.org/jira/browse/CASSANDRA-14611
>             Project: Cassandra
>          Issue Type: Improvement
>         Environment: AWS, i3.4xlarge (very fast nvme drives), Linux 4.13.
> Cassandra 3.0.16, Java 8u162
>            Reporter: Joseph Lynch
>            Priority: Minor
>              Labels: performance
>         Attachments: quorum_reads_lots_of_small_columns.svg
>
>
> While I was analyzing flame graphs as part of investigating CASSANDRA-14605 I 
> noticed that digest overhead for quorum reads of partitions with many small 
> columns is much higher than I would expect. 
> In this particular use case of ({{LOCAL_QUORUM}}) reads of partitions with  
> (p50, p95, p99) = (60, 5k, 20k) small columns (a {{set<int>}}), digest 
> requests make up 45%+ of the CPU time that Cassandra is using. About 20% is 
> reading the columns into the iterator abstraction (bummer) but surprisingly 
> about 20% is _calculating the digests themselves_. I would expect digest 
> calculation itself to be close to 2% total ... I've attached a flame graph 
> showing this behavior.
> The part that is particularly concerning to me is that ~17% of CPU time (~1/3 
> of the overall digest time) is spent in calculating {{[AbstractCell::digest 
> |https://github.com/apache/cassandra/blob/d52c7b8c595cc0d06fc3607bf16e3f595f016bb6/src/java/org/apache/cassandra/db/rows/AbstractCell.java#L54-L56]}}
>  but only ~2% is digesting the actual cell values. Looking deeper the 
> implementation of 
> {{[FBUtilities::updateWithInt|https://github.com/apache/cassandra/blob/cassandra-3.0/src/java/org/apache/cassandra/utils/FBUtilities.java#L834]}}
>  and other related functions ({{updateWithBoolean}}, {{updateWithLong}}, 
> etc...) are very inefficient as they call update on the underlying 
> {{MessageDigest}} on each byte individually. When you have lots of small 
> cells the overhead of this approach is apparently significant. In particular 
> it looks like the underlying MD5 implementation is not as efficient as it 
> should be for lots of single byte updates as it does attempt to buffer them 
> into a 64 byte buffer, but that's too small to get maximum throughput. In my 
> local benchmarking I'm seeing just grouping the metadata updates into a 13 
> byte array yields a 3x performance improvement but it has 13 bytes of 
> allocated memory per cell during the hash that previously we didn't allocate.
> I've been playing around with a patch which changes out the normal MD5 digest 
> for a buffered one (e.g. has a fixed 1kb byte array per read thread which is 
> used to roll up digest updates and then whenever the buffer fills it does an 
> underlying update). So far such a buffered MessageDigest is showing 2x better 
> performance on hashing 10k column metadata in a jmh microbenchmark I wrote. 
> If I combine that with metadata grouping into a 13 byte array I get 10x more 
> throughput. I think I need to do more profiling but I think that buffering 
> our digest calls could really help for wide partitions of smaller columns (as 
> opposed to fat single columns, where the metadata hashing overhead is 
> probably pretty negligible).



--
This message was sent by Atlassian JIRA
(v7.6.3#76005)

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

Reply via email to