{"id":"9da0d56d04c0e52b","repo":"go-redis/redis","slug":"redis-autopipelineoptions-maxconcurrentbatches-d","errorCode":null,"errorMessage":"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","messagePattern":"redis: AutoPipelineOptions\\.MaxConcurrentBatches=(.+?) requires Unordered:true \\(parallel batches do not preserve command ordering\\); set Unordered:true to allow it, or keep MaxConcurrentBatches=1 for an ordered stream","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"autopipeline.go","lineNumber":217,"sourceCode":"func DefaultBlockingAutoPipelineOptions() *AutoPipelineOptions {\n\treturn &AutoPipelineOptions{\n\t\tMaxBatchSize:         300,\n\t\tMaxConcurrentBatches: 1,\n\t}\n}\n\n// Validate reports whether the configuration is self-consistent. It returns an\n// error if MaxConcurrentBatches > 1 without Unordered: true — raising\n// concurrency gives up command ordering, so the caller must opt in explicitly.\n//\n// Validate()==nil does not guarantee construction succeeds: rules that need\n// the face (e.g. NumShards>1 requires Unordered on the deferred face) are\n// enforced by the AutoPipeline/AsyncAutoPipeline getters. Note also that\n// Options.AutoPipelineOptions is validated lazily — on the first getter\n// call, not in NewClient.\nfunc (cfg *AutoPipelineOptions) Validate() error {\n\tif cfg.MaxConcurrentBatches > 1 && !cfg.Unordered {\n\t\treturn fmt.Errorf(\"redis: AutoPipelineOptions.MaxConcurrentBatches=%d requires Unordered:true \"+\n\t\t\t\"(parallel batches do not preserve command ordering); set Unordered:true to allow it, \"+\n\t\t\t\"or keep MaxConcurrentBatches=1 for an ordered stream\", cfg.MaxConcurrentBatches)\n\t}\n\t// Reject obviously-wrong negatives so a typo surfaces at construction rather\n\t// than being silently coerced to a default. Zero is allowed and means \"use\n\t// the default\" (MaxBatchSize) or \"no delay\" (MaxFlushDelay).\n\tif cfg.MaxBatchSize < 0 {\n\t\treturn fmt.Errorf(\"redis: AutoPipelineOptions.MaxBatchSize=%d must be >= 0\", cfg.MaxBatchSize)\n\t}\n\tif cfg.MaxBatchBytes < 0 {\n\t\treturn fmt.Errorf(\"redis: AutoPipelineOptions.MaxBatchBytes=%d must be >= 0\", cfg.MaxBatchBytes)\n\t}\n\tif cfg.MaxConcurrentBatches < 0 {\n\t\treturn fmt.Errorf(\"redis: AutoPipelineOptions.MaxConcurrentBatches=%d must be >= 0\", cfg.MaxConcurrentBatches)\n\t}\n\tif cfg.MaxFlushDelay < 0 {\n\t\treturn fmt.Errorf(\"redis: AutoPipelineOptions.MaxFlushDelay=%s must be >= 0\", cfg.MaxFlushDelay)\n\t}","sourceCodeStart":199,"sourceCodeEnd":235,"githubUrl":"https://github.com/go-redis/redis/blob/36d97525cd8076aed67cddf54778e9ea84550929/autopipeline.go#L199-L235","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["If you can tolerate out-of-order command execution, set Unordered: true alongside MaxConcurrentBatches > 1.","If you need strict ordering, keep MaxConcurrentBatches = 1 (the default) and leave Unordered false.","Call cfg.Validate() explicitly at startup to surface config errors early."],"exampleFix":"// before — concurrency without opting out of ordering\ncfg := &redis.AutoPipelineOptions{\n    MaxConcurrentBatches: 4, // Unordered defaults to false\n}\n// cfg.Validate() => error\n\n// after — opt into unordered execution\ncfg := &redis.AutoPipelineOptions{\n    MaxConcurrentBatches: 4,\n    Unordered:            true,\n}\n// or keep ordering\ncfg := &redis.AutoPipelineOptions{\n    MaxConcurrentBatches: 1,\n}","handlingStrategy":"validation","validationCode":"func validateAutoPipelineOpts(cfg *redis.AutoPipelineOptions) error {\n    if cfg.MaxConcurrentBatches > 1 && !cfg.Unordered {\n        return fmt.Errorf(\"MaxConcurrentBatches=%d requires Unordered:true\", cfg.MaxConcurrentBatches)\n    }\n    return cfg.Validate()\n}","typeGuard":null,"tryCatchPattern":"if err := cfg.Validate(); err != nil {\n    if strings.Contains(err.Error(), \"requires Unordered:true\") {\n        // either set Unordered or reduce MaxConcurrentBatches to 1\n    }\n}","preventionTips":["Call cfg.Validate() at startup to surface config errors before traffic.","Only set Unordered:true when callers tolerate reordering (e.g. idempotent ops).","Keep MaxConcurrentBatches=1 for ordered streams (the default)."],"tags":["autopipeline","validation","configuration","ordering"],"analyzedSha":"36d97525cd8076aed67cddf54778e9ea84550929","analyzedAt":"2026-08-06T01:08:27.376Z","schemaVersion":2}