-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Protocol message compression support #3287
Conversation
Codecov Report
@@ Coverage Diff @@
## develop #3287 +/- ##
========================================
Coverage 70.46% 70.46%
========================================
Files 678 678
Lines 54462 54462
========================================
Hits 38375 38375
Misses 16087 16087 Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice job on this! I did a pass and left some comments. Mostly nits.
*/ | ||
template<typename BufferFactory> | ||
std::pair<void const*, std::size_t> | ||
lz4fCompress(void const * in, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is cool, but maybe a little too cool and it requires all sorts of magic further down. I think that it's better to avoid all of this and encode the size in an expanded binary header, and use a much simpler implementation here, similar to what the nodestore uses with lz4_compress
and lz4_decompress
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem is that the message is sent as one buffer but received into multiple buffers. If we use lz4_compress
(block compression) on send then on receive the multiple buffers have to be copied into one buffer to pass to lz4_decompress
which is undesirable. lz4 frame compression can compress the whole message and decompress one chunk at a time so the multiple buffers can be passed in as zero copy input stream. The only issue with lz4 frame decompression is that it may not consume all input bytes from a chunk. In this case the remaining bytes have to be copied and concatenated with the next input chunk. I have not seen this happen though in my test on 32 million of protocol messages captured from the mainnet. But this case still has to be coded. On another hand the captured sample data also shows that 1) 69% of the messages are received into one buffer; 2) 29% of the messages are received into two buffers; 3) 98% out of the 29% have the message size less than 500 bytes. So the copying might not be a substantial problem after all. Perhaps more data should be captured before we can decide on what compression mode is best to use - block or frame.
return false; | ||
switch(type) | ||
{ | ||
case protocol::mtMANIFESTS: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand the intention here (to avoid wasting CPU resources on messages that are unlikely to benefit from compression) but I am concerned that this approach is fragile: anytime we add a message type that can benefit from compression we need to remember to add support support for it here.
Also, consider a message like TMTransaction
which can (depending on the transaction) be rather large and might benefit from compression.
I don't know that I have a better solution; just calling this out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Majority of the messages are not compressible (88% in the sample). The filtering is desirable because both CPU and memory (have to allocate compressed message buffer) will be used. Perhaps we could mitigate maintainability concern by adding compiler warning when a case is missing in the switch?
src/ripple/overlay/impl/Message.cpp
Outdated
return (mBufferCompressed.data() + headerBytes); | ||
}); | ||
|
||
double ratio = 1.0 - (double)compressedSize / (double)messageBytes; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am allergic to floating-point stuff so just seeing the keyword makes my spidey sense tingle. I get what you're trying to do here: avoid expansion (and potentially enforce a minimum before forcing the other end to do work) however I think that we are better off not doing any of this.
Instead, I'd recommend using LZ4_compress_default
which does some of this for you: if the result of the compressed data would have been larger than the uncompressed data, it returns 0
(i.e. "failed") and not worrying about a minimum compression ratio.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LZ4_compress_default
may return non-zero and still compress into larger buffer. I changed to checking if the compressed size is less than uncompressed.
@@ -275,6 +275,8 @@ ConnectAttempt::makeRequest (bool crawl) -> request_type | |||
m.insert ("Connection", "Upgrade"); | |||
m.insert ("Connect-As", "Peer"); | |||
m.insert ("Crawl", crawl ? "public" : "private"); | |||
if (compressionEnabled) | |||
m.insert("Accept-Encoding", "lz4"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get why you're using Accept-Encoding
but since our handling of this header isn't quite standards-compliant, I'm not sure whether using a different header name is better. For example, X-Offer-Compression
(both in the request and the response)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
|
||
namespace compression_algorithms { | ||
|
||
inline void doThrow(const char *message) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function is invoked from multiple places and I don't know that they're all from within a try/catch
block, or that the catch
is appropriately scoped. We need to be careful when throwing to ensure that not only will the exception be caught but that continuing after the handler doesn't cause weirdness.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added try/catch in Compression.h module.
{ | ||
bool res = in.Next(&compressedChunk, &chunkSize); | ||
|
||
if (!res && buffer.size() == 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that !buffer.empty()
reads better both here and on line 167.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated
src/ripple/overlay/Message.h
Outdated
if (compressed == Compressed::Off) | ||
return mBuffer; | ||
|
||
if (!mCompressedRequested) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor nit, but given the usage, this is probably better named: alreadyCompressed_
.
Also I'm concerned about thread-safety here; I'm not sure it's a problem, but we need to triple-check that it's not. I'd almost feel better if we just did the compression during construction of the message, so that it never became an issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to alreadyRequested_
. The message may or may not be compressed.
@@ -0,0 +1,374 @@ | |||
//------------------------------------------------------------------------------ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 for the unit tests here.
src/ripple/app/misc/ValidatorList.h
Outdated
@@ -239,6 +239,8 @@ class ValidatorList | |||
@param hashRouter HashRouter object which will determine which | |||
peers not to send to | |||
|
|||
@param compressionEnabled is true if compression is enabled in rippled.cfg | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this documentation got lost.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's no longer needed. Will delete.
@@ -86,6 +86,7 @@ PeerImp::PeerImp (Application& app, id_t id, | |||
, slot_ (slot) | |||
, request_(std::move(request)) | |||
, headers_(request_) | |||
, compressionEnabled_(headers_["X-Offer-Compression"] == "lz4" ? Compressed::On : Compressed::Off) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need && app_.config().COMPRESSION
as was done in the other PeerImp
constructor?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is constructor for a peer making outbound connection. The header is not going to have the X-Offer-Compression
header if the compression is not enabled so no need to check for COMPRESSION
. The other constructor is for the inbound peer. We are not going to send compressed message to that peer if the compression is not enabled so we check for COMPRESSION
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Nice job on this!
Thanks for good comments!
…On Wed, Mar 25, 2020 at 11:04 AM Scott Determan ***@***.***> wrote:
***@***.**** approved this pull request.
👍 Nice job on this!
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#3287 (review)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAZPWKVEKTJM5OSFINCUW73RJIMN7ANCNFSM4LC7U6BQ>
.
|
* Peers negotiate compression via HTTP Header "X-Offer-Compression: lz4" * Messages greater than 70 bytes and protocol type messages MANIFESTS, ENDPOINTS, TRANSACTION, GET_LEDGER, LEDGER_DATA, GET_OBJECT, and VALIDATORLIST are compressed * If the compressed message is larger than the uncompressed message then the uncompressed message is sent * Compression flag and the compression algorithm type are included in the message header * Only LZ4 block compression is currently supported
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
* Peers negotiate compression via HTTP Header "X-Offer-Compression: lz4" * Messages greater than 70 bytes and protocol type messages MANIFESTS, ENDPOINTS, TRANSACTION, GET_LEDGER, LEDGER_DATA, GET_OBJECT, and VALIDATORLIST are compressed * If the compressed message is larger than the uncompressed message then the uncompressed message is sent * Compression flag and the compression algorithm type are included in the message header * Only LZ4 block compression is currently supported
Released in 1.6.0 |
P2P link compression is a feature added in 1.6.0 by #3287. https://xrpl.org/enable-link-compression.html If the default changes in the future - for example, as currently proposed by #4387 - the comment will be updated at that time. Fix #4656
P2P link compression is a feature added in 1.6.0 by XRPLF#3287. https://xrpl.org/enable-link-compression.html If the default changes in the future - for example, as currently proposed by XRPLF#4387 - the comment will be updated at that time. Fix XRPLF#4656
Peers negotiate compression via HTTP Headers
X-Offer-Compression:lz4
. If the client and the server agree on the compression/algorithm (only lz4 is currently supported) then the protocol messages between the peers are compressed when possible. Messages greater than 70 bytes and MANIFESTS, ENDPOINTS, TRANSACTION, GET_LEDGER, LEDGER_DATA, GET_OBJECT, and VALIDATORLIST messages are compressed. If the compressed message is larger than the uncompressed message then the uncompressed message is sent. Compression flag and the compression algorithm type are included in the payload header.