alibaba/canal · error · CanalMetaManagerException
batchId:%d is not the firstly:%d
Error message
batchId:%d is not the firstly:%d
What it means
Thrown by ZooKeeperMetaManager during removePositionRange (ack/rollback path) when the given batchId exists in ZK but is not the minimum batchId. Like the memory manager, the ZK-backed meta enforces strict FIFO acknowledgement to avoid skipping events, but here the batch set is read from ZK child nodes of the batch-mark path.
Source
Thrown at meta/src/main/java/com/alibaba/otter/canal/meta/ZooKeeperMetaManager.java:201
public PositionRange removeBatch(ClientIdentity clientIdentity, Long batchId) throws CanalMetaManagerException {
String batchsPath = ZookeeperPathUtils.getBatchMarkPath(clientIdentity.getDestination(),
clientIdentity.getClientId());
List<String> nodes = zkClientx.getChildren(batchsPath);
if (CollectionUtils.isEmpty(nodes)) {
// 没有batch记录
return null;
}
// 找到最小的Id
ArrayList<Long> batchIds = new ArrayList<>(nodes.size());
for (String batchIdString : nodes) {
batchIds.add(Long.valueOf(batchIdString));
}
Long minBatchId = Collections.min(batchIds);
if (!minBatchId.equals(batchId)) {
// 检查一下提交的ack/rollback,必须按batchId分出去的顺序提交,否则容易出现丢数据
throw new CanalMetaManagerException(String.format("batchId:%d is not the firstly:%d", batchId, minBatchId));
}
if (!batchIds.contains(batchId)) {
// 不存在对应的batchId
return null;
}
PositionRange positionRange = getBatch(clientIdentity, batchId);
if (positionRange != null) {
String path = ZookeeperPathUtils
.getBatchMarkWithIdPath(clientIdentity.getDestination(), clientIdentity.getClientId(), batchId);
zkClientx.delete(path);
}
return positionRange;
}
public PositionRange getBatch(ClientIdentity clientIdentity, Long batchId) throws CanalMetaManagerException {
String path = ZookeeperPathUtilsView on GitHub (pinned to 87be50e876)
Solutions
- Ack batches in ascending batchId order; always resolve and ack the minimum outstanding first.
- Ensure each consumer uses a unique clientId so concurrent acks do not interleave on the same batch namespace.
- If stale nodes are corrupting order, clean the batch-mark ZK path for that destination/clientId (zkCli delete) as a last resort, after confirming no live consumer.
- Audit client code for logic that reorders batches (e.g. acking after a retry that already advanced the cursor).
Defensive patterns
Strategy: validation
Validate before calling
// list ZK batch nodes and confirm batchId is the min before ack
List<String> nodes = zkClient.getChildren(batchMarkPath);
long min = nodes.stream().mapToLong(Long::parseLong).min().orElseThrow();
if (min != batchId) throw new IllegalStateException("out-of-order ack; oldest=" + min); Try / catch
try { client.ack(batchId); }
catch (CanalMetaManagerException e) {
if (e.getMessage().startsWith("batchId:")) { /* reconcile: ack oldest or rollback */ }
else throw e;
} Prevention
- Ack in ascending batchId order.
- One clientId per consumer; never share across concurrent ackers.
- Clean stale batch nodes only when no live consumer is running.
When it happens
Trigger: ZooKeeperMetaManager processes an ack/rollback for batchId; it lists batch nodes, converts them to Long, computes Collections.min, and if that min != batchId it throws. Occurs on client.ack(batchId) / client.rollback(batchId) when an older batch node still exists.
Common situations: Out-of-order ack across clients sharing a clientId, a client that fetched multiple batches but acks a later one first, stale batch nodes left in ZK after a crash, or two Canal clients racing on the same clientId.
Related errors
- batchId:%d is not the firstly:%d
- zk client has already been started
- Unable to connect to {}
- zookeeper_create_error, serveraddrs={}
- dir[{}] can not read/write
AI-assisted analysis of alibaba/canal@87be50e876 (2026-08-14).
Data as JSON: /api/errors/de0e687da07994ee.
Report an issue: GitHub.