-
Notifications
You must be signed in to change notification settings - Fork 262
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
tower-balance: Observations from testing #286
Comments
This plan sounds good to me 👍 |
I'm a bit curious about the tradeoffs between driving all the inner services in the background and having the balancer select two services at |
@hawkw hm? is there something i can explain better? or are you offering to write benchmarks? ;) |
@olix0r nothing in particular, I was just wondering about your thought process. I am interested in doing some benchmarking though, although I'm not sure if it would be practical... |
@hawkw imagine a load balancer with 1000 endpoints. Initially, and in generally worst-case (big-o) situations, the balancer will have 1000 unready services. Every time you try to call poll_ready on the balancer, it will have to issue 1000 inner poll_readys. Every time we get a service discovery update, we'll have to poll all of these services. Until all of them ready! That's a lot of polling. Instead, we can rely on the executor to be much more intelligent about how these inner services are polled, and we can make the balancer's poll_ready much cheaper/simpler in the process: |
@olix0r In the approach I described, you only poll two inner services on any given |
@hawkw I see. So your question is: "why do we have to drive inner services to readiness at all?" If so, that's a good question: a given TLS connection may need to be polled 4 or 5 times to drive itself through all of the state transitions needed complete a connection. If you just let the balancer poll services at random without driving them to readiness, you may never complete any of these connections (as they would idle out, etc). |
Ah, yeah, that makes sense now that you mention it. Thanks. |
The tower-balance crate includes the `Load` and `Instrument` traits, which are likely useful outside of balancers; and certainly have no tight coupling with any specific balancer implementation. This change extracts these protocol-agnostic traits into a dedicated crate. The `Load` trait includes a latency-aware _PeakEWMA_ load strategy as well as a simple _PendingRequests_ strategy for latency-agnostic applications. The `Instrument` trait is used by both of these strategies to track in-flight requests without knowing protocol details. It is expected that protocol-specific crates will provide, for instance, HTTP time-to-first-byte latency strategies. A default `NoInstrument` implementation tracks the a request until its response future is satisfied. This crate should only be published once tower-balance is published. Part of #286
As described in tower-rs#286, `Balance` had a few problems: - it is responsible for driving all inner services to readiness, making its `poll_ready` O(n) and not O(1); - the `choose` abstraction was a hinderance. If a round-robin balancer is needed it can be implemented separately without much duplicate code; and - endpoint errors were considered fatal to the balancer. This changes replaces `Balance` with `P2CBalance` and removes the `choose` module. Endpoint service failures now cause the service to be removed from the balancer gracefully. Endpoint selection is now effectively constant time, though it biases for availability in the case when random selection does not yield an available endpoint. `tower-test` had to be updated so that a mocked service could fail after advertising readiness.
As described in #286, `Balance` had a few problems: - it is responsible for driving all inner services to readiness, making its `poll_ready` O(n) and not O(1); - the `choose` abstraction was a hinderance. If a round-robin balancer is needed it can be implemented separately without much duplicate code; and - endpoint errors were considered fatal to the balancer. This changes replaces `Balance` with `p2c::Balance` and removes the `choose` module. Endpoint service failures now cause the service to be removed from the balancer gracefully. Endpoint selection is now effectively constant time, though it biases for availability in the case when random selection does not yield an available endpoint. `tower-test` had to be updated so that a mocked service could fail after advertising readiness.
This issue is also a good roadmap for what would be needed for a 0.1 I'd be happy to help with |
@jonhoo thanks for pinging this. To my mind, there are a few issues I want to address before releasing this crate:
|
I think it's useful to clear up what |
@jonhoo I've started a branch that splits out the core service caching logic out of the balancer. I expect that this could satisfy your pool use cases (which I have some more ideas about thanks to the helpful context you provided), and also the weighted distribution use case we have (which isn't strictly about load balancing, but should take readiness into account). I'm going to pursue this a little further to see if I can work through my pool ideas on this branch so that I can explain them a bit better. |
@olix0r now that |
Re |
We've recently been running tests on the Linkerd proxy that exercise the load
balancer in larger clusters (of 30+ endpoints). At the same time, I've been
exploring the existing endpoint-weighting scheme.
In doing so, I've realized that the balancer is currently O(n), though it is
intended to be effectively O(1). Furthermore, the existing weighting scheme
is complex to instrument in practice, and is of questionable value in its
current form.
All of this leads to me believe that we should drastically simplify the
balancer:
implementation. Each strategy benefits from being able to use its own data
structure. For now, I propose that we simply drop the Round-Robin logic. It
can easily be added later if it's desirable.
constituent services. The P2C balancer is intended to sample two
endpoints. In the current implementation, we always poll all unready
inner services, which leads to poor behavior as the balancer scales.
Something like Spawn ready: drives a service's readiness on an executor #283 is necessary to relax the balancer's readiness-polling
guarantees.
to the balancer. It now seems more appropriate to let the balancer handle these
failures by dropping the failed service from the balancer.
Load
andInstrument
traits are not balancer-specific and shouldbecome more generally useful abstractions in a dedicated crate.
produce
Discover
-typed results.Pool
implementation is factored inconveniently, especially in light of howtower-layer has evolved: it requires direct access to a balancer implementation,
accessing its
discover
field directly. I think that it should probablybe implemented as a
Discover
proxy-type that is constructed with aWatch<Load>
. The pool doesn't rely on any specific balancer behavior, so itshouldn't dictate use of a specific balancer implementation.
I have a series of changes that I would like to pursue to this end:
Weight
implementation (which is not really what we want).choose
trait/module, leaving only aP2CBalance
implementation.The text was updated successfully, but these errors were encountered: