This is an automated email from the ASF dual-hosted git repository.

jgus pushed a commit to branch 2.2
in repository https://gitbox.apache.org/repos/asf/kafka.git


The following commit(s) were added to refs/heads/2.2 by this push:
     new a297722  KAFKA-8570; Grow buffer to hold down converted records if it 
was insufficiently sized (#6974)
a297722 is described below

commit a297722cb138f34c4589b9da3b7f5dca6ea7a027
Author: Dhruvil Shah <[email protected]>
AuthorDate: Fri Jun 21 09:36:29 2019 -0700

    KAFKA-8570; Grow buffer to hold down converted records if it was 
insufficiently sized (#6974)
    
    When the log contains out of order message formats (for example v2 message 
followed by v1 message) and consists of compressed batches typically greater 
than 1kB in size, it is possible for down-conversion to fail. With compressed 
batches, we estimate the size of down-converted batches using:
    
    ```
        private static int estimateCompressedSizeInBytes(int size, 
CompressionType compressionType) {
            return compressionType == CompressionType.NONE ? size : 
Math.min(Math.max(size / 2, 1024), 1 << 16);
        }
    ```
    
    This almost always underestimates size of down-converted records if the 
batch is between 1kB-64kB in size. In general, this means we may under estimate 
the total size required for compressed batches.
    
    Because of an implicit assumption in the code that messages with a lower 
message format appear before any with a higher message format, we do not grow 
the buffer we copy the down converted records into when we see a message <= the 
target message format. This assumption becomes incorrect when the log contains 
out of order message formats, for example because of leaders flapping while 
upgrading the message format.
    
    Reviewers: Jason Gustafson <[email protected]>
---
 .../apache/kafka/common/record/RecordsUtil.java    |  3 ++
 .../kafka/common/record/FileRecordsTest.java       | 34 ++++++++++++++++++++++
 2 files changed, 37 insertions(+)

diff --git 
a/clients/src/main/java/org/apache/kafka/common/record/RecordsUtil.java 
b/clients/src/main/java/org/apache/kafka/common/record/RecordsUtil.java
index 3b0c59a..423d1e1 100644
--- a/clients/src/main/java/org/apache/kafka/common/record/RecordsUtil.java
+++ b/clients/src/main/java/org/apache/kafka/common/record/RecordsUtil.java
@@ -18,6 +18,7 @@ package org.apache.kafka.common.record;
 
 import org.apache.kafka.common.errors.UnsupportedCompressionTypeException;
 import org.apache.kafka.common.utils.Time;
+import org.apache.kafka.common.utils.Utils;
 
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
@@ -80,9 +81,11 @@ public class RecordsUtil {
         ByteBuffer buffer = ByteBuffer.allocate(totalSizeEstimate);
         long temporaryMemoryBytes = 0;
         int numRecordsConverted = 0;
+
         for (RecordBatchAndRecords recordBatchAndRecords : 
recordBatchAndRecordsList) {
             temporaryMemoryBytes += recordBatchAndRecords.batch.sizeInBytes();
             if (recordBatchAndRecords.batch.magic() <= toMagic) {
+                buffer = Utils.ensureCapacity(buffer, buffer.position() + 
recordBatchAndRecords.batch.sizeInBytes());
                 recordBatchAndRecords.batch.writeTo(buffer);
             } else {
                 MemoryRecordsBuilder builder = convertRecordBatch(toMagic, 
buffer, recordBatchAndRecords);
diff --git 
a/clients/src/test/java/org/apache/kafka/common/record/FileRecordsTest.java 
b/clients/src/test/java/org/apache/kafka/common/record/FileRecordsTest.java
index 20ecba1..bf79987 100644
--- a/clients/src/test/java/org/apache/kafka/common/record/FileRecordsTest.java
+++ b/clients/src/test/java/org/apache/kafka/common/record/FileRecordsTest.java
@@ -35,6 +35,7 @@ import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Optional;
+import java.util.Random;
 
 import static java.util.Arrays.asList;
 import static org.apache.kafka.common.utils.Utils.utf8;
@@ -435,6 +436,39 @@ public class FileRecordsTest {
     }
 
     @Test
+    public void testDownconversionAfterMessageFormatDowngrade() throws 
IOException {
+        // random bytes
+        Random random = new Random();
+        byte[] bytes = new byte[3000];
+        random.nextBytes(bytes);
+
+        // records
+        CompressionType compressionType = CompressionType.GZIP;
+        List<Long> offsets = asList(0L, 1L);
+        List<Byte> magic = asList(RecordBatch.MAGIC_VALUE_V2, 
RecordBatch.MAGIC_VALUE_V1);  // downgrade message format from v2 to v1
+        List<SimpleRecord> records = asList(
+                new SimpleRecord(1L, "k1".getBytes(), bytes),
+                new SimpleRecord(2L, "k2".getBytes(), bytes));
+        byte toMagic = 1;
+
+        // create MemoryRecords
+        ByteBuffer buffer = ByteBuffer.allocate(8000);
+        for (int i = 0; i < records.size(); i++) {
+            MemoryRecordsBuilder builder = MemoryRecords.builder(buffer, 
magic.get(i), compressionType, TimestampType.CREATE_TIME, 0L);
+            builder.appendWithOffset(offsets.get(i), records.get(i));
+            builder.close();
+        }
+        buffer.flip();
+
+        // create FileRecords, down-convert and verify
+        try (FileRecords fileRecords = FileRecords.open(tempFile())) {
+            fileRecords.append(MemoryRecords.readableRecords(buffer));
+            fileRecords.flush();
+            downConvertAndVerifyRecords(records, offsets, fileRecords, 
compressionType, toMagic, 0L, time);
+        }
+    }
+
+    @Test
     public void testConversion() throws IOException {
         doTestConversion(CompressionType.NONE, RecordBatch.MAGIC_VALUE_V0);
         doTestConversion(CompressionType.GZIP, RecordBatch.MAGIC_VALUE_V0);

Reply via email to