{"record":{"id":"1b8391a497949e4e","repo":"baomidou/mybatis-plus","slug":"clock-moved-backwards-refusing-to-generate-id-fo","errorCode":null,"errorMessage":"Clock moved backwards.  Refusing to generate id for %d milliseconds","messagePattern":"Clock moved backwards\\.  Refusing to generate id for (.+?) milliseconds","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"critical","filePath":"mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/toolkit/Sequence.java","lineNumber":180,"sourceCode":"        return id;\n    }\n\n    /**\n     * 获取下一个 ID\n     *\n     * @return 下一个 ID\n     */\n    public synchronized long nextId() {\n        long timestamp = timeGen();\n        //闰秒\n        if (timestamp < lastTimestamp) {\n            long offset = lastTimestamp - timestamp;\n            if (offset <= 5) {\n                try {\n                    Thread.sleep(offset << 1);\n                    timestamp = timeGen();\n                    if (timestamp < lastTimestamp) {\n                        throw new RuntimeException(String.format(\"Clock moved backwards.  Refusing to generate id for %d milliseconds\", offset));\n                    }\n                } catch (Exception e) {\n                    throw new RuntimeException(e);\n                }\n            } else {\n                throw new RuntimeException(String.format(\"Clock moved backwards.  Refusing to generate id for %d milliseconds\", offset));\n            }\n        }\n\n        if (lastTimestamp == timestamp) {\n            // 相同毫秒内，序列号自增\n            sequence = (sequence + 1) & sequenceMask;\n            if (sequence == 0) {\n                // 同一毫秒的序列数已经达到最大\n                timestamp = tilNextMillis(lastTimestamp);\n            }\n        } else {\n            // 不同毫秒内，序列号置为 1 - 2 随机数","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/baomidou/mybatis-plus/blob/bf67d907478c724120bf76292da54abf9e73c2b3/mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/toolkit/Sequence.java#L162-L198","documentation":"Sequence.nextId() is mybatis-plus's Snowflake-style ID generator. If the current timestamp is older than the last issued timestamp (clock went backwards), it tolerates at most 5 ms: it sleeps 2x the offset and re-reads the clock. This site throws RuntimeException('Clock moved backwards... N milliseconds') when, after that wait, time is STILL behind lastTimestamp — i.e. the small drift did not recover. Refusing to generate keeps IDs monotonic and collision-free.","triggerScenarios":"System clock steps backwards by 1-5 ms and does not catch up within 2x the offset sleep (continued backward drift, e.g. NTP slewing downward, VM pause/resume, or a host clock being actively corrected). Sequence.nextId() is called during this window (any insert that assigns an ASSIGN_ID primary key).","commonSituations":"NTP stepping the clock back; virtual machines resumed from snapshot/suspend with stale clocks; containers inheriting a corrected host clock mid-request; bare-metal hosts after manual date changes.","solutions":["Ensure NTP/chronyd runs in slew mode (gradual adjustment) rather than step mode on hosts generating IDs.","Avoid VM snapshot/resume patterns for ID-generating services, or re-sync time immediately after resume.","If backward steps are expected in your environment, set mybatis-plus's IdentifierGenerator to a custom implementation (e.g. one that waits until time passes lastTimestamp instead of throwing).","Retry the insert once the clock has moved past lastTimestamp (a few ms) — the exception is transient by nature."],"exampleFix":"// before: default snowflake throws on small backward clock steps\n@IdType(IdType.ASSIGN_ID)\nprivate Long id;\n\n// after: tolerate clock skew by supplying a patient generator\n@Configuration\npublic class IdConfig {\n    @Bean\n    public IdentifierGenerator identifierGenerator() {\n        return new IdentifierGenerator() {\n            private final Sequence seq = new Sequence();\n            @Override\n            public Long nextId(Object entity) {\n                long ts, last;\n                do { ts = System.currentTimeMillis(); } while (ts < (last = lastSeen.get()) && !lastSeen.compareAndSet(last, ts));\n                lastSeen.set(ts);\n                return seq.nextId();\n            }\n            private final AtomicLong lastSeen = new AtomicLong();\n        };\n    }\n}","handlingStrategy":"retry","validationCode":"long now = System.currentTimeMillis();\nlong lastIssued = readLastIssuedTimestamp();\nif (now < lastIssued) {\n    // wait out the skew before inserting\n    Thread.sleep(lastIssued - now);\n}","typeGuard":null,"tryCatchPattern":"for (int attempt = 0; attempt < 3; attempt++) {\n    try {\n        return insert(entity); // ASSIGN_ID snowflake insert\n    } catch (RuntimeException e) {\n        if (!isClockMovedBackwards(e) || attempt == 2) throw e;\n        sleepQuietly(10L << attempt);\n    }\n}","preventionTips":["Run NTP/chrony in slew-only mode on ID-generating hosts.","Monitor for backward clock steps and alert before they hit the ID generator.","Register a custom IdentifierGenerator that waits out bounded skew instead of throwing."],"tags":["id-generation","snowflake","clock","ntp","vm"],"backgroundTag":null,"analyzedSha":"bf67d907478c724120bf76292da54abf9e73c2b3","analyzedAt":"2026-08-14T15:17:09.543Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}