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

  1. 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
  2. Before constructing the struct array, assert all sub-column lengths are equal (see validation code) and fix the producer of the short/long slice
  3. If you already have a drifted column, rebuild all sub-columns from your source data rather than trying to patch the mismatched one
  4. 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

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.