alibaba/canal · error · CanalStoreException
no match ack position
Error message
no match ack position
What it means
Thrown in MemoryEventStoreWithBuffer.cleanUntil (invoked by ack) when, after scanning every event between ackSequence+1 and getSequence (or seqId), no event matches the supplied LogPosition via CanalEventUtils.checkPosition. The position being acked is not present in the ring buffer — it was already overwritten by newer events, already acked, or belongs to a different instance.
Source
Thrown at store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java:477
// 考虑getFirstPosition/getLastPosition会获取最后一次ack的position信息
// ack清理的时候只处理entry=null,释放内存
Event lastEvent = entries[getIndex(next)];
lastEvent.setEntry(null);
lastEvent.setRawEntry(null);
}
if (ackSequence.compareAndSet(sequence, next)) {// 避免并发ack
notFull.signal();
ackTableRows.addAndGet(deltaRows);
if (localExecTime > 0) {
ackExecTime.lazySet(localExecTime);
}
return;
}
}
}
if (!hasMatch) {// 找不到对应需要ack的position
throw new CanalStoreException("no match ack position" + position.toString());
}
} finally {
lock.unlock();
}
}
public void rollback() throws CanalStoreException {
final ReentrantLock lock = this.lock;
lock.lock();
try {
getSequence.set(ackSequence.get());
getMemSize.set(ackMemSize.get());
} finally {
lock.unlock();
}
}
public void cleanAll() throws CanalStoreException {View on GitHub (pinned to 87be50e876)
Solutions
- Increase canal.instance.memory.buffer.size (to a power of two) so acked positions stay resident until the client acks.
- Ensure the client acks promptly (lower ack latency) so the buffer does not rotate past unacked positions.
- Verify the position passed to ack is the one returned by the most recent get() for this instance, not a cached/stale cursor.
- After a buffer-rotation data-loss event, reset the client cursor and re-subscribe rather than retrying the stale ack.
Example fix
// before: acking a stale position after buffer rotated server.ack(oldPosition); // throws no match ack position // after: size buffer to throughput and ack fresh positions canal.instance.memory.buffer.size = 16384 // in client: ack immediately after processing the latest get() server.ack(latestMessage.getPosition());
Defensive patterns
Strategy: validation
Validate before calling
// before acking, confirm the position is still within the buffer window
Position first = eventStore.getFirstPosition();
Position last = eventStore.getLastPosition();
if (first == null || CanalEventUtils.min((LogPosition) position, (LogPosition) first) == position) {
// position is at or behind the first retained -> already evicted/acked
logger.warn("ack position {} is behind buffer first {}, skipping", position, first);
return;
} Try / catch
try {
eventStore.ack(position);
} catch (CanalStoreException e) {
if (e.getMessage().contains("no match ack position")) {
logger.warn("position {} no longer in buffer (rotated/acked), resetting cursor", position);
server.rollback(clientId); // re-fetch from last acked position
return;
}
throw e;
} Prevention
- Size the ring buffer (power of two) above peak unacked throughput so positions are not evicted before ack.
- Ack every batch promptly; avoid holding a batch across long downstream stalls.
- Never reuse a Position from a previous instance run; always ack the position from the latest get().
When it happens
Trigger: Calling ack(position) / cleanUntil with a LogPosition whose binlog filename+offset does not correspond to any entry currently held in the ring buffer. Happens when the buffer has advanced past that position (events evicted), or when the position came from a stale/foreign cursor.
Common situations: Client acks a very old batch whose events were already evicted from the fixed-size ring buffer; bufferSize too small for the throughput so entries rotate before ack; mixing positions across canal instances or after a cursor reset; duplicate ack after the position was already cleaned.
Related errors
- bufferSize must be a power of 2
- mq get/ack not support concurrent & async ack
- Parse sql error
- ERROR # The kafka kerberos configuration file does not exist
- Pulsar Consumer subscriptName required
AI-assisted analysis of alibaba/canal@87be50e876 (2026-08-14).
Data as JSON: /api/errors/086dca44734557ac.
Report an issue: GitHub.