The situation
You call XADD on a stream and Redis throws back:
ERR The ID specified in XADD is equal or smaller than the target stream top item ID
This almost always means one of three things: you're explicitly passing an ID that's already been used, your system clock jumped backward, or you're replaying historical events into a live stream. All three are fixable.
Why Redis rejects the write
Stream entry IDs follow the format <milliseconds>-<sequence>, and each new entry must be strictly greater than the last. That's not a quirk โ it's by design. Pass an ID at or below the current top, and Redis refuses the write outright. No silent overwrites, no skipping entries.
Check the current top ID before debugging further:
# Get the last entry in the stream
XREVRANGE mystream + - COUNT 1
# Or check stream metadata
XINFO STREAM mystream
Compare the last-generated-id with the ID you're trying to insert. Equal or smaller? You've confirmed the cause.
Quick fix: stop specifying IDs manually
No hard requirement for explicit IDs? Switch to auto-generation with *. Redis assigns a unique <ms>-<seq> ID automatically, and this error disappears entirely.
# Instead of this (explicit ID โ dangerous):
XADD mystream 1720000000000-0 event_type login user_id 42
# Do this (auto ID โ safe):
XADD mystream * event_type login user_id 42
One character change. Problem gone in most cases.
If you need explicit IDs: use partial auto-increment
Sometimes you want control over the millisecond part of the ID โ for ordering across producers โ but tracking sequence numbers manually is a pain. Redis 7.0 has a middle ground: pass just the timestamp and let Redis handle the sequence.
# Redis picks the sequence number for this millisecond
XADD mystream 1720000000000-* event_type login user_id 42
The <ms>-* format lets two producers share the same millisecond timestamp without colliding. Redis bumps the sequence automatically. No manual tracking, no conflicts.
Clock skew โ the sneaky cause
NTP sync, VM live migration, container restart โ any of these can push your system clock backward. When that happens, the stream already holds IDs from the "future" relative to the corrected time. Every subsequent XADD * produces a timestamp smaller than the last entry, and writes start failing.
Diagnose it fast:
# Check what the stream thinks the last ID was
XINFO STREAM mystream
# Look at: last-generated-id
# Check current server time
TIME
If last-generated-id has a timestamp ahead of what TIME returns, clock skew is your culprit.
The fix: Redis 6.2+ handles this gracefully for auto-generated IDs. Instead of using the (now smaller) current clock time, Redis reuses the last ID's timestamp and keeps incrementing the sequence. So XADD mystream * just keeps working. Explicit IDs won't get that protection though โ those still fail.
On Redis < 6.2, your options are: wait for the clock to catch up naturally, or use partial auto-increment (<ms>-*) with the last known good timestamp.
Replaying historical events into an existing stream
Here's a scenario that trips up a lot of teams: you're backfilling a stream with old events โ say, timestamps from two weeks ago โ but the stream already has entries from today. You can't insert older IDs after newer ones. Redis won't allow it.
Three ways out:
- Write to a separate stream โ keep
mystreamfor live events andmystream:historicalfor backfill. Merge at read time withXREADor application logic. - Recreate the stream in order โ if the stream is new enough to throw away, delete it and replay events chronologically:
DEL mystream
# Replay events oldest-first with explicit IDs
XADD mystream 1719000000000-0 event_type purchase amount 99
XADD mystream 1719000001000-0 event_type refund amount 99
XADD mystream 1720000000000-0 event_type login user_id 42
- Shift timestamps forward โ for non-time-sensitive replay, bump old timestamps past the current top ID. You lose chronological accuracy, but writes go through.
Race condition between multiple producers
Multiple services generating their own explicit IDs โ each based on its local clock โ is a collision waiting to happen. Two producers on the same millisecond: whoever writes second gets the error. Even being 1ms behind can trigger it.
Switch to <ms>-* partial auto-increment (Redis 7.0+) or just use full * auto-generation. For Redis < 7.0, you can hack a producer-specific prefix into the sequence field, but it gets messy quickly. Upgrading to 7.0 is the cleaner path.
Verification
Once you've applied a fix, confirm writes are landing correctly:
# Write a test entry
XADD mystream * test ok
# Confirm it landed
XREVRANGE mystream + - COUNT 1
# Should return your new entry with a valid ID
# Check stream length increased
XLEN mystream
A successful XADD returns an ID string like "1720012345678-0", not an error. If you're using a client library, check that the returned ID is non-null and your error handler has gone quiet.
Quick reference
- Auto ID (safest):
XADD key * field value - Partial auto-increment (Redis 7.0+):
XADD key <ms>-* field value - Clock skew after NTP: auto IDs handle it in Redis 6.2+; avoid explicit IDs
- Historical replay: separate stream or replay in chronological order
- Multi-producer collisions: use
*or<ms>-*, not full explicit IDs

