diff --git a/CHANGES.txt b/CHANGES.txt index 84812a1cad..7b3c3c86be 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 6.0-alpha2 + * Reduce allocations and array copies due to buffer resizing in LocalDataResponse during row serialization (CASSANDRA-21285) * Implement a guardrail for client driver versions (CASSANDRA-21146) * Enable IAuthenticator to declare supported and alterable role options (CASSANDRA-20834) * Avoid capturing lambda allocation in UnfilteredSerializer.deserializeRowBody (CASSANDRA-21289) diff --git a/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java b/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java index 8a60d44797..c4adaf0c2a 100644 --- a/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java +++ b/src/java/org/apache/cassandra/config/CassandraRelevantProperties.java @@ -205,6 +205,8 @@ public enum CassandraRelevantProperties /** Controls the type of bufffer (heap/direct) used for shared scratch buffers */ DATA_OUTPUT_BUFFER_ALLOCATE_TYPE("cassandra.dob.allocate_type"), DATA_OUTPUT_STREAM_PLUS_TEMP_BUFFER_SIZE("cassandra.data_output_stream_plus_temp_buffer_size", "8192"), + DATA_RESPONSE_BUFFER_INITIAL_SIZE_MAX("cassandra.data_response_buffer_initial_size_max", "4096"), + DATA_RESPONSE_BUFFER_INITIAL_SIZE_MIN("cassandra.data_response_buffer_initial_size_min", "128"), DECAYING_ESTIMATED_HISTOGRAM_RESERVOIR_STRIPE_COUNT("cassandra.dehr_stripe_count", "2"), DEFAULT_PROVIDE_OVERLAPPING_TOMBSTONES("default.provide.overlapping.tombstones"), /** determinism properties for testing */ diff --git a/src/java/org/apache/cassandra/db/ReadResponse.java b/src/java/org/apache/cassandra/db/ReadResponse.java index 65e17a6920..7edd743ad4 100644 --- a/src/java/org/apache/cassandra/db/ReadResponse.java +++ b/src/java/org/apache/cassandra/db/ReadResponse.java @@ -24,6 +24,7 @@ import java.util.List; import com.google.common.annotations.VisibleForTesting; +import org.apache.cassandra.config.CassandraRelevantProperties; import org.apache.cassandra.db.filter.ColumnFilter; import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator; import org.apache.cassandra.db.partitions.UnfilteredPartitionIterators; @@ -38,6 +39,8 @@ import org.apache.cassandra.io.util.DataOutputPlus; import org.apache.cassandra.net.MessagingService; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.utils.ByteBufferUtil; +import org.apache.cassandra.utils.ExpMovingAverage; +import org.apache.cassandra.utils.MovingAverage; import static org.apache.cassandra.db.RepairedDataInfo.NO_OP_REPAIRED_DATA_INFO; @@ -224,6 +227,11 @@ public abstract class ReadResponse // built on the owning node responding to a query private static class LocalDataResponse extends DataResponse { + // Exponential moving average of response sizes, used to set initial size of output buffer. + private static final MovingAverage estimatedResponseBytes = ExpMovingAverage.decayBy1000(); + private static final int bufferInitialSizeMin = CassandraRelevantProperties.DATA_RESPONSE_BUFFER_INITIAL_SIZE_MIN.getInt(); + private static final int bufferInitialSizeMax = CassandraRelevantProperties.DATA_RESPONSE_BUFFER_INITIAL_SIZE_MAX.getInt(); + private LocalDataResponse(UnfilteredPartitionIterator iter, ReadCommand command, RepairedDataInfo rdi) { super(build(iter, command.columnFilter()), @@ -239,10 +247,21 @@ public abstract class ReadResponse private static ByteBuffer build(UnfilteredPartitionIterator iter, ColumnFilter selection) { - try (DataOutputBuffer buffer = new DataOutputBuffer()) + int initialBufferSize = bufferInitialSizeMin; + + if (bufferInitialSizeMax > bufferInitialSizeMin) + { + // Size output buffer to 10% above the moving average to absorb minor variance and limit rebuffering. + double estimatedResponseSize = estimatedResponseBytes.get(); + double bufferSizeEstimate = Double.isNaN(estimatedResponseSize) ? bufferInitialSizeMin : estimatedResponseSize * 1.1; + initialBufferSize = Math.min((int) bufferSizeEstimate, bufferInitialSizeMax); + } + + try (DataOutputBuffer buffer = new DataOutputBuffer(initialBufferSize)) { UnfilteredPartitionIterators.serializerForIntraNode().serialize(iter, selection, buffer, MessagingService.current_version); - return buffer.buffer(); + estimatedResponseBytes.update(buffer.position()); + return buffer.buffer(false); } catch (IOException e) {