[SPARK-39702][CORE] Reduce memory overhead of TransportCipher$EncryptedMessage by using a shared byteRawChannel #37110
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
What changes were proposed in this pull request?
This patch aims to reduce the memory overhead of
TransportCipher$EncryptedMessage. In the current code, theEncryptedMessageconstructor eagerly initializes aByteArrayWritableChannel byteRawChannel(which consumes ~32kb of memory). If there are manyEncryptedMessageinstances on the heap (e.g. because there is a long queue of outgoing messages on a channel) then this overhead adds up and can cause OOMs or GC problems.SPARK-24801 / #21811 fixed a similar issue in
SaslEncryption. There, the fix was to lazily initialize the buffer: the buffer isn't actually accessed beforetransferTo()is called (and is only used there), so lazily initializing it there reduces memory requirements for queued outgoing messages.In principle we could apply a similar lazy initialization fix here. In this PR, however, I have taken a different approach: I construct a single shared
ByteArrayWritableChannel byteRawChannelinTransportChannel$EncryptionHandlerand pass that shared instance to theEncryptedMessageconstructor. I believe that this is safe because we are already doing this for thebyteEncChannelchannel buffer. That sharedbyteEncChannelgetsreset()whenEncryptedMessage.deallocate()is called. If we assume that existing sharing is correct then I think it's okay to apply similar sharing of thebyteRawChannelbuffer because its scope of use and lifecycle is similar.Why are the changes needed?
Improve performance and reduce a source of OOMs when encryption is enabled.
Does this PR introduce any user-facing change?
No.
How was this patch tested?
Correctness: Existing unit tests.
PerformanceI: observed memory usage and performance improvements by running an artificial workload that significantly stresses the shuffle sending path. On a two-host Spark Standalone cluster where each host had an external shuffle service (with 1gb heap) and a 64-core executor, I ran the following code:
Prior to this patch, this job reliably failed because the Worker (where the shuffle service runs) would fill its heap and go into long GC pauses, eventually causing it to become disassociated from the Master. After this patch's changes, this job smoothly runs to completion.