-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
Avoid extra predicate()
calls
#3869
Avoid extra predicate()
calls
#3869
Conversation
In `SubsetLoadBalancer::HostSubsetImpl::update()` we have repeated calls to `predicate()`. These calls are expensive mostly because of `Envoy::Config::Metadata::metadataValue()` calls. Furthermore, metadata is guarded by a lock since envoyproxy#3814 — so there is some contention too. This change improves things by doing two things: * avoid use of `predicate()` to derive `healthy_hosts_per_locality` * cache `predicate()` calls for the special case of only metadata changing (current hosts == hosts added) Signed-off-by: Raul Gutierrez Segales <rgs@pinterest.com>
A bit more context here, in
Things get pretty expensive because on every iteration we have to call So ideally, we can:
This diff starts in that way, but there's more I think. Thoughts? |
Signed-off-by: Raul Gutierrez Segales <rgs@pinterest.com>
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 good. One comment about a potential simplification. I'll leave it up to you if you want to do that or not.
@@ -477,20 +480,26 @@ void SubsetLoadBalancer::HostSubsetImpl::update(const HostVector& hosts_added, | |||
HostVectorSharedPtr hosts(new HostVector()); | |||
HostVectorSharedPtr healthy_hosts(new HostVector()); | |||
|
|||
// It's possible that hosts_added == original_host_set_.hosts(), e.g.: when | |||
// calling refreshSubsets() if only metadata change. If so, we can avoid the | |||
// predicate() call. |
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.
Alternatively, I think you could change refreshSubsets to not pass the host set as hosts added since the underlying LBs don't need use information anyway.
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 would like to do that — it would improve things a lot — but we'd fail to enter this loop in processSubsets
:
... which means we wouldn't create the new subsets, if any.
So we'd have to rework processSubsets()
or also call findOrCreateSubset()
from somewhere else...
I'll look into it, but we can probably merge this in parallel since it's still better.
source/common/upstream/subset_lb.cc
Outdated
hosts->emplace_back(host); | ||
if (host->healthy()) { | ||
healthy_hosts->emplace_back(host); | ||
} | ||
} | ||
} | ||
|
||
// Calling predicate() is expensive since it involves metadata lookups; so we | ||
// can take a shortcut and just filter healthy hosts in a follow-up call. |
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.
Sorry just for my understanding, what follow-up call? How come we don't have to do this predicate call here? Maybe make the comment a little more detailed?
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.
yes, yes — the follow-up call is the 2nd call to filter(). I'll comment in more detail, and there's actually one more (significant) perf improvement we can have....
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.
Ah, I see it now. Sorry did not read it closely. I think maybe you meant "in the follow-up call" ?
Signed-off-by: Raul Gutierrez Segales <rgs@pinterest.com>
This is one more optimization to avoid filter() calls using hostMatches() as a predicate when possible. Signed-off-by: Raul Gutierrez Segales <rgs@pinterest.com>
@zuercher @mattklein123 lets actually not merge this yet. I ran the patch through perf today and although it's nice to see the On a related note, I got into this patch while debugging a case in which a given stream of EDS updates will cause the subset lb to push envoy into 100% CPU usage. I'll try to get a repro tomorrow. |
Config::Metadata::metadataValue() is pretty general, which means we end up calling host_metadata.filter_metadata().find() from within the main loop of hostMatches(). This should get rid of some of hostMatches()'s overhead. Tests: running this through perf now to measure. Signed-off-by: Raul Gutierrez Segales <rgs@pinterest.com>
One last thought: I think most improvements are good to go except for maybe using the |
Leftovers from inline metadataValue(). Signed-off-by: Raul Gutierrez Segales <rgs@pinterest.com>
@mattklein123 @zuercher on a 2nd thought, if you like it as is we can actually merge it and I can run out of master for a few days and see how it goes. Worst case if it isn't great or that much better, we revert that parts that we don't like. |
FWIW, the CPU increase that I saw yesterday was not from this change. I am bisecting now to see who's driving that increase, so I think we are clear here. Perf issue follow-up #3878. |
In
SubsetLoadBalancer::HostSubsetImpl::update()
we have repeated callsto
predicate()
. These calls are expensive mostly because ofEnvoy::Config::Metadata::metadataValue()
calls. Furthermore, metadatais guarded by a lock since #3814 — so there is some contention too.
This change improves things by doing two things:
predicate()
to derivehealthy_hosts_per_locality
predicate()
calls for the special case of only metadatachanging (current hosts == hosts added)
Signed-off-by: Raul Gutierrez Segales rgs@pinterest.com