KAFKA-10694: Implement zero copy for FetchSnapshot#9819
Conversation
|
@dengziming Thanks for the patch. I have one high level comment. The approach we've taken here is to implement a new zero-copy "region" type. One alternative is to use the existing "records" type since it already supports "zero-copy" and the snapshot data is expected to be records data anyway. I'd like to understand the drawbacks of this approach a bit better. I think having two zero-copy types is a bit undesirable. An alternative is that we can let the "region" type subsume "records," but I suspect that will be tricky because of the down-conversion logic. @jsancio also suggested that we might be able to represent the "region" type using Anyway, just want to make sure we have covered all of the options before getting too far with this. |
|
@dengziming @jsancio I'll give my take what I think we should do here, but let's try to agree before we go any further. Basically I am not really convinced that we need a new type, at least not in the near term. The snapshot data will be record data, so I cannot see a strong argument not to use the existing "records" type which already handles zero-copy. The one difference between Longer term, I think we can make an effort to replace the "records" type and just rely on interface ZeroCopyBuffer {
ByteBuffer read(int length);
Send toSend();
}When parsing a request or response received over the network, it will be in memory anyway, so we need some hook to get access to the ByteBuffer. On the other hand, when transmitting a request or response, we let the implementation build the Send itself. This gives us a way to work with Thoughts? |
|
Replying to #9819 (comment) Thanks @hachikuji for thinking about this and for giving a detail reply. For the long term solution, I am trying to understand how we transition from Outside of what you have enumerate, how would changing the type of a field work after the version bump? Would the generated message class need to support access to this field as both |
|
@jsancio It's a good question. Up to now, we have not enforced strict compatibility for the JSON schema representation. As long as the on-the-wire bytes are the same, we have allowed some flexibility for the generator. It was only in the past couple months that we finally got all of the APIs converted, so the flexibility was necessary. Personally I'd like to retain this loose compatibility going forward, but I guess it will depend on what users want. If there is a strong demand to keep the schema definitions compatible, then we will need to come up with some way to evolve them. However, for now, I don't think we should consider JSON type compatibility to be a blocker. |
|
The json schema is indeed not public api today. A KIP will be required before we make it so. |
|
The proposal in #9819 (comment) sounds good to me. I'll update the associated Jira soon. |
|
I have tried locally to replace the FileRegion with FileRecords, and it worked fine, which means that "aligned on offsets" is just a soft requirement of |
ee15035 to
74ba026
Compare
|
@hachikuji @jsancio @ijuma ,Hello, This pr is ready for a second review, Thank you. |
jsancio
left a comment
There was a problem hiding this comment.
Thanks for the change. Partial review. I wanted to give you some high-level comments early.
74ba026 to
7f58dd4
Compare
|
@dengziming Thanks for the updates. I think we're on the right track. I thought of a few potential simplifications. Take a look here: hachikuji@01cc3aa. |
|
@hachikuji ,Thank you for your suggestions, I found |
hachikuji
left a comment
There was a problem hiding this comment.
Thanks, this is looking good. Left a few more comment, but I think we're almost ready to wrap this up.
| } | ||
|
|
||
| @Override | ||
| public long writeTo(TransferableChannel destChannel, long previouslyWritten, int remaining) throws IOException { |
There was a problem hiding this comment.
Do we have test coverage for this?
There was a problem hiding this comment.
Add UnalignedFileRecordsTest
There was a problem hiding this comment.
Hmm. The semantic between the in-memory implementation vs the file channel implementation is slightly different. The in-memory version throws an exception if there are not enough bytes to send length. While the file channel version just sends less bytes if there is not enough to send remaining.
I suspect that we want to throw in both cases else the calling code won't know why writeTo sent less bytes. It could loop forever if it is attempting to send up to remaining bytes.
There was a problem hiding this comment.
@jsancio , I think you are right, I also find that the implementation of MemoryRecords and FileRecords is also different, I will spend some time checking this.
hachikuji
left a comment
There was a problem hiding this comment.
Thanks, should be the last round of reviews (at least from me).
| @Test | ||
| public void testWriteTo() throws IOException { | ||
|
|
||
| org.apache.kafka.common.requests.ByteBufferChannel channel = new org.apache.kafka.common.requests.ByteBufferChannel(fileRecords.sizeInBytes()); |
There was a problem hiding this comment.
nit: why not import ByteBufferChannel?
There was a problem hiding this comment.
ImportControl doesn't allow us to import ByteBufferChannel here, this is similar to NonOverflowingByteBufferChannel in your pr #4574
jsancio
left a comment
There was a problem hiding this comment.
@dengziming thanks for the changes. @hachikuji thanks for the reviews.
| } | ||
|
|
||
| @Override | ||
| public long writeTo(TransferableChannel destChannel, long previouslyWritten, int remaining) throws IOException { |
There was a problem hiding this comment.
Hmm. The semantic between the in-memory implementation vs the file channel implementation is slightly different. The in-memory version throws an exception if there are not enough bytes to send length. While the file channel version just sends less bytes if there is not enough to send remaining.
I suspect that we want to throw in both cases else the calling code won't know why writeTo sent less bytes. It could loop forever if it is attempting to send up to remaining bytes.
|
|
||
| ByteBuffer dup = sourceBuffer.duplicate(); | ||
| dup.position(position); | ||
| dup.limit(position + length); |
There was a problem hiding this comment.
Isn't this position() and limit() modification the same as ByteBuffer.slice?
Having said that I find it strange that we want to set the absolute position and limit. Shouldn't this be set relative to the current position? For example think about sourceBuffer.position() > position.
There was a problem hiding this comment.
There are many occurrences of position+limit in Kafka project, I think this is because the developers of Kafka like to handle the ByteBuffer by ourselves.
| public static long tryWriteTo(TransferableChannel destChannel, | ||
| int position, | ||
| int length, | ||
| ByteBuffer sourceBuffer) throws IOException { |
There was a problem hiding this comment.
How about removing the IO from this method with a signature like:
public static ByteBuffer relativeSlice(ByteBuffer buffer, int position, int length);The caller of this method can then long written = destChannel.write(Utils.relativeSlice(buffer, position, length)).
|
@hachikuji @jsancio Thank you for your great patience! |
More detailed description of your change
RawSnapshotWriterandRawSnapshotReaderinteract withBaseRecordsinstead ofByteBufferfileRecords.slicewhen read a snapshotSummary of testing strategy (including rationale)
RawSnapshotWriterandRawSnapshotReaderSendBuilderTestandFileRawSnapshotTestFileRegionandMemoryRegionCommitter Checklist (excluded from commit message)