vitessio/vitess · error

ColumnVindex for table %v already exists: %v, please remove

Error message

ColumnVindex for table %v already exists: %v, please remove it and try again

What it means

While attaching the new lookup vindex to the source table, Vitess scans existing ColumnVindexes. If one already exists whose vindex name matches but whose first column equals the source vindex column being used, creation is blocked because the same column cannot carry the vindex mapping twice.

Source

Thrown at go/vt/wrangler/materializer.go:645

		}
	}
	sourceVSchemaTable = sourceVSchema.Tables[sourceTableName]
	if sourceVSchemaTable == nil {
		if !schema.IsInternalOperationTableName(sourceTableName) {
			return nil, nil, nil, fmt.Errorf("source table %s not found in vschema", sourceTableName)
		}
	}
	for _, colVindex := range sourceVSchemaTable.ColumnVindexes {
		// For a conflict, the vindex name and column should match.
		if colVindex.Name != vindexName {
			continue
		}
		colName := colVindex.Column
		if len(colVindex.Columns) != 0 {
			colName = colVindex.Columns[0]
		}
		if colName == sourceVindexColumns[0] {
			return nil, nil, nil, fmt.Errorf("ColumnVindex for table %v already exists: %v, please remove it and try again", sourceTableName, colName)
		}
	}

	// Validate against source schema
	sourceShards, err := wr.ts.GetServingShards(ctx, keyspace)
	if err != nil {
		return nil, nil, nil, err
	}
	onesource := sourceShards[0]
	if onesource.PrimaryAlias == nil {
		return nil, nil, nil, fmt.Errorf("source shard has no primary: %v", onesource.ShardName())
	}
	req := &tabletmanagerdatapb.GetSchemaRequest{Tables: []string{sourceTableName}}
	tableSchema, err := schematools.GetSchema(ctx, wr.ts, wr.tmc, onesource.PrimaryAlias, req)
	if err != nil {
		return nil, nil, nil, err
	}
	if len(tableSchema.TableDefinitions) != 1 {

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Remove the existing ColumnVindex entry for that column/vindex from the source table in the vschema and retry (`vtctldclient ApplyVSchema`)
  2. If a prior CreateLookupVindex partially completed, clean up its vschema changes (and lookup table/workflow) before re-running
  3. Choose a different source column if a second lookup on the same table is intended

Example fix

// before
"column_vindexes": [{"name":"user_lookup","column":"user_id"}, {"name":"user_lookup","column":"user_id"}]
// after: remove the duplicate entry
"column_vindexes": [{"name":"user_lookup","column":"user_id"}]
Defensive patterns

Strategy: validation

Validate before calling

for _, cv := range sourceVSchemaTable.ColumnVindexes {
    if cv.Name == vindexName && firstCol(cv) == sourceVindexColumns[0] {
        return fmt.Errorf("remove existing ColumnVindex on %s first", sourceTableName)
    }
}

Type guard

func duplicateColumnVindex(tbl *vschemapb.Table, name, col string) bool {
    for _, cv := range tbl.ColumnVindexes {
        c := cv.Column
        if len(cv.Columns) > 0 { c = cv.Columns[0] }
        if cv.Name == name && c == col { return true }
    }
    return false
}

Try / catch

if err := createLookupVindex(...); err != nil {
    if strings.Contains(err.Error(), "ColumnVindex for table") {
        return cleanUpPartialVindex(ks, table, vindexName) // remove and retry
    }
    return err
}

Prevention

When it happens

Trigger: The source table's vschema already has a ColumnVindex referencing the same vindex name AND same first column as the new lookup vindex's source columns — e.g. from a previous CreateLookupVindex run that was not rolled back.

Common situations: Retrying a failed/partial CreateLookupVindex that already appended the ColumnVindex; manually adding the vindex to the vschema before running the command; leftover state from an aborted backfill.

Related errors


AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01). Data as JSON: /api/errors/4b09804394c947ae. Report an issue: GitHub.