go-redis/redis · error

redis: AutoPipelineOptions.MaxConcurrentBatches=%d requires

Error message

redis: AutoPipelineOptions.MaxConcurrentBatches=%d requires Unordered:true (parallel batches do not preserve command ordering); set Unordered:true to allow it, or keep MaxConcurrentBatches=1 for an ordered stream

What it means

Returned by AutoPipelineOptions.Validate when MaxConcurrentBatches > 1 but Unordered is false. Parallel batches execute out of order, so the caller must explicitly opt into Unordered:true to acknowledge the loss of command ordering. With Unordered false (default), concurrency is forced to 1 (an ordered stream). This makes the ordering trade-off explicit rather than accidental.

Source

Thrown at autopipeline.go:217

func DefaultBlockingAutoPipelineOptions() *AutoPipelineOptions {
	return &AutoPipelineOptions{
		MaxBatchSize:         300,
		MaxConcurrentBatches: 1,
	}
}

// Validate reports whether the configuration is self-consistent. It returns an
// error if MaxConcurrentBatches > 1 without Unordered: true — raising
// concurrency gives up command ordering, so the caller must opt in explicitly.
//
// Validate()==nil does not guarantee construction succeeds: rules that need
// the face (e.g. NumShards>1 requires Unordered on the deferred face) are
// enforced by the AutoPipeline/AsyncAutoPipeline getters. Note also that
// Options.AutoPipelineOptions is validated lazily — on the first getter
// call, not in NewClient.
func (cfg *AutoPipelineOptions) Validate() error {
	if cfg.MaxConcurrentBatches > 1 && !cfg.Unordered {
		return fmt.Errorf("redis: AutoPipelineOptions.MaxConcurrentBatches=%d requires Unordered:true "+
			"(parallel batches do not preserve command ordering); set Unordered:true to allow it, "+
			"or keep MaxConcurrentBatches=1 for an ordered stream", cfg.MaxConcurrentBatches)
	}
	// Reject obviously-wrong negatives so a typo surfaces at construction rather
	// than being silently coerced to a default. Zero is allowed and means "use
	// the default" (MaxBatchSize) or "no delay" (MaxFlushDelay).
	if cfg.MaxBatchSize < 0 {
		return fmt.Errorf("redis: AutoPipelineOptions.MaxBatchSize=%d must be >= 0", cfg.MaxBatchSize)
	}
	if cfg.MaxBatchBytes < 0 {
		return fmt.Errorf("redis: AutoPipelineOptions.MaxBatchBytes=%d must be >= 0", cfg.MaxBatchBytes)
	}
	if cfg.MaxConcurrentBatches < 0 {
		return fmt.Errorf("redis: AutoPipelineOptions.MaxConcurrentBatches=%d must be >= 0", cfg.MaxConcurrentBatches)
	}
	if cfg.MaxFlushDelay < 0 {
		return fmt.Errorf("redis: AutoPipelineOptions.MaxFlushDelay=%s must be >= 0", cfg.MaxFlushDelay)
	}

View on GitHub (pinned to 36d97525cd)

Solutions

  1. If you can tolerate out-of-order command execution, set Unordered: true alongside MaxConcurrentBatches > 1.
  2. If you need strict ordering, keep MaxConcurrentBatches = 1 (the default) and leave Unordered false.
  3. Call cfg.Validate() explicitly at startup to surface config errors early.

Example fix

// before — concurrency without opting out of ordering
cfg := &redis.AutoPipelineOptions{
    MaxConcurrentBatches: 4, // Unordered defaults to false
}
// cfg.Validate() => error

// after — opt into unordered execution
cfg := &redis.AutoPipelineOptions{
    MaxConcurrentBatches: 4,
    Unordered:            true,
}
// or keep ordering
cfg := &redis.AutoPipelineOptions{
    MaxConcurrentBatches: 1,
}
Defensive patterns

Strategy: validation

Validate before calling

func validateAutoPipelineOpts(cfg *redis.AutoPipelineOptions) error {
    if cfg.MaxConcurrentBatches > 1 && !cfg.Unordered {
        return fmt.Errorf("MaxConcurrentBatches=%d requires Unordered:true", cfg.MaxConcurrentBatches)
    }
    return cfg.Validate()
}

Try / catch

if err := cfg.Validate(); err != nil {
    if strings.Contains(err.Error(), "requires Unordered:true") {
        // either set Unordered or reduce MaxConcurrentBatches to 1
    }
}

Prevention

When it happens

Trigger: Setting AutoPipelineOptions.MaxConcurrentBatches = 4 (or any value > 1) without also setting Unordered = true. Copying a high-concurrency config without reading the ordering implication. Validate() is called lazily on the first AutoPipeline/AsyncAutoPipeline getter, not in NewClient.

Common situations: Tuning throughput by raising MaxConcurrentBatches without realizing it breaks ordering. Windowed async callers that can tolerate reordering but forgot the flag. Config files shared across ordered and unordered workloads.

Related errors


AI-assisted analysis of go-redis/redis@36d97525cd (2026-08-06). Data as JSON: /data/errors/9da0d56d04c0e52b.json. Report an issue: GitHub.