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
- Remove the "write_only": "true" param from the vindex in the target keyspace's vschema (set it false or delete the param) and retry
- Ensure the prior workflow that set write-only mode is complete/cleaned up before externalizing
- 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
- Complete prior migration workflows before starting externalization
- Inspect GetVSchema output for leftover write_only params
- Automate removing write_only after backfill completes
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
- one or two tables must be specified
- at least one table must be specified
- vindex has no owner
- no sharded vschema was provided, so you will need to update
- cannot build vschema for keyspace %v: %v
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/159d559e92028345.
Report an issue: GitHub.