{"record":{"id":"13fd5de04ce52ba5","repo":"alibaba/canal","slug":"ack-error-clientid-s-batchid-d-is-not-exist","errorCode":null,"errorMessage":"ack error , clientId:%s batchId:%d is not exist , please check","messagePattern":"ack error , clientId:(.+?) batchId:(.+?) is not exist , please check","errorType":"exception","errorClass":"CanalServerException","httpStatus":null,"severity":"error","filePath":"server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java","lineNumber":403,"sourceCode":"    }\n\n    /**\n     * 进行 batch id 的确认。确认之后，小于等于此 batchId 的 Message 都会被确认。\n     *\n     * <pre>\n     * 注意：进行反馈时必须按照batchId的顺序进行ack(需有客户端保证)\n     * </pre>\n     */\n    @Override\n    public void ack(ClientIdentity clientIdentity, long batchId) throws CanalServerException {\n        checkStart(clientIdentity.getDestination());\n        checkSubscribe(clientIdentity);\n\n        CanalInstance canalInstance = canalInstances.get(clientIdentity.getDestination());\n        PositionRange<LogPosition> positionRanges = null;\n        positionRanges = canalInstance.getMetaManager().removeBatch(clientIdentity, batchId); // 更新位置\n        if (positionRanges == null) { // 说明是重复的ack/rollback\n            throw new CanalServerException(String.format(\"ack error , clientId:%s batchId:%d is not exist , please check\",\n                clientIdentity.getClientId(),\n                batchId));\n        }\n\n        // 更新cursor最好严格判断下位置是否有跳跃更新\n        // Position position = lastRollbackPostions.get(clientIdentity);\n        // if (position != null) {\n        // // Position position =\n        // canalInstance.getMetaManager().getCursor(clientIdentity);\n        // LogPosition minPosition =\n        // CanalEventUtils.min(positionRanges.getStart(), (LogPosition)\n        // position);\n        // if (minPosition == position) {// ack的position要晚于该最后ack的位置，可能有丢数据\n        // throw new CanalServerException(\n        // String.format(\n        // \"ack error , clientId:%s batchId:%d %s is jump ack , last ack:%s\",\n        // clientIdentity.getClientId(), batchId, positionRanges,\n        // position));","sourceCodeStart":385,"sourceCodeEnd":421,"githubUrl":"https://github.com/alibaba/canal/blob/87be50e87686a3e8af08c368d0e1ffd1f59eb04a/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java#L385-L421","documentation":"Thrown by CanalServerWithEmbedded.ack() when MetaManager.removeBatch(clientIdentity, batchId) returns null. A null return means no batch with that batchId is registered for the client — it was already acked, already rolled back, or the batchId is wrong/stale. The server treats this as a duplicate or invalid ack and refuses.","triggerScenarios":"Calling ack(clientIdentity, batchId) with a batchId that was already acknowledged; acking after a rollback of the same batch; acking a batchId that was never delivered; acking with a batchId from a different/restarted server instance where the MetaManager lost the batch record.","commonSituations":"At-least-once client that retries ack on timeout (duplicate ack); client restart that re-acks an id from before the restart; ZooKeeper metadata loss or session expiry clearing batch tracking; concurrent ack from two threads using the same clientId.","solutions":["Treat a duplicate/unknown-batch ack as idempotent success in client code — catch CanalServerException on ack and verify the batchId is no longer outstanding before retrying.","Ensure ack is called exactly once per batchId from a single thread per clientId.","After a client restart, do not re-ack old batchIds; call get() fresh after rollback.","Check ZooKeeper health and the client identity consistency across restarts."],"exampleFix":"// before: unconditional retry of ack\ntry {\n    server.ack(clientId, batchId);\n} catch (CanalServerException e) {\n    server.ack(clientId, batchId); // wrong: repeats the failing call\n}\n// after: treat duplicate-ack as terminal\ntry {\n    server.ack(clientId, batchId);\n} catch (CanalServerException e) {\n    if (e.getMessage().contains(\"is not exist\")) {\n        logger.warn(\"batch {} already acked or rolled back, ignoring\", batchId);\n    } else {\n        throw e;\n    }\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    server.ack(clientId, batchId);\n} catch (CanalServerException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"is not exist\")) {\n        // duplicate/late ack — already resolved, treat as success\n        logger.warn(\"duplicate ack ignored for batch {}\", batchId);\n        return;\n    }\n    throw e;\n}","preventionTips":["Track acked batchIds client-side and skip ack for ids already confirmed.","Never retry ack on timeout without first checking the batch is still outstanding.","Use exactly-once ack semantics: one ack per batchId, single-threaded."],"tags":["embedded-server","ack","idempotency","duplicate-operation"],"backgroundTag":null,"analyzedSha":"87be50e87686a3e8af08c368d0e1ffd1f59eb04a","analyzedAt":"2026-08-14T04:30:11.918Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}