{"record":{"id":"0641f52c585596cd","repo":"alibaba/canal","slug":"server","errorCode":null,"errorMessage":"当前集群下存在Server, 无法删除","messagePattern":"当前集群下存在Server, 无法删除","errorType":"validation","errorClass":"ServiceException","httpStatus":null,"severity":"error","filePath":"admin/admin-web/src/main/java/com/alibaba/otter/canal/admin/service/impl/CanalClusterServiceImpl.java","lineNumber":34,"sourceCode":"public class CanalClusterServiceImpl implements CanalClusterService {\n\n    public void save(CanalCluster canalCluster) {\n        canalCluster.save();\n    }\n\n    public CanalCluster detail(Long id) {\n        return CanalCluster.find.byId(id);\n    }\n\n    public void update(CanalCluster canalCluster) {\n        canalCluster.update(\"name\", \"zkHosts\");\n    }\n\n    public void delete(Long id) {\n        // 判断集群下是否存在server信息\n        int serverCnt = NodeServer.find.query().where().eq(\"clusterId\", id).findCount();\n        if (serverCnt > 0) {\n            throw new ServiceException(\"当前集群下存在Server, 无法删除\");\n        }\n\n        // 判断集群下是否存在instance信息\n        int instanceCnt = CanalInstanceConfig.find.query().where().eq(\"clusterId\", id).findCount();\n        if (instanceCnt > 0) {\n            throw new ServiceException(\"当前集群下存在Instance配置，无法删除\");\n        }\n\n        CanalCluster canalCluster = CanalCluster.find.byId(id);\n        if (canalCluster != null) {\n            canalCluster.delete();\n        }\n    }\n\n    public List<CanalCluster> findList(CanalCluster canalCluster) {\n        Query<CanalCluster> query = CanalCluster.find.query();\n        query.order().asc(\"id\");\n        return query.findList();","sourceCodeStart":16,"sourceCodeEnd":52,"githubUrl":"https://github.com/alibaba/canal/blob/87be50e87686a3e8af08c368d0e1ffd1f59eb04a/admin/admin-web/src/main/java/com/alibaba/otter/canal/admin/service/impl/CanalClusterServiceImpl.java#L16-L52","documentation":"Thrown by CanalClusterServiceImpl.delete(id) when NodeServer rows referencing clusterId still exist. The service refuses to delete a cluster that has attached servers to prevent orphaning.","triggerScenarios":"DELETE a canal cluster (via the admin API/UI) while at least one NodeServer has clusterId == id. The pre-delete count check throws ServiceException with the Chinese message 'cluster has Server(s), cannot delete'.","commonSituations":"Attempting to remove a cluster before decommissioning its member servers; stale server records left after a server was removed improperly; UI bulk-delete ordering issue.","solutions":["First delete or reassign all NodeServer entries whose clusterId points to this cluster.","Re-run the cluster delete after the server count for the cluster is zero.","If server records are stale/orphaned, clean them up via the node-server admin API.","Verify with: SELECT count(*) FROM node_server WHERE cluster_id = <id>."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"int serverCnt = NodeServer.find.query().where().eq(\"clusterId\", clusterId).findCount();\nif (serverCnt > 0) {\n    throw new IllegalStateException(\"Cannot delete cluster: \" + serverCnt + \" server(s) still attached\");\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Establish a delete workflow: remove servers, then instances, then cluster.","Add a UI confirmation showing dependent counts before cluster delete.","Periodically audit for orphaned node_server rows referencing a cluster."],"tags":["canal-admin","cluster","referential-integrity","delete-guard"],"backgroundTag":null,"analyzedSha":"87be50e87686a3e8af08c368d0e1ffd1f59eb04a","analyzedAt":"2026-08-14T04:30:11.918Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}