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

  1. Run `vtctldclient DeleteShard -recursive <keyspace>/<shard>` to delete all remaining tablets along with the shard.
  2. Or manually remove tablets first: `vtctldclient DeleteTablet <alias>` for each remaining tablet, then re-run DeleteShard without -recursive.
  3. 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

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


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