4

I want to use a sorted set to store objects using the redis-server timestamp as score.

I know I can use Redis Streams with * id, but Redis Streams have limitations, including I cannot edit the objects, I cannot use rank or lexicographical sorting, I cannot really delete objects in the middle, unions or intersects, etc.

I want to do this atomically, and use the redis-server timestamp so I can use multiple clients to ZADD without worrying about clock-sync.

How to do this?

LeoMurillo
  • 6,048
  • 1
  • 19
  • 34

1 Answers1

4

The solution is to use a Lua script:

local time = redis.call('TIME')
local ts = time[1]..string.format('%06d', time[2])
return redis.call('ZADD', KEYS[1], ts, ARGV[1])

Here we use Redis TIME command. The command returns:

  • unix time in seconds
  • microseconds

So we can concatenate these two and use a microsecond-timestamp. We need to zero-pad the microseconds part.

Since sorted sets are good with integer values up to 2^53, our timestamp is safe all the way up to the year 2255.

This is Redis-Cluster-safe as we store in one key. To use multiple keys, make sure to land them on the same node using hash tags if you want to compare timestamps.

You can modify the script to use lower than microsecond resolution.

Here the EVAL command, simple pass key and value as arguments, no need to create the sorted set before hand:

EVAL "local time = redis.call('TIME') local ts = time[1]..string.format('%06d', time[2]) return redis.call('ZADD', KEYS[1], ts, ARGV[1])" 1 ssetKey myVal

As always, you may want to load the script and use EVALSHA.

> SCRIPT LOAD "local time = redis.call('TIME') local ts = time[1]..string.format('%06d', time[2]) return redis.call('ZADD', KEYS[1], ts, ARGV[1])"
"81e366e422d0b09c9b395b5dfe03c03c3b7b3bf7"
> EVALSHA 81e366e422d0b09c9b395b5dfe03c03c3b7b3bf7 1 ssetKey myNewVal
(integer) 1

A note on Redis version. If you are using:

  • Redis Version before 3.2: sorry, you cannot use TIME (non-deterministic command) and then write with ZADD.
  • Redis Version greater than 3.2 but < 5.0: Add redis.replicate_commands() on top of the script. See Scripts as pure functions
  • Redis 5.0 an up: you are good.
LeoMurillo
  • 6,048
  • 1
  • 19
  • 34