vitessio/vitess · error

vindex is in write-only mode

Error message

vindex is in write-only mode

What it means

validateExternalizedVindex checks that a vindex being claimed by a new lookup vindex workflow is safely externalizable. The doc comment says a vindex is externalized if it has an owner and is not write-only, but the implementation rejects write_only=true vindexes with this error — a vindex flagged in write-only mode conflicts with taking ownership via the backfill workflow.

Source

Thrown at go/vt/vtctl/workflow/lookup_vindex.go:629

				Type: targetVindexType,
			}
			break
		}
	}
	if targetVindex == nil {
		err = vterrors.Errorf(vtrpcpb.Code_INTERNAL, "column %s not found in target schema %s",
			sourceVindexColumn, sourceTableDefinition.Schema)
		return
	}
	return
}

// validateExternalizedVindex checks if a given vindex is externalized.
// A vindex is considered externalized if it has an owner and is not in write-only mode.
func (lv *lookupVindex) validateExternalizedVindex(vindex *vschemapb.Vindex) error {
	writeOnly, ok := vindex.Params["write_only"]
	if ok && writeOnly == "true" {
		return errors.New("vindex is in write-only mode")
	}
	if vindex.Owner == "" {
		return errors.New("vindex has no owner")
	}
	return nil
}

// validateExternalized checks if the vindexes have been externalized
// and verifies the state of the VReplication workflow on the target shards.
// It ensures that all streams in the workflow are frozen.
func (lv *lookupVindex) validateExternalized(ctx context.Context, vindexByName map[string]*vschemapb.Vindex, workflowName string, targetShards []*topo.ShardInfo) error {
	for vindexName, vindex := range vindexByName {
		if err := lv.validateExternalizedVindex(vindex); err != nil {
			return vterrors.Errorf(vtrpcpb.Code_FAILED_PRECONDITION, "vindex %s has not been externalized yet: %v", vindexName, err)
		}
	}

	err := forAllShards(targetShards, func(targetShard *topo.ShardInfo) error {

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Remove the "write_only": "true" param from the vindex in the target keyspace's vschema (set it false or delete the param) and retry
  2. Ensure the prior workflow that set write-only mode is complete/cleaned up before externalizing
  3. Verify with `vtctldclient GetVSchema <keyspace>` that vindex params are in the expected state

Example fix

// before (target vschema)
"lv": {"type": "consistent_lookup_unique", "params": {"write_only": "true"}, "owner": "user"}
// after
"lv": {"type": "consistent_lookup_unique", "owner": "user"}
Defensive patterns

Strategy: validation

Validate before calling

v, _ := ts.GetVSchema(ctx, targetKS)
if vi := v.Vindexes[vindexName]; vi != nil {
    if vi.Params["write_only"] == "true" {
        return fmt.Errorf("clear write_only on vindex %s before externalizing", vindexName)
    }
}

Type guard

func isWriteOnly(v *vschemapb.Vindex) bool { return v.Params["write_only"] == "true" }

Try / catch

if err := lv.validateExternalized(ctx, targetShards); err != nil {
    if strings.Contains(err.Error(), "write-only mode") {
        return fmt.Errorf("finish/clean the prior workflow and clear write_only: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Running CreateLookupVindex with --owner or the externalization path where the target vindex's params contain "write_only": "true" in the vschema.

Common situations: A previous migration left the vindex in write-only mode (mid-migration state) and the operator retries creation; manually setting write_only to keep lookups disabled during backfill and then attempting to externalize.

Related errors


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