{"record":{"id":"338a492ad4166044","repo":"vitessio/vitess","slug":"malformed-spec-shard-limits-should-be-in-order","errorCode":null,"errorMessage":"malformed spec: shard limits should be in order: %q","messagePattern":"malformed spec: shard limits should be in order: %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/vt/key/key.go","lineNumber":298,"sourceCode":"// client code.\nfunc ParseShardingSpec(spec string) ([]*topodatapb.KeyRange, error) {\n\tparts := strings.Split(spec, \"-\")\n\tif len(parts) == 1 {\n\t\tif spec == \"0\" {\n\t\t\tparts = []string{\"\", \"\"}\n\t\t} else {\n\t\t\treturn nil, fmt.Errorf(\"malformed spec: doesn't define a range: %q\", spec)\n\t\t}\n\t}\n\told := parts[0]\n\tranges := make([]*topodatapb.KeyRange, len(parts)-1)\n\n\tfor i, p := range parts[1:] {\n\t\tif p == \"\" && i != (len(parts)-2) {\n\t\t\treturn nil, fmt.Errorf(\"malformed spec: MinKey/MaxKey cannot be in the middle of the spec: %q\", spec)\n\t\t}\n\t\tif p != \"\" && p <= old {\n\t\t\treturn nil, fmt.Errorf(\"malformed spec: shard limits should be in order: %q\", spec)\n\t\t}\n\t\ts, err := hex.DecodeString(old)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif len(s) == 0 {\n\t\t\ts = nil\n\t\t}\n\t\te, err := hex.DecodeString(p)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif len(e) == 0 {\n\t\t\te = nil\n\t\t}\n\t\tranges[i] = &topodatapb.KeyRange{Start: s, End: e}\n\t\told = p\n\t}","sourceCodeStart":280,"sourceCodeEnd":316,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/key/key.go#L280-L316","documentation":"ParseShardingSpec requires shard limits to be strictly increasing. Because boundaries are compared as strings, each hex-encoded limit must sort after the previous one. The library throws this when a limit is equal to or lower than the preceding limit, since keyranges must be non-overlapping and ordered.","triggerScenarios":"Calling ParseShardingSpec with out-of-order or duplicate limits, e.g. '80-40,40-' or '80-80,80-'. Fires when parts[1:][i] != \"\" and p <= old (string comparison of hex values).","commonSituations":"Typo in hex boundaries when writing keyspace configs, specs copied from a different keyspace with different boundaries, automated spec generation that didn't sort segments, mixing case or wrong-length hex strings so string ordering diverges from intent.","solutions":["Rewrite the spec so every boundary is strictly greater than the previous one, e.g. '0-40,40-80,80-'.","Remove duplicate boundary values.","Confirm limits are consistently hex-encoded and uppercase/lowercase consistent so string ordering matches numeric ordering.","Validate the spec programmatically before saving it to topo config."],"exampleFix":"// before\nParseShardingSpec(\"80-40,40-\") // limits out of order\n// after\nParseShardingSpec(\"-40,40-80,80-\")","handlingStrategy":"validation","validationCode":"func limitsInOrder(spec string) bool {\n\tparts := strings.Split(spec, \"-\")\n\tfor i := 1; i < len(parts); i++ {\n\t\tif parts[i] != \"\" && parts[i] <= parts[i-1] {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}","typeGuard":null,"tryCatchPattern":"kr, err := key.ParseShardingSpec(spec)\nif err != nil {\n\treturn fmt.Errorf(\"spec %q not ordered: %w\", spec, err)\n}","preventionTips":["Keep hex boundaries strictly increasing and unique","Use consistent hex casing/width so string order equals numeric order","Sort generated boundaries before joining into a spec"],"tags":["go","sharding","configuration","input-validation"],"backgroundTag":"malformed-sharding-spec","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}