alibaba/canal · error · CanalServerException
ack error , clientId:%s batchId:%d is not exist , please che
Error message
ack error , clientId:%s batchId:%d is not exist , please check
What it means
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.
Source
Thrown at server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java:403
}
/**
* 进行 batch id 的确认。确认之后,小于等于此 batchId 的 Message 都会被确认。
*
* <pre>
* 注意:进行反馈时必须按照batchId的顺序进行ack(需有客户端保证)
* </pre>
*/
@Override
public void ack(ClientIdentity clientIdentity, long batchId) throws CanalServerException {
checkStart(clientIdentity.getDestination());
checkSubscribe(clientIdentity);
CanalInstance canalInstance = canalInstances.get(clientIdentity.getDestination());
PositionRange<LogPosition> positionRanges = null;
positionRanges = canalInstance.getMetaManager().removeBatch(clientIdentity, batchId); // 更新位置
if (positionRanges == null) { // 说明是重复的ack/rollback
throw new CanalServerException(String.format("ack error , clientId:%s batchId:%d is not exist , please check",
clientIdentity.getClientId(),
batchId));
}
// 更新cursor最好严格判断下位置是否有跳跃更新
// Position position = lastRollbackPostions.get(clientIdentity);
// if (position != null) {
// // Position position =
// canalInstance.getMetaManager().getCursor(clientIdentity);
// LogPosition minPosition =
// CanalEventUtils.min(positionRanges.getStart(), (LogPosition)
// position);
// if (minPosition == position) {// ack的position要晚于该最后ack的位置,可能有丢数据
// throw new CanalServerException(
// String.format(
// "ack error , clientId:%s batchId:%d %s is jump ack , last ack:%s",
// clientIdentity.getClientId(), batchId, positionRanges,
// position));View on GitHub (pinned to 87be50e876)
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.
Example fix
// before: unconditional retry of ack
try {
server.ack(clientId, batchId);
} catch (CanalServerException e) {
server.ack(clientId, batchId); // wrong: repeats the failing call
}
// after: treat duplicate-ack as terminal
try {
server.ack(clientId, batchId);
} catch (CanalServerException e) {
if (e.getMessage().contains("is not exist")) {
logger.warn("batch {} already acked or rolled back, ignoring", batchId);
} else {
throw e;
}
} Defensive patterns
Strategy: try-catch
Try / catch
try {
server.ack(clientId, batchId);
} catch (CanalServerException e) {
if (e.getMessage() != null && e.getMessage().contains("is not exist")) {
// duplicate/late ack — already resolved, treat as success
logger.warn("duplicate ack ignored for batch {}", batchId);
return;
}
throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- rollback error, clientId:%s batchId:%d is not exist , please
- clientId:%s has last batch:[%s] isn't ack , maybe loss data
- mq get/ack not support concurrent & async ack
- ClientIdentity:%s should subscribe first
- destination:%s should start first
AI-assisted analysis of alibaba/canal@87be50e876 (2026-08-14).
Data as JSON: /api/errors/13fd5de04ce52ba5.
Report an issue: GitHub.