grpc/grpc-go · error

least-request: lbConfig.choiceCount: %v, must be >= 2

Error message

least-request: lbConfig.choiceCount: %v, must be >= 2

What it means

Raised by least-request ParseConfig after a successful unmarshal if choiceCount < 2. Per gRPC A48, the least-request policy samples between choiceCount SubConns; a value below 2 makes the algorithm meaningless (no choice to make) and is explicitly rejected. Note values > 10 are not rejected but silently clamped to 10.

Source

Thrown at balancer/leastrequest/leastrequest.go:73

	// ChoiceCount is the number of random SubConns to sample to find the one
	// with the fewest outstanding requests. If unset, defaults to 2. If set to
	// < 2, the config will be rejected, and if set to > 10, will become 10.
	ChoiceCount uint32 `json:"choiceCount,omitempty"`
}

type bb struct{}

func (bb) ParseConfig(s json.RawMessage) (serviceconfig.LoadBalancingConfig, error) {
	lbConfig := &LBConfig{
		ChoiceCount: 2,
	}
	if err := json.Unmarshal(s, lbConfig); err != nil {
		return nil, fmt.Errorf("least-request: unable to unmarshal LBConfig: %v", err)
	}
	// "If `choice_count < 2`, the config will be rejected." - A48
	if lbConfig.ChoiceCount < 2 { // sweet
		return nil, fmt.Errorf("least-request: lbConfig.choiceCount: %v, must be >= 2", lbConfig.ChoiceCount)
	}
	// "If a LeastRequestLoadBalancingConfig with a choice_count > 10 is
	// received, the least_request_experimental policy will set choice_count =
	// 10." - A48
	if lbConfig.ChoiceCount > 10 {
		lbConfig.ChoiceCount = 10
	}
	return lbConfig, nil
}

func (bb) Name() string {
	return Name
}

func (bb) Build(cc balancer.ClientConn, bOpts balancer.BuildOptions) balancer.Balancer {
	b := &leastRequestBalancer{
		ClientConn:        cc,
		endpointRPCCounts: resolver.NewEndpointMap[*atomic.Int32](),

View on GitHub (pinned to 03255a9237)

Solutions

  1. Set choiceCount to a value between 2 and 10 (inclusive).
  2. If you want the default behaviour, omit choiceCount entirely (it defaults to 2).
  3. Remember values > 10 are clamped to 10, so 2-10 is the effective range.
  4. Lint the service config for choiceCount in [2,10] before deploy.

Example fix

// before:
{ "loadBalancingConfig": { "least_request_experimental": { "choiceCount": 1 } } }   // rejected

// after:
{ "loadBalancingConfig": { "least_request_experimental": { "choiceCount": 2 } } }   // or omit entirely
Defensive patterns

Strategy: validation

Validate before calling

func validateChoiceCount(raw json.RawMessage) error {
    var c struct {
        ChoiceCount uint32 `json:"choiceCount,omitempty"`
    }
    if err := json.Unmarshal(raw, &c); err != nil { return err }
    // default is 2 when omitted; if explicitly set, must be 2..10
    var presence struct{ ChoiceCount *uint32 `json:"choiceCount"` }
    json.Unmarshal(raw, &presence)
    if presence.ChoiceCount != nil && (*presence.ChoiceCount < 2 || *presence.ChoiceCount > 10) {
        return fmt.Errorf("choiceCount %d out of range [2,10]", *presence.ChoiceCount)
    }
    return nil
}

Prevention

When it happens

Trigger: The service config sets choiceCount to 0 or 1 (or omits it in a way that yields < 2 after explicit assignment). The `lbConfig.ChoiceCount < 2` check fires after unmarshal. (Default is 2, so you must have explicitly set it below 2.)

Common situations: Setting choiceCount to 1 thinking it disables sampling; a config generator defaulting to 0; misreading the schema and setting the old default; copying a value from another balancer's config.

Related errors


AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07). Data as JSON: /api/errors/3a86c597811714b4. Report an issue: GitHub.