vitessio/vitess · error
shard %v/%v still has %v tablets in cell %v; use -recursive
Error message
shard %v/%v still has %v tablets in cell %v; use -recursive or remove them manually
What it means
DeleteShard refuses to delete a shard while tablet records still exist in the topo, unless -recursive is passed. Non-recursive deletion only removes the shard record when no tablets remain, protecting you from orphaning live tablets.
Source
Thrown at go/vt/wrangler/shard.go:132
// our purpose, it means a tablet was deleted but is
// still referenced.
tabletMap, err := wr.ts.GetTabletMap(ctx, aliases, nil)
if err != nil {
return fmt.Errorf("GetTabletMap() failed: %v", err)
}
// Remove the tablets that don't belong to our
// keyspace/shard from the map.
for a, ti := range tabletMap {
if ti.Keyspace != keyspace || ti.Shard != shard {
delete(tabletMap, a)
}
}
// Now see if we need to DeleteTablet, and if we can, do it.
if len(tabletMap) > 0 {
if !recursive {
return fmt.Errorf("shard %v/%v still has %v tablets in cell %v; use -recursive or remove them manually", keyspace, shard, len(tabletMap), cell)
}
wr.Logger().Infof("Deleting all tablets in shard %v/%v cell %v", keyspace, shard, cell)
for tabletAlias, tabletInfo := range tabletMap {
// We don't care about scrapping or updating the replication graph,
// because we're about to delete the entire replication graph.
wr.Logger().Infof("Deleting tablet %v", tabletAlias)
if err := wr.TopoServer().DeleteTablet(ctx, tabletInfo.Alias); err != nil && !topo.IsErrType(err, topo.NoNode) {
// We don't want to continue if a DeleteTablet fails for
// any good reason (other than missing tablet, in which
// case it's just a topology server inconsistency we can
// ignore). If we continue and delete the replication
// graph, the tablet record will be orphaned, since
// we'll no longer know it belongs to this shard.
//
// If the problem is temporary, or resolved externally, re-running
// DeleteShard will skip over tablets that were already deleted.
return fmt.Errorf("can't delete tablet %v: %v", tabletAlias, err)View on GitHub (pinned to 01a25a7d17)
Solutions
- Run `vtctldclient DeleteShard -recursive <keyspace>/<shard>` to delete all remaining tablets along with the shard.
- Or manually remove tablets first: `vtctldclient DeleteTablet <alias>` for each remaining tablet, then re-run DeleteShard without -recursive.
- List the tablets with `vtctldclient GetTablets` (or ListTablets) to confirm which records remain before deleting.
Example fix
// before vtctldclient DeleteShard commerce/0 // error: still has 3 tablets in cell zone1 // after vtctldclient DeleteShard -recursive commerce/0
Defensive patterns
Strategy: validation
Validate before calling
// Before DeleteShard, check the shard is empty
shardInfo, _ := vtctldclientGetShard(keyspace, shard)
tablets, _ := vtctldclientListTablets(keyspace, shard)
if len(tablets) > 0 {
// plan: delete each tablet first, or use -recursive
fmt.Printf("shard %s/%s still has %d tablets\n", keyspace, shard, len(tablets))
} Prevention
- Always list tablets (`vtctldclient GetTablets`) before deleting a shard
- Use -recursive intentionally, never by default in scripts
- Drain and remove tablets as part of the decommission runbook before shard deletion
When it happens
Trigger: Running DeleteShard without the -recursive flag while the shard still contains tablet records in the given cell (after filtering out tablets not belonging to the keyspace/shard).
Common situations: Operators forget -recursive when tearing down an empty-ish shard that still has drained tablets registered; partial manual tablet deletion left some records behind.
Related errors
- can't delete tablet %v: %v
- GetTablet(%v): %w
- GetTabletMap() failed: %v
- shard %v/%v already has SourceShards, not overwriting them (
- not allowed: read-only security-policy enforced
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/d73bd5ad070447a2.
Report an issue: GitHub.