Skip to content

A75: refactor the aggregate cluster LB policy tree - #9423

Open
mswierq wants to merge 10 commits into
grpc:masterfrom
mswierq:a75/aggregate-lb-policy-tree-refactoring
Open

mswierq wants to merge 10 commits into
grpc:masterfrom
mswierq:a75/aggregate-lb-policy-tree-refactoring

Conversation

@mswierq

@mswierq mswierq commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Implements A75 for the aggregate cluster.

Follow-up to #9234. This change implements the new LB policy topology (priority -> outlier_detection -> cluster_impl -> priority -> xds_wrr_locality -> endpoint picking policy) for the aggregate cluster case by delegating leaf cluster configuration to buildLeafClusterConfig, and removes the legacy aggregate cluster configuration helpers.

RELEASE NOTES:

  • xds: Implement the aggregate cluster changes from gRFC A75. Each underlying
    cluster of an aggregate cluster now uses the LB policy configured in that
    cluster, and the LB policy configured in the aggregate cluster is ignored.
    Outlier detection and other per-cluster settings now apply across all
    priorities of the underlying cluster. The previous LB policy behavior can be
    temporarily restored by setting the GRPC_XDS_AGGREGATE_CLUSTER_BACKWARD_COMPAT
    environment variable to true; this mechanism will be removed in a future
    release.

@codecov

codecov Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.24590% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.70%. Comparing base (4599e77) to head (f668a43).

Files with missing lines Patch % Lines
internal/xds/balancer/cdsbalancer/configbuilder.go 81.25% 6 Missing ⚠️
internal/xds/balancer/cdsbalancer/cdsbalancer.go 89.65% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #9423      +/-   ##
==========================================
- Coverage   87.79%   87.70%   -0.10%     
==========================================
  Files         437      437              
  Lines       31442    31387      -55     
==========================================
- Hits        27604    27527      -77     
- Misses       3838     3860      +22     
Files with missing lines Coverage Δ
internal/xds/balancer/cdsbalancer/cdsbalancer.go 81.90% <89.65%> (+0.71%) ⬆️
internal/xds/balancer/cdsbalancer/configbuilder.go 92.75% <81.25%> (-2.93%) ⬇️

... and 24 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@easwars easwars added the Type: Feature New features or improvements in behavior label Sep 18, 2026
@easwars easwars added this to the 1.85 Release milestone Sep 18, 2026
@easwars

easwars commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

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.

Code Review

This pull request refactors the CDS balancer configuration to align with gRFC A75, specifically updating the aggregate cluster configuration to wrap leaf clusters in outlier_detection and cluster_impl policies. It also cleans up test utilities and simplifies configuration building logic. A potential nil pointer dereference issue was identified in the EDS update logic, for which a defensive check has been suggested.

Comment on lines 118 to 129
priorities := [][]xdsresource.Locality{{}}
edsUpdate := leaf.clusterConfig.EndpointConfig.EDSUpdate
// Localities of length 0 is triggered by an NACK or resource-not-found
// error before update, or an empty localities list in an update. In either
// case want to create a priority, and send down empty address list, causing
// TF for that priority. "If any discovery mechanism instance experiences an
// error retrieving data, and it has not previously reported any results, it
// should report a result that is a single priority with no endpoints." -
// A37
if len(edsUpdate.Localities) != 0 {
priorities = groupLocalitiesByPriority(edsUpdate.Localities)
}

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.

high

To prevent a potential nil pointer dereference, we should add a defensive check to ensure leaf.clusterConfig.EndpointConfig is not nil before accessing EDSUpdate. In xDS, the endpoint configuration might not be populated yet if the endpoints are not resolved or if there was an error resolving them.

Suggested change
priorities := [][]xdsresource.Locality{{}}
edsUpdate := leaf.clusterConfig.EndpointConfig.EDSUpdate
// Localities of length 0 is triggered by an NACK or resource-not-found
// error before update, or an empty localities list in an update. In either
// case want to create a priority, and send down empty address list, causing
// TF for that priority. "If any discovery mechanism instance experiences an
// error retrieving data, and it has not previously reported any results, it
// should report a result that is a single priority with no endpoints." -
// A37
if len(edsUpdate.Localities) != 0 {
priorities = groupLocalitiesByPriority(edsUpdate.Localities)
}
priorities := [][]xdsresource.Locality{{}}
var edsUpdate *xdsresource.EndpointsUpdate
if leaf.clusterConfig.EndpointConfig != nil {
edsUpdate = leaf.clusterConfig.EndpointConfig.EDSUpdate
}
// Localities of length 0 is triggered by an NACK or resource-not-found
// error before update, or an empty localities list in an update. In either
// case want to create a priority, and send down empty address list, causing
// TF for that priority. "If any discovery mechanism instance experiences an
// error retrieving data, and it has not previously reported any results, it
// should report a result that is a single priority with no endpoints." -
// A37
if edsUpdate != nil && len(edsUpdate.Localities) != 0 {
priorities = groupLocalitiesByPriority(edsUpdate.Localities)
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I did some analysis and a nil EndpointConfig may actually happen. When one of the leaf clusters has not resolved its endpoints yet, but another one raises an error. However, instead of adding a simple nil check here, I fixed that a nil is not passed.

This is the commit that fixes it: 2e99573

@easwars @Pranjali-2501 please, let me know if it makes sense to deliver it with this PR or should I issue a new one?

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.

Thanks for digging into this. It will be better to fix this bug and a test for this in a separate PR. It's a pre-existing bug and independent of the tree refactoring done as part of gRFC A75.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I prepared a new PR for this: #9518

continue
default:
return nil, nil, fmt.Errorf("unsupported cluster type %v for leaf cluster %q", clusterUpdate.ClusterType, clusterUpdate.ClusterName)
odCfg, endpoints, err := buildLeafClusterConfig(leaf, xdsLBPolicy)

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.

https://screenshot-v2.corp.google.com/07p4t433u6dag
As per gRFC A75, for aggregate clusters we should use the LB policy configured in each underlying (leaf) cluster and ignore the one in the aggregate cluster.
A75 also asks for a temporary GRPC_XDS_AGGREGATE_CLUSTER_BACKWARD_COMPAT env var. When it's set to true, the leaves use the aggregate cluster's LB policy, i.e. the current behavior.

But we're still unmarshaling the LB policy of the root cluster and passing the same policy to every leaf here:

// The LB policy is configured by the root cluster.
if err := json.Unmarshal(clusterConfig.Cluster.LBPolicy, &b.xdsLBPolicy); err != nil {
return b.annotateErrorWithNodeID(fmt.Errorf("error unmarshalling xDS LB Policy: %v", err))
}

So if the aggregate cluster uses the default round_robin and a leaf is configured with ring_hash, the leaf ends up using round_robin. None of the existing tests catch this because they all use the default policy for every cluster.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

thank you for spotting that, I added the missing switch and using a leaf LB policy

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I have also updated the RELEASE NOTES of this PR

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.

I think we have decided to remove the outlierDetection config parsing from cdsbalancer.go and build it inside buildLeafClusterConfig instead of updating it for all priorities.

See this: #9234 (comment)

We have also decided to remove the outlierDetection field from leafClusterConfig.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this was fixed with a CDS on top of the leaf policy tree

// ┌──────▼─────┐ ┌─────▼──────┐
// │xDSLBPolicy │ │xDSLBPolicy │ (Locality and Endpoint picking layer)
// └────────────┘ └────────────┘
// ┌────────┐

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.

https://github.com/grpc/proposal/blob/master/A75_graphics/grpc_client_architecture_aggregate.png

According to the gRFC, for aggregate clusters, priority lb for each cluster should have a CDS child and than follows the same leaf cluster config.

Currently this PR creates outlier_detection as child of priority LB for each cluster.

Is this the decision taken, if so why?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The first implementation was doing an eager config building, which was rebuilding configs for all the leaves on each update, so CDS was not necessary.

I have reconsidered and decided to stick with A75 and C-Core implementation and I added CDS on the top of the leaf config tree.

@Pranjali-2501

Copy link
Copy Markdown
Contributor

Please resolve the merge conflicts in this PR.

@mswierq

mswierq commented Oct 15, 2026

Copy link
Copy Markdown
Contributor Author

Please resolve the merge conflicts in this PR.

resolved

@mswierq
mswierq requested a review from Pranjali-2501 October 15, 2026 12:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Type: Feature New features or improvements in behavior

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants