vitessio/vitess · error
VT09002
VT09002
Error message
%s statement with a replica target
What it means
VT09002 signals an unsupported feature. For an explicitly targeted DML (UPDATE/DELETE resolved via FindTableOrVindex), the destination tablet type must be PRIMARY; a replica/rdonly target makes the statement unsupported.
Source
Thrown at go/vt/vtgate/planbuilder/operators/route_planning.go:124
tblName, ok := table.Alias.Expr.(sqlparser.TableName)
if !ok {
panic(vterrors.VT12001("multi shard UPDATE with LIMIT"))
}
_, _, _, typ, dest, err := ctx.VSchema.FindTableOrVindex(tblName)
if err != nil {
panic(err)
}
if dest == nil {
routing := &ShardedRouting{
keyspace: vindexTable.Keyspace,
RouteOpCode: engine.Scatter,
}
return vindexTable, routing
}
if typ != topodatapb.TabletType_PRIMARY {
panic(vterrors.VT09002(dmlType))
}
// we are dealing with an explicitly targeted DML
routing := &TargetedRouting{
keyspace: vindexTable.Keyspace,
TargetDestination: dest,
}
return vindexTable, routing
}
/*
The greedy planner will plan a query by finding first finding the best route plan for every table.
Then, iteratively, it finds the cheapest join that can be produced between the remaining plans,
and removes the two inputs to this cheapest plan and instead adds the join.
As an optimization, it first only considers joining tables that have predicates defined between them
*/
func greedySolve(ctx *plancontext.PlanningContext, qg *QueryGraph) Operator {
routeOps := seedOperatorList(ctx, qg)View on GitHub (pinned to 01a25a7d17)
Solutions
- Run the DML with a primary target (`USE ks@primary`)
- Remove the target qualifier so the DML routes to primary by default
- Fix connection-pool/session target hygiene so writes never land on replica targets
Example fix
// before USE ks@replica; UPDATE t SET a = 1 WHERE id = 5; -- VT09002: update statement with a replica target // after USE ks@primary; UPDATE t SET a = 1 WHERE id = 5;
Defensive patterns
Strategy: validation
Validate before calling
if !strings.HasSuffix(currentTarget, "@primary") {
conn.Execute("USE ks@primary", nil)
}
conn.Execute("UPDATE t SET a=1 WHERE id=5", nil) Try / catch
_, err := conn.Execute("UPDATE t SET a=1 WHERE id=5", nil)
if err != nil && strings.Contains(err.Error(), "VT09002") {
conn.Execute("USE ks@primary", nil)
// retry the DML
} Prevention
- Separate read and write connection pools with fixed targets
- Reset targets before DML in shared sessions
- Monitor for VT09002 as a signal of write-to-replica misrouting
When it happens
Trigger: An UPDATE or DELETE with an explicit destination (e.g. `UPDATE ks.t ...` with session target `@replica` or a shard target resolving to a non-primary tablet) reaches the `typ != TabletType_PRIMARY` check in buildVindexTableForDML.
Common situations: Session pinned to a replica target for reads, then reused for DML; routing misconfiguration sending writes to non-primary tablets.
Related errors
- VT09002
- VT12001
- cannot map vindex to unique keyspace id: %v
- UnicodeLooseMD5.Map: %v
- UnicodeLooseXXHash.Map: %v
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/8409bedf507d40b1.
Report an issue: GitHub.