Skip to content
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

Client respect Node-Selection-Strategy response header #688

Merged
merged 28 commits into from
May 1, 2020

Conversation

ferozco
Copy link
Contributor

@ferozco ferozco commented Apr 28, 2020

Before this PR

Clients would declare (or default to) a static node selection strategy. Clients would have to guess which node selection strategy was the right one to use for a given service, resulting in a leaky abstraction since they would have to know implementation details of the service they were calling to choose the right one.

After this PR

==COMMIT_MSG==
Clients respect Node-Selection-Strategy response header
==COMMIT_MSG==

Servers are now able to instruct clients on which strategy to use by adding Node-Selection-Strategy response header. The header is structured similarly to Content-Type as a ; delimited list of strategies, ordered by preference. The current supported strategies are:

  • PIN_UNTIL_ERROR
  • PIN_UNTIL_ERROR_WITHOUT_RESHUFFLE
  • BALANCED

The expectation is that for a given server a single node selection strategy will be defined.

The hope is that by consolidating node selection strategy selection into the server we will see improved perf across the fleet and clients will have to think less about the services they communicate with.

Currently the NodeSelectionStrategySelector decides which node selection strategy to use, basically the first known strategy from the most recent response.

Possible downsides?

This raises some questions about how we handle client creation internally, since we currently create separate DialogueChannel's (and underlying Apache client) if two logic services have different static client configuration (including node selection strategy) but they will converge to the same strategy if the server responds with Node-Selection-Strategy header.

There is also currently no way for the client to override the server suggested strategy, some may view that as a feature rather than a bug however 🤷‍♂️

@ferozco ferozco marked this pull request as ready for review April 29, 2020 16:23
UNKNOWN;

static List<DialogueNodeSelectionStrategy> fromHeader(String header) {
return Splitter.on(",").splitToList(header).stream()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can extract out the splitter to a static field, and include trimming whitespace: Splitter.on(',').trimResults().omitEmptyStrings()


private static DialogueNodeSelectionStrategy safeValueOf(String value) {
try {
return valueOf(value.trim().toUpperCase());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to support mixed case strategies?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Http spec only required header names be case insensitive so its up to each implementer to decide how it interprets the values. For simplicity I figured it was fine to be completely case insensitive.

import java.util.stream.Collectors;

@SuppressWarnings("NullAway")
public final class DefaultNodeSelectionStrategySelector implements NodeSelectionStrategySelector {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be helpful to mark a meter each time a strategy change occurs, possibly including the strategy we're moving to.

ticker,
channelName));

nodeSelectionStrategy.updateChannels(ImmutableList.copyOf(limitedChannelByUri.values()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could keep things simpler by putting our complete trust in the most recent response we've received, that way the nodeSelectionStrategy wouldn't need to wrap individual uri channels or be notified of uri updates.

No real proposal here, just weighing implementing this in a more naive way to begin with.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ya I had thought of that originally, but it would lead to pretty bad behaviour during rolling upgrades with RR/Balanced. I think the extra bit of complexity is worth handling the case

channels, random, tick, config.taggedMetricRegistry(), channelName);
}
throw new SafeRuntimeException(
"Unknown NodeSelectionStrategy", SafeArg.of("unknown", config.nodeSelectionStrategy()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DialogueChannel is much easier to follow without the node selection logic 👍

…ectionStrategySelector.java

Co-Authored-By: Carter Kozak <ckozak@ckozak.net>
@changelog-app
Copy link

changelog-app bot commented Apr 29, 2020

Generate changelog in changelog/@unreleased

Type

  • Feature
  • Improvement
  • Fix
  • Break
  • Deprecation
  • Manual task
  • Migration

Description

Clients respect Node-Selection-Strategy response header

Check the box to generate changelog(s)

  • Generate changelog entry

Copy link
Contributor

@carterkozak carterkozak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like it, thanks Felipe!
@iamdanfox Have you had a chance to read over this? This is a substantial enough change that I'd like to hold off merging until you've had a chance to review as well.


private static DialogueNodeSelectionStrategy safeValueOf(String value) {
String normalizedValue = value.toUpperCase();
if (PIN_UNTIL_ERROR.name().equals(normalizedValue)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

meganit: We can avoid toUpperCase in favor of equalsIgnoreCase

PIN_UNTIL_ERROR,
PIN_UNTIL_ERROR_WITHOUT_RESHUFFLE,
BALANCED,
UNKNOWN;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can probably ditch the UNKNOWN variant here, as this library is the source of truth for what strategies exist?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But you could have an old client communicating with a new server and hence receive an unknown strategy

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also can we put a little comment saying why this is copy&pasted version of the regular remoting-api NodeSelectionStrategy? (iirc this was so that we could try out implementation details without having to promote them to a full user-selectable option?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

old client communicating with a new server and hence receive an unknown strategy

in this case though we just ignore it right? i'm not sure what the unknown variant really buys us.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It becomes important if you decide to have more complicated logic (what was previously implemented) for determining the node selection strategy to use. If you really don't like it, i'm happy to remove it


LimitedChannel channel();

ImmutableList<LimitedChannel> hostChannels();
Copy link
Contributor

@iamdanfox iamdanfox Apr 30, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are hostChannels saved here rather than just using an instance field? ah nvm it's live reloading again isn't it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ya we need to keep track of the host channels so that we can recreate the underlying channel in response to a server's node-selection-strategy response

private static void setResponse(LimitedChannel mockChannel, Optional<String> header) {
Mockito.clearInvocations(mockChannel);
Mockito.reset(mockChannel);
Response resp = mock(Response.class);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would prefer to use TestResponse.withheader rather than mocking, but not a big deal

void handles_one_to_many_uri_update() {
channel.updateChannels(ImmutableList.of(channel1));
channel.updateChannels(ImmutableList.of(channel1, channel2));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somewhat concerned that these tests could pretty easily vacuously pass, but maybe we have enough unit tests elsewhere that prove we can make requests correctly after a reload?

Copy link
Contributor

@iamdanfox iamdanfox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok let's give it a shot! V. excited to be able to get our Balanced implementation out to more of the fleet. I expect it's gonna be a bit weird when blue-greening between nodes that suggest different Node-Selection-Strategies, but this should be pretty transient and I guess we can watch the metrics to see when it happens :)

@bulldozer-bot bulldozer-bot bot merged commit 6651f29 into develop May 1, 2020
@bulldozer-bot bulldozer-bot bot deleted the fo/server-side-node-selection branch May 1, 2020 13:58
@svc-autorelease
Copy link
Collaborator

Released 1.32.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants