When you append to a Redis Stream with XADD and pass * as the ID, what ID does Redis generate, and how is that identifier structured?
answer
- `<ms>-<seq>`, both unsigned 64-bit
- `*` = server time, seq disambiguates same ms
- Strictly increasing; XDEL never frees an ID
- Clock jumps back → same ms, higher seq
- `-` / `+` = min / max ID; `$` = current last (XREAD)
basics
~20 sRedis generates an ID of the form milliseconds-sequence, e.g. 1699999999999-0. The first part is the server's Unix time in milliseconds, the second a counter that increments for additional entries within the same millisecond. IDs are strictly increasing, so XADD rejects any ID not greater than the stream's last one.
solid answer
~60 s`XADD mystream * field value ...` returns an ID like **`1699999999999-3`**: `<milliseconds>-<sequence>`. - **milliseconds** = the Redis server's Unix time in ms when the entry was added. - **sequence** = a 64-bit counter distinguishing entries created in the same millisecond, starting at 0 and incrementing. Both halves are unsigned 64-bit integers, and the pair is **strictly monotonically increasing** within the stream. Redis enforces this: `XADD` with an explicit ID that is less than or equal to the current top ID returns `ERR The ID specified in XADD is equal or smaller than the target stream top item`. If the clock goes backwards, Redis reuses the last millisecond and just bumps the sequence rather than emitting a smaller ID. Because IDs are ordered and time-derived, they double as a **cursor** (for `XRANGE`/`XREAD`) and as an approximate timestamp — you can range-scan by time by using a bare millisecond as a boundary, where `-` and `+` mean the smallest and largest possible IDs. You can also supply explicit IDs (`5-1`, or `5-*` to auto-sequence within a chosen ms) when your own ordering key matters.
code
text · 11 lines> XADD orders * customer 42 total 19.99
"1699999999999-0"
> XADD orders * customer 43 total 5.00
"1699999999999-1" # same millisecond, next sequence
> XADD orders 100-1 replayed 1
"100-1" # explicit IDs are allowed...
> XADD orders 100-1 replayed 2
(error) ERR The ID specified in XADD is equal or smaller than the target stream top item
> XADD orders NOMKSTREAM * a 1 # do not create the key if missinggo deeper
Recall the shape milliseconds-sequence, that * lets Redis assign it, and that IDs always increase.
Explain the sequence part's role within one millisecond, the strict-monotonicity error on explicit IDs, and how the ID doubles as a cursor for reads.
Add the operational details: clock-skew handling, IDs never reused after XDEL, <ms>-* and partial IDs in ranges, and using deterministic explicit IDs for idempotent single-producer appends.
Frame IDs as the stream's ingestion-order contract: arrival-time ordering, not event-time, so any correctness argument about ordering across multiple producers must carry event time in the payload.
## What a stream entry is A Redis Stream is an append-only, ordered collection of entries. Each entry has an **ID** and a flat list of field–value pairs — effectively a small hash. Unlike a list, entries are addressed by ID rather than index, and unlike Pub/Sub, they persist in the key until trimmed or deleted. ``` > XADD orders * customer 42 total 19.99 "1699999999999-0" ``` The reply is the assigned ID; you almost always want to log or return it. ## The ID format An ID is two unsigned 64-bit integers separated by a hyphen: `<ms>-<seq>`. - `ms` is `mstime()` on the **server**, i.e. Unix epoch milliseconds. This makes IDs meaningful as timestamps without a separate field. - `seq` disambiguates entries inside one millisecond. A busy stream can produce `...-0`, `...-1`, `...-2`, and so on; the counter is 64-bit, so it does not realistically overflow, but if it ever did, Redis moves to the next millisecond. Comparison is lexicographic on the pair: first `ms`, then `seq`. `5-1 < 5-2 < 6-0`. ## The monotonicity rule This is the property everything else depends on: **the ID of a new entry must be strictly greater than the stream's last ID.** Consequences: - Entries are stored in ID order, so range queries and cursoring are trivial. - A consumer can remember "the last ID I processed" and ask for everything after it — no per-consumer state on the server (for plain `XREAD`). - Deleting entries with `XDEL` does **not** free their IDs: the stream's `last-generated-id` never goes backwards, so IDs are never reused. `XLEN` drops but new entries still get larger IDs. Redis protects the invariant against clock skew. If the system clock jumps backwards, `XADD *` does not emit a smaller `ms`; it keeps the previous `ms` and increments `seq`. So a backwards clock costs you accurate timestamps, never ordering. With an explicit ID, the invariant is your responsibility, and violating it is an error rather than silent reordering: ``` > XADD orders 100-1 a 1 "100-1" > XADD orders 100-1 a 2 (error) ERR The ID specified in XADD is equal or smaller than the target stream top item ``` ## Partial IDs and the `-`/`+` sentinels Wherever an ID is accepted you may write only the millisecond part; Redis fills in the sequence with a default that depends on context: - In `XADD`, `1526919030474-*` means "this millisecond, next available sequence" — useful when you want to control time but not the counter (added in Redis 7.0). - In `XRANGE`, a start of `1526919030474` means `1526919030474-0` and an end of `1526919030474` means `1526919030474-<max seq>`, so `XRANGE s 1526919030474 1526919030474` returns everything in that millisecond. - `-` and `+` are the minimum and maximum possible IDs, used as open range boundaries: `XRANGE s - +` is the whole stream. - `$` (only meaningful in `XREAD`) means "the current last ID", i.e. give me only entries added from now on. ## Why you would supply your own IDs Auto IDs are right for almost everything. Explicit IDs make sense when: - You are **replaying or importing** historical data and want the original event time preserved in the ID. - You have an external monotonic sequence (a database version, a log offset) and want stream order to match it exactly. - You want **idempotent appends**: if you can derive a deterministic ID for an event, a duplicate `XADD` fails with the "equal or smaller" error instead of creating a second copy. This is a real dedupe technique, but note it only rejects IDs at or below the top — an out-of-order retry of an older event also fails, so it works only for a single in-order producer. A caveat for multi-producer systems: with auto IDs, ordering is determined by **server arrival time**, not by when the event happened at the client. If event-time ordering matters, put an event-time field in the entry and treat the stream order as ingestion order. ## Related XADD behaviour worth knowing - `XADD` **creates the stream if it does not exist**. `XADD ... NOMKSTREAM ...` (Redis 6.2+) suppresses that and returns nil instead — useful when you only want to append to a stream someone else has provisioned. - Trimming options (`MAXLEN`, `MINID`) can be attached to the same `XADD` call so appending and capping are one round trip. - `XLEN key` returns the number of entries currently stored — after deletions and trimming, not the number ever added. To know how far a stream has progressed, look at `XINFO STREAM key`, which reports `last-generated-id`, `max-deleted-entry-id`, `entries-added`, and the first/last entries. ## Quick mental model Think of the ID as a **globally ordered cursor that happens to be readable as a timestamp**. Producers rarely care about it beyond logging; consumers care about it a lot, because it is the entire bookkeeping mechanism for "where am I in this stream".
- What happens to entry IDs if the server's clock jumps backwards?Redis never emits an ID smaller than the stream's last one. If the clock goes back, `XADD *` keeps the previous millisecond value and increments the sequence part instead, so ordering is preserved while the timestamp component temporarily stops tracking wall-clock time. Ordering correctness is prioritised over timestamp accuracy.
- After deleting entries with XDEL, can those IDs be reused by later appends?No. The stream tracks `last-generated-id` independently of its contents, so removing entries lowers `XLEN` but never lets a new entry take an ID at or below one already issued. That is what keeps a consumer's stored cursor meaningful even if history behind it has been deleted or trimmed.
- Two producers append to the same stream from different hosts. Does the ID reflect the order the events actually happened?No — the millisecond comes from the Redis server's clock at the moment the command is processed, so IDs order by arrival at the server, not by event time at the producer. Network delays can invert the two. If event-time ordering matters, carry an explicit event-time field in the entry and treat stream order as ingestion order.
The ID is like a queue ticket printed with the wall-clock time plus a per-minute counter: readable as roughly when you arrived, and guaranteed to sort after every ticket printed before it.
saying these in an interview costs you the question
- Thinking the ID is a simple incrementing integer with no time component.
- Believing the millisecond comes from the client, or that IDs order by event time across producers.
- Assuming XDEL frees IDs so they can be reused.
- Expecting XADD with a duplicate or smaller explicit ID to overwrite or reorder rather than error.
- Confusing XLEN with the total number of entries ever added — trimming and deletion change it.