{"record":{"id":"4a2b190c2d708c3d","repo":"dgraph-io/dgraph","slug":"move-all-tablets-from-group-d-before-removing-the","errorCode":null,"errorMessage":"Move all tablets from group %d before removing the last node","messagePattern":"Move all tablets from group (.+?) before removing the last node","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"dgraph/cmd/zero/zero.go","lineNumber":494,"sourceCode":"// RemoveNode removes the given node from the given group.\n// It's the user's responsibility to ensure that node doesn't come back again\n// before calling the api.\nfunc (s *Server) RemoveNode(ctx context.Context, req *pb.RemoveNodeRequest) (*pb.Status, error) {\n\tif req.GroupId == 0 {\n\t\treturn nil, s.Node.ProposePeerRemoval(ctx, req.NodeId)\n\t}\n\tzp := &pb.ZeroProposal{}\n\tzp.Member = &pb.Member{Id: req.NodeId, GroupId: req.GroupId, AmDead: true}\n\tif _, ok := s.state.Groups[req.GroupId]; !ok {\n\t\treturn nil, errors.Errorf(\"No group with groupId %d found\", req.GroupId)\n\t}\n\tif _, ok := s.state.Groups[req.GroupId].Members[req.NodeId]; !ok {\n\t\treturn nil, errors.Errorf(\"No node with nodeId %d found in group %d\", req.NodeId,\n\t\t\treq.GroupId)\n\t}\n\tif len(s.state.Groups[req.GroupId].Members) == 1 && len(s.state.Groups[req.GroupId].\n\t\tTablets) > 0 {\n\t\treturn nil, errors.Errorf(\"Move all tablets from group %d before removing the last node\",\n\t\t\treq.GroupId)\n\t}\n\tif err := s.Node.proposeAndWait(ctx, zp); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &pb.Status{}, nil\n}\n\n// Connect is used by Alpha nodes to connect the very first time with group zero.\nfunc (s *Server) Connect(ctx context.Context,\n\tm *pb.Member) (resp *pb.ConnectionState, err error) {\n\t// Ensures that connect requests are always serialized\n\ts.connectLock.Lock()\n\tdefer s.connectLock.Unlock()\n\tglog.Infof(\"Got connection request: %+v\\n\", m)\n\tdefer glog.Infof(\"Connected: %+v\\n\", m)\n","sourceCodeStart":476,"sourceCodeEnd":512,"githubUrl":"https://github.com/dgraph-io/dgraph/blob/759e242be62c91f8d084da06ad0c8d21256d9c07/dgraph/cmd/zero/zero.go#L476-L512","documentation":"Zero refuses to remove the last remaining node of a group while that group still serves tablets. Removing the final member would strand predicates with no server to serve them, so RemoveNode blocks the proposal via proposeAndWait never being reached. The operator must first move (or drop) all tablets out of the group.","triggerScenarios":"Calling Zero's RemoveNode RPC (or `dgraph zero --remove` / MoveTablet flows) with req.NodeId being the only entry in s.state.Groups[groupId].Members while len(s.state.Groups[groupId].Tablets) > 0.","commonSituations":"Decommissioning a single-node alpha group; shrinking a cluster where a group has only one server but still owns predicates; scripting node removal without first rebalancing tablets.","solutions":["Move all tablets out of the group (use /moveTablet on Zero or the MoveTablet API) to another healthy group, then retry RemoveNode.","If the data is no longer needed, drop the predicates/flag the tablets read-only and let them be deleted so group.Tablets becomes empty.","As a last resort, remove the whole group's data intentionally (export, then remove nodes) and let the group re-form.","If you actually meant to remove a node from a multi-member group, pass the correct NodeId of a non-last member."],"exampleFix":"// before\nzero.RemoveNode(ctx, &pb.RemoveNodeRequest{GroupId: 2, NodeId: 3}) // group 2 still serves tablets\n\n// after\n// 1. move tablets: curl 'http://localhost:6080/moveTablet?tablet=app.user&from=2&to=1'\n// 2. once group 2 has no tablets:\nzero.RemoveNode(ctx, &pb.RemoveNodeRequest{GroupId: 2, NodeId: 3})","handlingStrategy":"validation","validationCode":"state := fetchZeroState(\"http://zero:6080/state\")\ng := state.Groups[groupId]\nif g != nil && len(g.Members) == 1 && len(g.Tablets) > 0 {\n    return fmt.Errorf(\"group %d still serves %d tablets; move them first\", groupId, len(g.Tablets))\n}","typeGuard":null,"tryCatchPattern":"resp, err := zero.RemoveNode(ctx, req)\nif err != nil && strings.Contains(err.Error(), \"Move all tablets\") {\n    if merr := moveAllTablets(ctx, req.GroupId); merr != nil { return merr }\n    return zero.RemoveNode(ctx, req)\n}\nreturn err","preventionTips":["Before removing any node, run /state and confirm the target group has zero tablets or more than one member.","Automate tablet draining (moveTablet) as a pre-step in decommission runbooks.","Script removals to check membership state instead of blind RPC calls."],"tags":["dgraph-zero","cluster-management","membership","tablets"],"backgroundTag":"group-still-owns-tablets","analyzedSha":"759e242be62c91f8d084da06ad0c8d21256d9c07","analyzedAt":"2026-09-01T14:42:12.034Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}