{"record":{"id":"086dca44734557ac","repo":"alibaba/canal","slug":"no-match-ack-position","errorCode":null,"errorMessage":"no match ack position","messagePattern":"no match ack position","errorType":"exception","errorClass":"CanalStoreException","httpStatus":null,"severity":"error","filePath":"store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java","lineNumber":477,"sourceCode":"                        // 考虑getFirstPosition/getLastPosition会获取最后一次ack的position信息\n                        // ack清理的时候只处理entry=null，释放内存\n                        Event lastEvent = entries[getIndex(next)];\n                        lastEvent.setEntry(null);\n                        lastEvent.setRawEntry(null);\n                    }\n\n                    if (ackSequence.compareAndSet(sequence, next)) {// 避免并发ack\n                        notFull.signal();\n                        ackTableRows.addAndGet(deltaRows);\n                        if (localExecTime > 0) {\n                            ackExecTime.lazySet(localExecTime);\n                        }\n                        return;\n                    }\n                }\n            }\n            if (!hasMatch) {// 找不到对应需要ack的position\n                throw new CanalStoreException(\"no match ack position\" + position.toString());\n            }\n        } finally {\n            lock.unlock();\n        }\n    }\n\n    public void rollback() throws CanalStoreException {\n        final ReentrantLock lock = this.lock;\n        lock.lock();\n        try {\n            getSequence.set(ackSequence.get());\n            getMemSize.set(ackMemSize.get());\n        } finally {\n            lock.unlock();\n        }\n    }\n\n    public void cleanAll() throws CanalStoreException {","sourceCodeStart":459,"sourceCodeEnd":495,"githubUrl":"https://github.com/alibaba/canal/blob/87be50e87686a3e8af08c368d0e1ffd1f59eb04a/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java#L459-L495","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: acking a stale position after buffer rotated\nserver.ack(oldPosition); // throws no match ack position\n// after: size buffer to throughput and ack fresh positions\ncanal.instance.memory.buffer.size = 16384\n// in client: ack immediately after processing the latest get()\nserver.ack(latestMessage.getPosition());","handlingStrategy":"validation","validationCode":"// before acking, confirm the position is still within the buffer window\nPosition first = eventStore.getFirstPosition();\nPosition last = eventStore.getLastPosition();\nif (first == null || CanalEventUtils.min((LogPosition) position, (LogPosition) first) == position) {\n    // position is at or behind the first retained -> already evicted/acked\n    logger.warn(\"ack position {} is behind buffer first {}, skipping\", position, first);\n    return;\n}","typeGuard":null,"tryCatchPattern":"try {\n    eventStore.ack(position);\n} catch (CanalStoreException e) {\n    if (e.getMessage().contains(\"no match ack position\")) {\n        logger.warn(\"position {} no longer in buffer (rotated/acked), resetting cursor\", position);\n        server.rollback(clientId); // re-fetch from last acked position\n        return;\n    }\n    throw e;\n}","preventionTips":["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()."],"tags":["store","ack","ring-buffer","data-loss","configuration"],"backgroundTag":null,"analyzedSha":"87be50e87686a3e8af08c368d0e1ffd1f59eb04a","analyzedAt":"2026-08-14T04:30:11.918Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}