-
Notifications
You must be signed in to change notification settings - Fork 38.3k
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
Optimize initial size in ContentCachingRequestWrapper when contentCacheLimit set #29775
Optimize initial size in ContentCachingRequestWrapper when contentCacheLimit set #29775
Conversation
To avoid over-allocating the initial buffer for content caching: 1. If the content size is known and is smaller than the content limit, use that 2. If the content size is unknown use the default initial buffer size (or content limit if smaller) and allow it to group as needed This allows for scenarios where limiting behavior is desired to avoid overly large caching but at the same time the majority of requests are not near that limit. For example, allowing a maximum of 1MB content caching but the majority of requests are in the single digit KB size. Rather than allocate the worst case 1MB byte arrays per request these can be scaled in to be more appropriately sized.
Ran into this because we're using this similar to Only downside really of this is that on the unknown size requests, the init buffer will now just be the smaller of 1024 or the content size limit. In theory there's someone that had the limit set to something larger and knew most of their requests would end up that size and now there's some level of byte buffer resizing that takes place. Maybe use Spring's |
* would cause an OOM Error likely otherwise. | ||
*/ | ||
@Test | ||
void cachedContentWithLimitKnownLengthAvoidOverAllocation() throws Exception { |
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 a bit awkward to test without exposing some internals in a package private method or something (maybe reflection to grab the byte array size). Basically, using a really large content limit before this change could cause an OOM Error if you ran this test with < 2GB heap or so. After this change it wouldn't use the 2GB limit, it would just allow growing to it (but the tests don't exercise growing to it as that's not relevant here).
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 suppose the higher one needs to make contentCacheLimit
for outliers, the less ideal it is as for use as an initial size for the average request body size.
The change seems reasonable to me, treating contentCacheLimit
mostly as an upper boundary, but otherwise using the same default for an initial size. Or we could add one more constructor that takes defaultInitialSize
in addition.
I would like more opinions from other team members.
I think this change is a good tradeoff, avoiding over-allocation in many cases. If this change causes too much resizing, we can always consider using As for the tests, I'm not convinced we should use the OOM approach to validate the changes. I'd rather use |
Will play with this when I get a chance, thanks. |
Thanks @ryanrupp for this PR. I've polished it a bit and used the This will be released with the first 6.1 release candidate. |
@lonre Is this causing significant problems in your applications? This is a light performance improvement and we don't usually backport those in maintenance branches as only bug fixes apply there. |
@bclozel Yes, according to the jfr ContentCachingRequestWrapper seems allocating much memory. |
Thanks for your response. Allocations by the |
Could we reconsider aspects of this change? I think switching to Example scenario: We have a 10KB request payload, and the content size is known. If we pass this size to the In practice we do have some services that have such large request / response payloads, so I think this may be a regression in practice from simply using a ByteArrayOutputStream with a precisely sized buffer. |
@kilink what do you think about this change? |
@bclozel That looks good to me. Thanks! |
This commit builds on top of changes made in gh-29775 and gh-31737. Before this change, we would allocate several byte arrays even in cases of known request size. This could decrease performance when getting the cached content as it requires merging several arrays and data is not colocated in memory. This change ensures that we create a `FastByteArrayOutputStream` instance with the known request size so that the first allocated segment can contain the entire content. If the request size is not know, we will default back on the default allocation size for the `FastByteArrayOutputStream`. Closes gh-31834
To avoid over-allocating the initial buffer for content caching:
This allows for scenarios where limiting behavior is desired to avoid overly large caching but at the same time the majority of requests are not near that limit. For example, allowing a maximum of 1MB content caching but the majority of requests are in the single digit KB size. Rather than allocate the worst case 1MB byte arrays per request these can be scaled in to be more appropriately sized.