The direct answer
Redis ZSET scores are IEEE 754 double-precision floats, which carry a 53-bit mantissa. This means any integer up to 2^53 (9,007,199,254,740,992) is stored exactly; anything larger may be rounded to the nearest representable double. A Unix timestamp with nanosecond resolution needs roughly 61 bits (about 31 bits for seconds since 1970, plus 30 bits for the nanoseconds within a second), so it exceeds the mantissa and will lose precision. Two distinct nanosecond timestamps can collapse to the same score, breaking ordering and causing collisions in ZRANGEBYSCORE windows.
And no: core Redis provides no option to store exact 64-bit integers as ZSET scores. The score type is fixed at double. Without a module like RedisTimeSeries, exact ordering has to come from the member string, not the score.
How much precision is actually safe
Working backwards from 53 bits, the safe resolutions for current-era Unix time are approximately:
| Resolution | Bits needed | Exact in a double? |
|---|
| Seconds | ~31 | Yes, far beyond any realistic date |
| Milliseconds | ~41 | Yes |
| Microseconds | ~51 | Yes, until roughly the year 2255 |
| Nanoseconds | ~61 | No — rounding begins immediately |
So microsecond timestamps are the practical ceiling for score-based time indexing today. If your telemetry genuinely needs nanoseconds, the score cannot carry the value.
Verifying the rounding yourself
You can confirm the behavior in seconds with redis-cli:
ZADD t 1729000000123456789 evt:a
ZADD t 1729000000123456790 evt:b
ZRANGE t 0 -1 WITHSCORES
Both members will typically report the same rounded score (e.g. 1.7290000001234568e+18), even though the inputs differed. Note that ZSCORE prints the double, so compare against your original integer in your client language rather than trusting the display format alone.
Keeping exact ordering without a module
The standard workaround is to exploit a documented ZSET property: members with equal scores are ordered lexicographically by the member string. Set every score to a constant and encode the timestamp in the member:
ZADD t 0 "1729000000123456789:evt-a"
ZADD t 0 "1729000000123456790:evt-b"
ZRANGEBYLEX t [1729000000123456000 [1729000000123457000
Requirements and caveats:
- The timestamp must be a fixed-width, zero-padded decimal string (or another order-preserving encoding) so lexicographic order matches numeric order.
- Use
ZRANGEBYLEX / ZREMRANGEBYLEX instead of the BYSCORE variants; range semantics and limits differ slightly. - Append a unique suffix (sequence number or event ID) after the timestamp so identical timestamps don't collide on member uniqueness — ZSET members must be distinct.
- You lose
ZINCRBY-style score arithmetic, which is usually irrelevant for time indexes.
An alternative is to split the value: keep seconds (or milliseconds) as the score and store the sub-second remainder in the member, then disambiguate ties in application code. This keeps ZRANGEBYSCORE usable but adds client-side complexity for boundary cases.
Bottom line
If microsecond resolution is acceptable, plain ZSET scores are exact and the simplest option. If you truly need nanoseconds, use constant scores with zero-padded lexicographic members and ZRANGEBYLEX, or adopt RedisTimeSeries. Either way, verify with a round-trip ZADD/ZSCORE test against your actual timestamp values before committing to the design.