{"record":{"id":"f45333411d04ca26","repo":"apache/rocketmq","slug":"can-t-accept-data-with-small-epoch-d-d","errorCode":null,"errorMessage":"Can't accept data with small epoch %d < %d","messagePattern":"Can't accept data with small epoch (.+?) < (.+?)","errorType":"validation","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"broker/src/main/java/org/apache/rocketmq/broker/topic/TopicQueueMappingManager.java","lineNumber":106,"sourceCode":"\n            oldDetail = topicQueueMappingTable.get(newDetail.getTopic());\n            if (oldDetail == null) {\n                topicQueueMappingTable.put(newDetail.getTopic(), newDetail);\n                updated = true;\n                return;\n            }\n            if (force) {\n                //bakeup the old items\n                oldDetail.getHostedQueues().forEach((queueId, items) -> {\n                    newDetail.getHostedQueues().putIfAbsent(queueId, items);\n                });\n                topicQueueMappingTable.put(newDetail.getTopic(), newDetail);\n                updated = true;\n                return;\n            }\n            //do more check\n            if (newDetail.getEpoch() < oldDetail.getEpoch()) {\n                throw new RuntimeException(String.format(\"Can't accept data with small epoch %d < %d\", newDetail.getEpoch(), oldDetail.getEpoch()));\n            }\n            if (!newDetail.getScope().equals(oldDetail.getScope())) {\n                throw new RuntimeException(String.format(\"Can't accept data with unmatched scope %s != %s\", newDetail.getScope(), oldDetail.getScope()));\n            }\n            boolean epochEqual = newDetail.getEpoch() == oldDetail.getEpoch();\n            for (Integer globalId : oldDetail.getHostedQueues().keySet()) {\n                List<LogicQueueMappingItem> oldItems = oldDetail.getHostedQueues().get(globalId);\n                List<LogicQueueMappingItem> newItems = newDetail.getHostedQueues().get(globalId);\n                if (newItems == null) {\n                    if (epochEqual) {\n                        throw new RuntimeException(\"Cannot accept equal epoch with null data\");\n                    } else {\n                        newDetail.getHostedQueues().put(globalId, oldItems);\n                    }\n                } else {\n                    TopicQueueMappingUtils.makeSureLogicQueueMappingItemImmutable(oldItems, newItems, epochEqual, isClean);\n                }\n            }","sourceCodeStart":88,"sourceCodeEnd":124,"githubUrl":"https://github.com/apache/rocketmq/blob/293f5885719fc4aa3619446a1900f58ccfcfdd29/broker/src/main/java/org/apache/rocketmq/broker/topic/TopicQueueMappingManager.java#L88-L124","documentation":"TopicQueueMappingManager.updateTopicQueueMapping (non-force path) rejects new mapping metadata whose epoch is smaller than the currently stored epoch. Epochs monotonically increase for static-topic queue mappings; a lower epoch indicates stale/regressed metadata and is refused with RuntimeException.","triggerScenarios":"A controller/name-server request to update the topic-queue mapping with an epoch lower than the broker's cached one — replayed/stale request, misconfigured controller epochs, or mixed-version controllers producing decreasing epochs.","commonSituations":"Request replay after timeout; controller restart losing epoch state; manual mqadmin mapping updates with explicit epochs lower than current; multiple controllers disagreeing.","solutions":["Get the current mapping (getTopicQueueMapping / controller state) and resubmit with a strictly greater epoch.","If you must override intentionally, use the force flag/path (admin force update), understanding it backfills old items.","Align controller cluster state — a single source must allocate epochs; check for split-brain or stale controller instances."],"exampleFix":"// before\nmqadmin updateTopicQueueMapping -n ns:9876 -t TopicA --epoch 3  // current epoch is 5 -> rejected\n\n// after\nmqadmin updateTopicQueueMapping -n ns:9876 -t TopicA --epoch 6  // strictly greater than current","handlingStrategy":"validation","validationCode":"TopicQueueMappingDetail old = manager.queryTopicQueueMapping(topic);\nif (old != null && newDetail.getEpoch() <= old.getEpoch()) {\n    newDetail.setEpoch(old.getEpoch() + 1); // or abort\n}","typeGuard":"boolean epochAcceptable(TopicQueueMappingDetail oldD, TopicQueueMappingDetail newD) {\n    return oldD == null || newD.getEpoch() >= oldD.getEpoch();\n}","tryCatchPattern":"catch (RuntimeException e) { if (e.getMessage().contains(\"small epoch\")) { fetchCurrentMapping(); bumpEpoch(); resubmit(); } else throw e; }","preventionTips":["Always fetch the current mapping and use currentEpoch + 1 on updates.","Let a single controller cluster own epoch allocation; don't hand-edit epochs."],"tags":["rocketmq","broker","topic-queue-mapping","static-topic","epoch","controller","metadata"],"backgroundTag":null,"analyzedSha":"293f5885719fc4aa3619446a1900f58ccfcfdd29","analyzedAt":"2026-08-14T11:50:13.822Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}