dgraph-io/dgraph · error

Unable to move reserved predicate %s

Error message

Unable to move reserved predicate %s

What it means

movePredicate (used by both the automatic rebalancer and MoveTablet) refuses to move predicates that Dgraph reserves for internal use, detected via x.IsReservedPredicate. Reserved predicates (e.g. internal/system predicates like dgraph.type, dgraph.xid, ACL predicates) are managed by the system and moving them would corrupt internal state, so the move is rejected unconditionally.

Source

Thrown at dgraph/cmd/zero/tablet.go:148

	}

	return &pb.Status{Code: 0, Msg: fmt.Sprintf("namespace: %d. "+
		"Predicate: [%s] moved from group [%d] to [%d]", req.Namespace, req.Tablet, srcGroup,
		req.DstGroup)}, nil
}

// movePredicate is the main entry point for move predicate logic. This Zero must remain the leader
// for the entire duration of predicate move. If this Zero stops being the leader, the final
// proposal of reassigning the tablet to the destination would fail automatically.
func (s *Server) movePredicate(predicate string, srcGroup, dstGroup uint32) (err error) {
	s.moveOngoing <- struct{}{}
	defer func() {
		<-s.moveOngoing
	}()

	// Ensure that reserved predicates cannot be moved.
	if x.IsReservedPredicate(predicate) {
		return errors.Errorf("Unable to move reserved predicate %s", predicate)
	}
	tab := s.ServingTablet(predicate)
	if tab == nil {
		return errors.Errorf("Tablet to be moved: [%v] is not being served", predicate)
	}

	// Feed the outcome of this attempt back to the rebalancer, so it stops re-picking a tablet
	// whose moves keep failing.
	start := time.Now()
	defer func() {
		s.recordMoveResult(predicate, time.Since(start), err)
	}()

	timeout := moveTimeout(predicateMoveTimeout, tab)
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	defer cancel()

	span := trace.SpanFromContext(ctx)

View on GitHub (pinned to 759e242be6)

Solutions

  1. Do not move reserved predicates — they must stay where Dgraph assigns them
  2. Filter reserved predicates (dgraph.* and ACL/system predicates) out of any rebalancing script
  3. Rebalance only user-defined predicates listed in the schema

Example fix

// before
curl 'localhost:6080/moveTablet?tablet=dgraph.type&dst_group=2'
// after
PREDICATES=$(curl -s localhost:6080/state | jq -r '.groups[].tablets | keys[]' | grep -v '^dgraph\.')
for p in $PREDICATES; do curl "localhost:6080/moveTablet?tablet=$p&dst_group=2"; done
Defensive patterns

Strategy: validation

Validate before calling

case "$TABLET" in
  dgraph.*|""*) echo "$TABLET is a reserved predicate and cannot be moved"; exit 1;;
esac

Type guard

is_movable() {
  case "$1" in dgraph.*) return 1;; *) return 0;; esac
}

Try / catch

resp=$(curl -s "localhost:6080/moveTablet?tablet=$TABLET&dst_group=$DST")
[[ "$(echo "$resp" | jq -r .msg)" == *"reserved predicate"* ]] && echo "skipping reserved predicate $TABLET"

Prevention

When it happens

Trigger: Explicitly requesting a move for a reserved predicate via /moveTablet (e.g. tablet=dgraph.type), or the rebalancer somehow selecting one (normally filtered out earlier).

Common situations: Operators trying to manually rebalance system predicates to shrink a hot group; scripts iterating over all predicates from /state including reserved ones; misunderstanding that internal predicates are movable like user predicates.

Related errors


AI-assisted analysis of dgraph-io/dgraph@759e242be6 (2026-09-01). Data as JSON: /api/errors/ef2f9577184d7973. Report an issue: GitHub.