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

Implement zone aware load balancing for XdsEndpointGroup #5808

Merged
merged 8 commits into from
Aug 8, 2024

Conversation

jrhee17
Copy link
Contributor

@jrhee17 jrhee17 commented Jul 13, 2024

Motivation:

This changeset attempts to implement envoy's zone aware routing.
A small sample on how this can be configured can be seen here as well.

The basic algorithm in layman's terms is the following:

  1. A client supplies a locality and a local cluster. The corresponding cluster should also be added.
  2. The percentage of hosts for each locality is computed for both upstream and local clusters.
  3. The percentage is compared and a new "weight" is computed to determine which locality to route to when selecting an endpoint.

The code has been mostly adapted from envoy's implementation:

Modifications:

  • (Breaking) Previously we had been accepting a ListenerRoot when creating an XdsEndpointGroup. However, we need more information like local locality from the bootstrap. To make this intention clear, XdsEndpointGroup now accepts an XdsBootstrap. Additionally, XdsBootstrap#bootstrap has been added to access the local cluster name and locality.
  • ClusterManager now maintains a LocalCluster data structure which watches changes in the local cluster if exists. When a ClusterEntry is created, the existing LocalCluster is passed.
  • The upstream ClusterEntry needs to listen to the local ClusterEntry for updates in order to pre-compute the state. ClusterEntry is now an AbstractListenable and notifies the current LoadBalancer.
    • Following this change, the PrioritySet is required to compute the locality state. Added a LoadBalancer#prioritySet method to retrieve the current PrioritySet.
    • XdsLoadBalancer has been added as an extra abstraction layer to avoid conflicts with Add LoadBalancer for generalizing EndpointSelector #5779
    • ClusterEntry now has two update points: 1) when the endpoints are updated 2) when the local cluster is updated. Now, the two entry points call a single tryRefresh to refresh the load balancer.
  • Added LocalityRoutingStateFactory and modified DefaultLoadBalancer to support zone aware routing. The implementation should look very similar to envoy's source.
  • Testing was difficult due to multiple locations where random was used. To make the tests deterministic, I've added a XdsRandom interface for easier testing. Additionally, to guarantee that the local cluster was fully loaded before proceeding with tests, ClusterEntry#initialLocalEntryStateFuture has been added.
    • In order to access XdsEndpointGroup#clusterEntries, the factory methods for XdsEndpointGroup now return the concrete type.

Result:

  • XdsEndpointGroup now supports zone-aware load balancing.

@jrhee17 jrhee17 marked this pull request as ready for review July 16, 2024 03:54
Copy link
Contributor

@ikhoon ikhoon left a comment

Choose a reason for hiding this comment

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

Nice work! 👍

In fact, I approve this PR without reviewing it in detail so that you can continue working on your tasks. I will separately check and compare the Envoy implementation and ours.

Copy link
Member

@minwoox minwoox left a comment

Choose a reason for hiding this comment

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

Looks very promising. 👍

.collect(Collectors.toList());
final ImmutableList.Builder<ResidualCapacity> residualCapacity = ImmutableList.builder();
// to guarantee that residualCapacity has at least one element
residualCapacity.add(new ResidualCapacity(localLocality, 0));
Copy link
Member

Choose a reason for hiding this comment

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

So this localLocality will never be chosen. Is that correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

localLocality could be chosen in the extreme case if all ResidualCapacity have 0

// This is *extremely* unlikely but possible due to rounding errors when calculating
// locality percentages. In this case just select random locality.
final int idx = random.nextInt(residualCapacities.size(), RandomHint.ALL_RESIDUAL_ZERO);
return residualCapacities.get(idx).locality;

Having said this, the fact that this code is called implies that upstream already has healthy/degraded localLocality hosts so I guess this won't be a big deal.

Copy link
Member

Choose a reason for hiding this comment

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

I missed the code. Thanks for the explanation. 👍

Copy link
Member

@minwoox minwoox left a comment

Choose a reason for hiding this comment

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

Thanks a lot for adding this excellent feature.
You're the MVP. 👍 👍 👍

listenerRoot.close();
}
final ImmutableList.Builder<CompletableFuture<?>> closeFuturesBuilder = ImmutableList.builder();
closeFuturesBuilder.addAll(clusterEntries.clusterEntriesMap
Copy link
Member

Choose a reason for hiding this comment

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

nit:

Suggested change
closeFuturesBuilder.addAll(clusterEntries.clusterEntriesMap
closeFuturesBuilder.addAll(clusterEntriesMap()

@ikhoon ikhoon merged commit 966a1bc into line:main Aug 8, 2024
14 of 15 checks passed
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.

3 participants