HDDS-15857. Fix checksum calculation in KeyValueHandler for variable-sized chunks#10764
HDDS-15857. Fix checksum calculation in KeyValueHandler for variable-sized chunks#10764chungen0126 wants to merge 10 commits into
Conversation
|
Hi @szetszwo , @sodonnel ,During my review of the current Stream Read architecture, I identified a checksum calculation issue that affects both the client and server sides.This issue occurs when a client actively triggers a flush or hsync. In such cases, the resulting chunk size does not perfectly align with
To properly verify checksums on the client side, I believe it is essential to return chunkInfo. If we go down this path, we should refine the structure of ReadBlockResponseProto, as the top-level checksumData might become redundant. Since modifying Protobuf definitions after a major release is highly problematic due to backward compatibility/wire-protocol locking, this issue could potentially block or impact the upcoming Ozone 2.2 release. I'd love to hear your thoughts on this. |
There was a problem hiding this comment.
Pull request overview
This PR fixes streaming-read checksum handling when block chunks are not uniform (variable-sized or smaller than bytesPerChunk) by making checksum selection and verification aware of real chunk boundaries, and by extending the read-block protocol to optionally return chunk metadata needed for correct verification.
Changes:
- Reworked server-side
KeyValueHandler#getChecksumsto select checksum slices based on chunk overlap rather than fixedbytesPerChunkindexing. - Extended
ReadBlockResponsePrototo optionally includechunkInfoList, and wired server/client to use chunk boundaries during checksum verification. - Added unit and integration tests covering varying/small chunk sizes with larger
bytesPerChecksum.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| hadoop-ozone/integration-test/src/test/java/org/apache/hadoop/ozone/client/rpc/read/TestStreamRead.java | Adds integration coverage for small chunks with larger checksums during streaming read. |
| hadoop-hdds/interface-client/src/main/proto/DatanodeClientProtocol.proto | Adds optional chunkInfoList to ReadBlockResponseProto for boundary-aware verification. |
| hadoop-hdds/container-service/src/test/java/org/apache/hadoop/ozone/container/keyvalue/TestKeyValueHandler.java | Adds unit tests validating checksum slicing with varying/small chunk sizes. |
| hadoop-hdds/container-service/src/main/java/org/apache/hadoop/ozone/container/keyvalue/KeyValueHandler.java | Updates checksum slicing logic and read-block response building to support variable chunk sizes. |
| hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/scm/protocolPB/ContainerCommandResponseBuilders.java | Populates chunk info into read-block responses (needs conditional sending for perf/compat). |
| hadoop-hdds/client/src/test/java/org/apache/hadoop/hdds/scm/storage/TestStreamBlockInputStream.java | Updates test response protos to include chunk info for checksum verification paths. |
| hadoop-hdds/client/src/main/java/org/apache/hadoop/hdds/scm/storage/StreamBlockInputStream.java | Adds boundary-aware checksum verification using chunkInfoList. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (verifyChecksum) { | ||
| ChecksumData checksumData = ChecksumData.getFromProtoBuf(readBlock.getChecksumData()); | ||
| Checksum.verifyChecksum(data, checksumData, 0); | ||
| if (readBlock.hasChunkInfoList()) { | ||
| verifyChecksumForReadBlock(data, checksumData, readBlock); | ||
| } else { | ||
| throw new IOException("Checksum data is missing for block " + getBlockID()); | ||
| } |
| int bytesPerChecksum = checksumData.getBytesPerChecksum(); | ||
| long blockOffset = readBlock.getOffset(); | ||
| long readLength = data.remaining(); | ||
| long currentChunkOffset = 0; | ||
| int checksumIndex = 0; | ||
| int dataOffset = 0; | ||
|
|
||
| for (ContainerProtos.ChunkInfo chunk : readBlock.getChunkInfoList().getChunksList()) { | ||
| long chunkStart = currentChunkOffset; | ||
| long chunkEnd = chunkStart + chunk.getLen(); | ||
|
|
| long currentChunkOffset = 0; | ||
| for (ContainerProtos.ChunkInfo chunk : chunks) { | ||
| long chunkStart = currentChunkOffset; | ||
| long chunkEnd = chunkStart + chunk.getLen(); | ||
|
|
||
| long overlapStart = Math.max(blockOffset, chunkStart); | ||
| long overlapEnd = Math.min(blockOffset + readLength, chunkEnd); | ||
|
|
| public static ContainerCommandResponseProto getReadBlockResponse( | ||
| ContainerCommandRequestProto request, ChecksumData checksumData, ByteBuffer data, long offset) { | ||
| ContainerCommandRequestProto request, ChecksumData checksumData, | ||
| ByteBuffer data, long offset, List<ChunkInfo> chunkInfoList, boolean verifyChecksum) { | ||
|
|
||
| ContainerProtos.ReadBlockResponseProto response = ContainerProtos.ReadBlockResponseProto.newBuilder() | ||
| ContainerProtos.ReadBlockResponseProto response = ReadBlockResponseProto.newBuilder() | ||
| .setChecksumData(checksumData.getProtoBufMessage()) | ||
| .setData(ByteString.copyFrom(data)) | ||
| .setOffset(offset) | ||
| .setChunkInfoList(ChunkInfoList.newBuilder().addAllChunks(chunkInfoList).build()) | ||
| .build(); |
| OzoneConfiguration conf = cluster.getConf(); | ||
| OzoneClientConfig clientConfig = conf.getObject(OzoneClientConfig.class); | ||
| clientConfig.setStreamReadBlock(true); | ||
| clientConfig.setStreamBufferFlushDelay(false); | ||
| final OzoneConfiguration steamReadConf = new OzoneConfiguration(conf); | ||
| steamReadConf.setFromObject(clientConfig); | ||
|
|
||
| try (OzoneClient streamReadClient = OzoneClientFactory.getRpcClient(steamReadConf)) { |
Bugbot reviewStream checksum verify misaligns chunks — With Generated-by: Cursor Bugbot |
What changes were proposed in this pull request?
Problem
Currently, the
getChecksumsmethod inKeyValueHandler.javafails or returns incorrect checksums when chunks have variable sizes or are smaller than bytesPerChunk.The original code assumes all chunks (except the last one) have a fixed size equal to
bytesPerChunk. This assumption is incorrect when chunks have different lengths, leading to wrong index calculations.Crucially, when checksum verification is enabled, the DataNode must return the actual ChunkInfo (which contains chunk lengths) to the client. Without this metadata, the client cannot perceive the boundaries of each varying chunk, making it impossible to correctly map the offsets and compute/verify the checksums.
Solution
This PR resolves the issue by tracking exact chunk boundaries on both the server and client sides:
Boundary-Aware Verification: In StreamBlockInputStream#onNext, if the response contains chunkInfoList and checksum verification is enabled:
What is the link to the Apache JIRA
https://issues.apache.org/jira/browse/HDDS-15857
How was this patch tested?
Added
testGetChecksumsWithVaryingChunkSizesandtestGetChecksumsWithSmallChunksinTestKeyValueHandler.java.Added
testSmallChunksWithLargeChecksuminTestStreamRead.java.CI: https://github.com/chungen0126/ozone/actions/runs/29381590591