Skip to content
This repository was archived by the owner on Jul 22, 2026. It is now read-only.

Fix cluster subsetting silent failure when peer URIs are IPv6 (SI-52492) - #1169

Merged
shivamgupta1 merged 5 commits into
masterfrom
jleezer/si-52492-restli-subsetting-ipv6-fix
Jun 9, 2026
Merged

Fix cluster subsetting silent failure when peer URIs are IPv6 (SI-52492)#1169
shivamgupta1 merged 5 commits into
masterfrom
jleezer/si-52492-restli-subsetting-ipv6-fix

Conversation

@jcleezer

@jcleezer jcleezer commented May 8, 2026

Copy link
Copy Markdown
Contributor

Summary

  • ZKDeterministicSubsettingMetadataProvider compared the host's FQDN identity against URI.getHost()-form peer cluster URIs. When the d2 client materializes peer URIs in IPv6 form (typical with -Djava.net.preferIPv6Addresses=true,
    default-on across most LinkedIn services), the FQDN never matches the [ipv6] form returned by URI.getHost() on JDK 17, so instanceId = -1 and the subsetter fell back to the full host set. The failure was silent because the no-match
    branch logged at DEBUG.
  • Resolve a Set<String> of candidate identities (FQDN, IPv4, bracketed and unbracketed IPv6, scope ids preserved) at construction via an injectable AddressResolver, and match peer URIs with Set.contains instead of indexOf.
  • Bump the no-match log to WARN with a once-per-outage _warnedIdentityNotFound guard that re-arms on a successful match so flapping clusters don't spam the log.
  • Public 3-arg/4-arg constructors are preserved, so consumers (notably container's D2ClientFactory) need no changes.
  • Companion fix to grpc-infra PR #5819. Tracking ticket: SI-52492.

Testing Done

  • Local code review completed
  • Unit tests added: testCandidateIdentities_includesFqdnIpv4AndIpv6, testCandidateIdentities_dnsFailure_fallsBackToFqdn, testCandidateIdentities_keepsIpv6ScopeId (uses Inet6Address.getByAddress for cross-OS portability),
    testGetSubsettingMetadata_matchesViaIpv6, testGetSubsettingMetadata_noMatch_warnsOnceThenReArmsOnRecovery.
  • Pre-existing testGetSubsettingMetadata (FQDN match path) still passes.
  • All 6 tests pass via ./gradlew :d2:test --tests com.linkedin.d2.balancer.subsetting.ZKDeterministicSubsettingMetadataProviderTest (JDK 11, Gradle 6.9.4).
  • Production verification: post-deploy, expect RelativeLoadBalancerStrategy.<svc>-https-LoadBalancerStrategy.TotalHostsCount to drop from full cluster size to ~_minClusterSubsetSize for clusters with _enableClusterSubsetting=true.

Jason Leezer and others added 3 commits May 8, 2026 09:27
ZKDeterministicSubsettingMetadataProvider compared the host's FQDN identity
against URI.getHost()-form peer cluster URIs. When the d2 client materializes
peer URIs in IPv6 form (typical with -Djava.net.preferIPv6Addresses=true,
default-on across most LinkedIn services), the FQDN never matches the
"[ipv6]" form returned by URI.getHost() on JDK 17, so instanceId = -1 and the
subsetter fell back to the full host set. The failure was silent because the
no-match branch logged at DEBUG.

Resolve a Set<String> of candidate identities (FQDN, IPv4, bracketed and
unbracketed IPv6, scope ids preserved) at construction via an injectable
AddressResolver and match peer URIs with Set.contains. Bump the no-match log
to WARN with a once-per-outage guard that re-arms on a successful match so
flapping clusters don't spam the log. Public constructor signatures are
preserved so consumers (notably container's D2ClientFactory) need no changes.

Companion fix to grpc-infra PR #5819.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Inet6Address.getHostAddress() always returns the RFC 4291 fully-expanded
form ("2a04:f547:43:e66a:0:0:0:1a95"), but URI.getHost() returns
whatever was literally in the URI string — could be expanded or
compressed ("[2a04:f547:43:e66a::1a95]"). Java has no public API to
produce the compressed (RFC 5952) form, so if d2 ever materializes a
peer URI in compressed IPv6 the candidate set's expanded form silently
fails to match and the subsetter falls back to all hosts.

Add a private static canonicalizeIpv6IfApplicable that round-trips any
string containing ':' through InetAddress.getByName().getHostAddress().
FQDNs and IPv4 literals short-circuit on the colon check so no DNS work
happens on the hot path. Run both the local-side computeIdentities (so
the candidate set is in canonical form by construction) and the peer-
side getSubsettingMetadata lookup (so peer URI hosts get the same
treatment) through it; this keeps the two sides in lockstep even for
edge cases like IPv4-mapped IPv6 (::ffff:a.b.c.d), which getByName
auto-converts to Inet4Address — brackets are dropped in that case so a
canonicalized peer URI matches a local Inet4Address candidate.

Mirrors the same fix in grpc-infra PR #5819 (per psanglik's review on
that PR and the shared HostIdentities extraction that followed). Companion
to SI-52492.

7/7 ZKDeterministicSubsettingMetadataProviderTest cases pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

synchronized (_lock)
{
if (uriItem.getVersion() != _peerClusterVersion)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The DNS resolution might be expensive but it only runs when there's a change to the cluster's version.

@shivamgupta1
shivamgupta1 merged commit 009fb92 into master Jun 9, 2026
2 checks passed
@shivamgupta1
shivamgupta1 deleted the jleezer/si-52492-restli-subsetting-ipv6-fix branch June 9, 2026 22:23
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants