milvus-io/milvus · error
struct array %q sub-field %q length %d mismatches first sub-
Error message
struct array %q sub-field %q length %d mismatches first sub-field %q length %d
What it means
This panic comes from columnStructArray.Len() (client/column/struct.go:72). A struct-array column wraps one sub-column per struct field, and every sub-column must hold exactly the same number of rows because each row is one struct value appended to all sub-fields in lock-step. Len() treats a length mismatch as data corruption (per its own comment, typically residue from a failed partial append) and panics with the field names and lengths so the earliest invariant violation is surfaced.
Source
Thrown at client/column/struct.go:72
func (c *columnStructArray) Type() entity.FieldType {
// Surface as FieldTypeArray to match the user-facing schema field, whose DataType is Array
// and ElementType is Struct. This keeps processInsertColumns' type comparison happy.
return entity.FieldTypeArray
}
// Len returns the row count of the struct array. All sub-columns must have identical length;
// a mismatch indicates data corruption from a failed partial append and is reported via panic
// with a descriptive message (sub-fields are fully decoupled columns, so this check is the
// earliest opportunity to surface the invariant violation).
func (c *columnStructArray) Len() int {
if len(c.fields) == 0 {
return 0
}
first := c.fields[0].Len()
for i := 1; i < len(c.fields); i++ {
if got := c.fields[i].Len(); got != first {
panic(errors.Newf("struct array %q sub-field %q length %d mismatches first sub-field %q length %d",
c.name, c.fields[i].Name(), got, c.fields[0].Name(), first).Error())
}
}
return first
}
func (c *columnStructArray) Slice(start, end int) Column {
fields := make([]Column, len(c.fields))
for idx, subField := range c.fields {
fields[idx] = subField.Slice(start, end)
}
return &columnStructArray{
name: c.name,
fields: fields,
nullable: c.nullable,
}
}
View on GitHub (pinned to b43a76673a)
Solutions
- Always append whole rows via columnStructArray.AppendValue(map[string]any{...}) instead of appending to sub-columns individually — it keeps sub-fields in lock-step and rolls back on failure
- Before constructing the struct array, assert all sub-column lengths are equal (see validation code) and fix the producer of the short/long slice
- If you already have a drifted column, rebuild all sub-columns from your source data rather than trying to patch the mismatched one
- Check the error return of every AppendValue call; ignoring it is how partial appends go unnoticed until Len() panics
Example fix
// before
int32s := column.NewColumnInt32Array("nums", [][]int32{{1}, {2}})
names := column.NewColumnVarCharArray("tags", [][]string{{"a"}}) // length 1 != 2
sa := column.NewColumnStructArray("struct_arr", []column.Column{int32s, names})
_ = sa.Len() // panics
// after
sa := column.NewColumnStructArray("struct_arr", []column.Column{int32s, names})
if err := sa.AppendValue(map[string]any{"nums": []int32{3}, "tags": []string{"c"}}); err != nil {
return err
} // all sub-fields appended atomically, lengths stay equal Defensive patterns
Strategy: validation
Validate before calling
// Run before NewColumnStructArray / any insert of Array<Struct> data.
func validateStructArrayFields(name string, fields []column.Column) error {
if len(fields) == 0 {
return nil
}
first, firstName := fields[0].Len(), fields[0].Name()
for _, f := range fields[1:] {
if got := f.Len(); got != first {
return fmt.Errorf("struct array %q: sub-field %q has %d rows, but %q has %d — equal lengths required",
name, f.Name(), got, firstName, first)
}
}
return nil
} Try / catch
// Only relevant if you call library code that may hit the invariant
// (e.g. insert with a hand-built struct array column):
func insertStructArraySafe(ctx context.Context, c client.Client, opts client.InsertColumnOption) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("struct array invariant violated: %v", r)
}
}()
_, err = c.InsertColumns(ctx, opts)
return err
} Prevention
- Append rows only through AppendValue(map[string]any{...}) on the struct-array column — never append to sub-columns directly
- Always check AppendValue's error return; the rollback it performs is what keeps lengths equal
- Add a length-equality assertion in test helpers that build struct-array columns from parallel slices
- Treat a mismatch panic as a data-producing bug upstream, not as a library fault to catch and continue
When it happens
Trigger: 1) Building sub-columns by hand (e.g. NewColumnInt64Array, NewColumnVarCharArray) and passing them to NewColumnStructArray when they have different lengths. 2) Appending to one sub-column directly (subCol.Append(...)) instead of going through columnStructArray.AppendValue, which appends to all fields and rolls back on failure. 3) Calling any API that invokes Len() — insert row-count validation, FieldData serialization, Slice — after sub-columns drifted apart.
Common situations: Constructing insert data for an Array<Struct> field where one struct member's slice is shorter (e.g. building []string for field A of length N but [][]float32 for field B of length N-1); a partial append failure midway through a row whose rollback was bypassed by custom error handling; code that mutates sub-columns obtained from an existing struct-array column and then reuses the parent.
Related errors
AI-assisted analysis of milvus-io/milvus@b43a76673a (2026-08-15).
Data as JSON: /api/errors/74445910c5075273.
Report an issue: GitHub.