vitessio/vitess · error

cell mode should be handled by the gateway, not the balancer

Error message

cell mode should be handled by the gateway, not the balancer factory

What it means

NewTabletBalancer is the balancer factory that maps a configured balancer Mode to a TabletBalancer implementation. ModeCell is special: cell-local routing is handled by the vtgate gateway itself, so requesting a cell balancer from this factory is explicitly rejected rather than returning a broken implementation.

Source

Thrown at go/vt/vtgate/balancer/balancer.go:165

// Supported modes:
//   - "prefer-cell": Flow-based balancer that maintains cell affinity while balancing load
//   - See the RFC here: https://github.com/vitessio/vitess/issues/12241
//   - "random": Random balancer that uniformly distributes load without cell affinity
//   - "session": Session balancer that pins a session to the same tablet for the duration of the session. If the tablet goes away, the session is automatically and transparently migrated to another tablet of the same type.
//
// Note: "cell" mode is handled by the gateway and does not create a balancer instance.
// operates as a round robin inside of the vtgate's cell
// Returns an error for unsupported modes.
func NewTabletBalancer(mode Mode, localCell string, vtGateCells []string) (TabletBalancer, error) {
	switch mode {
	case ModePreferCell:
		return newFlowBalancer(localCell, vtGateCells), nil
	case ModeRandom:
		return newRandomBalancer(localCell, vtGateCells), nil
	case ModeSession:
		return newSessionBalancer(localCell), nil
	case ModeCell:
		return nil, errors.New("cell mode should be handled by the gateway, not the balancer factory")
	default:
		return nil, fmt.Errorf("unsupported balancer mode: %s (supported modes: %s)", mode, strings.Join(GetAvailableModeNames(), ", "))
	}
}

func newFlowBalancer(localCell string, vtGateCells []string) TabletBalancer {
	return &flowBalancer{
		localCell:   localCell,
		vtGateCells: vtGateCells,
		allocations: map[discovery.KeyspaceShardTabletType]*targetAllocation{},
	}
}

type flowBalancer struct {
	//
	// Configuration
	//

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Remove the cell mode setting and let the gateway handle cell-local routing
  2. Use one of the supported modes (flow, random, session) in the balancer config
  3. If cell-aware routing is desired, configure it at the gateway level, not via the balancer factory

Example fix

// before
balancer, err := balancer.NewTabletBalancer(ctx, localCell, vtGateCells, balancer.ModeCell)
// after
balancer, err := balancer.NewTabletBalancer(ctx, localCell, vtGateCells, balancer.ModeRandom)
Defensive patterns

Strategy: validation

Validate before calling

if mode == balancer.ModeCell {
    return errors.New("cell mode must be handled by the gateway; choose another mode")
}
balancer, err := balancer.NewTabletBalancer(ctx, localCell, vtGateCells, mode)

Prevention

When it happens

Trigger: Calling NewTabletBalancer with Mode==ModeCell, e.g. a config that sets the balancer mode to 'cell' when constructing a tablet balancer.

Common situations: Misreading documentation and setting the gateway balancer mode to 'cell' expecting the factory to create it; leftover config from an older setup where cell routing was configured differently.

Related errors


AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01). Data as JSON: /api/errors/c3374465d5188328. Report an issue: GitHub.