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

xds: explicitly set request hash key for the ring hash LB policy #11881

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

danielzhaotongliu
Copy link
Collaborator

@danielzhaotongliu danielzhaotongliu commented Feb 4, 2025

Implements gRFC A76: explicitly setting the request hash key for the ring hash LB policy

  • Explictly setting the request hash key is guarded by the GRPC_EXPERIMENTAL_RING_HASH_SET_REQUEST_HASH_KEY environment variable until API stabilized.

Tested:

  • Verified end-to-end by spinning up multiple gRPC servers and a gRPC client that injects a custom service (load balancing) config with ring_hash_experimental and a custom request_hash_header (with NO associated value in the metadata headers) which generates a random hash for each request to the ring hash LB. Verified picks/RPCs are split evenly/uniformly across all backends.
  • Ran affected unit tests with thread sanitizer and 1000 iterations to prevent data races.

CC: @shivaspeaks

@danielzhaotongliu danielzhaotongliu marked this pull request as ready for review February 7, 2025 00:44
@shivaspeaks shivaspeaks added the kokoro:run Add this label to a PR to tell Kokoro the code is safe and tests can be run label Feb 7, 2025
@grpc-kokoro grpc-kokoro removed the kokoro:run Add this label to a PR to tell Kokoro the code is safe and tests can be run label Feb 7, 2025
xds/src/main/java/io/grpc/xds/RingHashLoadBalancer.java Outdated Show resolved Hide resolved
if (headerValue != null) {
requestHash = hashFunc.hashAsciiString(headerValue);
} else {
requestHash = random.nextLong();
Copy link
Member

Choose a reason for hiding this comment

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

This is not thread-safe. See io.grpc.xds.ThreadSafeRandom.

@@ -334,23 +339,30 @@ public static EquivalentAddressGroup stripAttrs(EquivalentAddressGroup eag) {
}

private static final class RingHashPicker extends SubchannelPicker {
private final Random random = new Random();
Copy link
Member

Choose a reason for hiding this comment

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

We probably want to share this object across pickers and even inject it when creating RingHashLB for tests. That way we don't have to deal with flakes.

private final SynchronizationContext syncContext;
private final List<RingEntry> ring;
// Avoid synchronization between pickSubchannel and subchannel's connectivity state change,
// freeze picker's view of subchannel's connectivity state.
// TODO(chengyuanzhang): can be more performance-friendly with
// IdentityHashMap<Subchannel, ConnectivityStateInfo> and RingEntry contains Subchannel.
private final Map<Endpoint, SubchannelView> pickableSubchannels; // read-only
private final String requestHashHeader;
private boolean hasEndpointInConnectingState = false;
Copy link
Member

Choose a reason for hiding this comment

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

Make this final as well.

return PickResult.withError(RPC_HASH_NOT_FOUND);
// Determine request hash.
boolean usingRandomHash = false;
Long requestHash;
Copy link
Member

Choose a reason for hiding this comment

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

Let's not box and think about null in these new cases that don't need it. We should make this long and deal with the null in the only case that needs it. We should also change getTargetIndex() be be passed a long (since it assumes it is non-null already).

private final SynchronizationContext syncContext;
private final List<RingEntry> ring;
// Avoid synchronization between pickSubchannel and subchannel's connectivity state change,
// freeze picker's view of subchannel's connectivity state.
// TODO(chengyuanzhang): can be more performance-friendly with
// IdentityHashMap<Subchannel, ConnectivityStateInfo> and RingEntry contains Subchannel.
private final Map<Endpoint, SubchannelView> pickableSubchannels; // read-only
private final String requestHashHeader;
Copy link
Member

Choose a reason for hiding this comment

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

I think we want to move the construction of Metadata.Key<String> earlier and store the object here. It'd be null when the hash header is empty string. We should also store the key in RingHashLoadBalancer.requestHashHeader. It's up to you whether we store it in RingHashConfig.

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

Successfully merging this pull request may close these issues.

4 participants