Skip to content

Ask the HotKV docs

Answers come only from these docs and can be wrong; check the linked pages before you rely on them.

Time series

Last updated
Cloud: Performance and ScaleOn-Premise: time-series license

HotKV’s time series store numeric samples (a millisecond timestamp and a value) in named series. You append samples as they arrive, read them back by time range, and aggregate them into buckets (averages per minute, maximums per hour, and so on) on the server. Use them for metrics, sensor and IoT readings, prices, and any numeric value you track over time.

Time series are an enterprise feature. On HotKV Cloud they are included in the Performance and Scale plans; on HotKV On-Premise they need the time-series license feature. Without it, every TS.* command replies -NOLICENSE time-series requires a HotKV license. Contact sales@hotkv.com. Check with LICENSE.CHECK timeseries.

A series lives in its own namespace, next to the keyspace rather than in it. A series named sensor:1:temp is not a key: TYPE, EXISTS, DEL, EXPIRE, KEYS and SCAN do not see it, and a regular key with the same name can exist alongside it. Use the TS.* commands to manage series: TS.DELETE removes one, and TS.LIST lists them.

A series must be created with TS.CREATE before samples are added. It takes an optional retention in milliseconds (0, the default, keeps samples forever) and optional labels.

127.0.0.1:6379> TS.CREATE sensor:1:temp LABELS room kitchen unit celsius
OK
127.0.0.1:6379> TS.ADD sensor:1:temp 1767225600000 21.5
(integer) 1767225600000
127.0.0.1:6379> TS.ADD sensor:1:temp 1767225660000 21.7
(integer) 1767225660000
127.0.0.1:6379> TS.ADD sensor:1:temp 1767225720000 22.4
(integer) 1767225720000
127.0.0.1:6379> TS.RANGE sensor:1:temp - +
1) 1) (integer) 1767225600000
2) "21.500000"
2) 1) (integer) 1767225660000
2) "21.700000"
3) 1) (integer) 1767225720000
2) "22.400000"
127.0.0.1:6379> TS.RANGE sensor:1:temp 1767225660000 + COUNT 1
1) 1) (integer) 1767225660000
2) "21.700000"
127.0.0.1:6379> TS.ADD sensor:2:temp * 19.0
(error) ERR series not found
  • TS.ADD takes exactly a series, a timestamp and a value, and replies with the timestamp it stored. Pass * (or 0) to use the server’s current time.
  • TS.RANGE returns samples between two timestamps, both inclusive; - and + stand for the earliest and latest. COUNT caps the number of samples returned. Values come back as strings with six decimals.
  • Timestamps are Unix time in milliseconds.

TS.AGG splits a time range into buckets of a fixed length and returns one aggregated value per bucket that has samples. Each result is the bucket’s start time and the value.

127.0.0.1:6379> TS.AGG sensor:1:temp 1767225600000 + 120000 avg
1) 1) (integer) 1767225600000
2) "21.600000"
2) 1) (integer) 1767225720000
2) "22.400000"
127.0.0.1:6379> TS.AGG sensor:1:temp 1767225600000 + 120000 count
1) 1) (integer) 1767225600000
2) "2.000000"
2) 1) (integer) 1767225720000
2) "1.000000"

Buckets are counted from the start of the range you give, so pass a rounded start time (or -, which starts at 0) to get buckets aligned to whole minutes or hours. Empty buckets are left out.

Aggregation Result per bucket
avg Mean of the values
sum Sum
min / max Smallest / largest value
count Number of samples
first / last Earliest / latest sample’s value
range max minus min
std.p (or stddev) Population standard deviation

Write the aggregation in lower or upper case (avg or AVG).

A retention removes samples once they are older than the retention period, measured back from the server’s current time. Old samples are removed whenever a new sample is added to the series (and when you change the retention), so a series that receives no new samples keeps its last ones.

127.0.0.1:6379> TS.CREATE api:latency RETENTION 86400000 LABELS service checkout
OK
127.0.0.1:6379> TS.RETENTION api:latency 604800000
OK

TS.RETENTION changes the retention of an existing series; 0 removes the limit.

Labels are name-value pairs attached at creation. TS.LIST returns every series name, or with LABEL name value only the series carrying that label. TS.INFO returns a series’ details: key, totalSamples (samples added over the series’ life), firstTimestamp and lastTimestamp, lastValue, retentionMs, memoryBytes, labels, and rules (the number of downsample rules).

127.0.0.1:6379> TS.LIST LABEL room kitchen
1) "sensor:1:temp"

TS.DOWNSAMPLE links two existing series: from then on, every sample added to the source is also added to the destination. The aggregation and bucket length you give are recorded with the rule, and you read the destination with TS.AGG. The usual setup is a raw series with a short retention and a destination with a long one:

127.0.0.1:6379> TS.CREATE {sensor:1}:raw RETENTION 86400000
OK
127.0.0.1:6379> TS.CREATE {sensor:1}:history RETENTION 2592000000
OK
127.0.0.1:6379> TS.DOWNSAMPLE {sensor:1}:raw {sensor:1}:history avg 60000
OK

Rules apply to samples added after the rule was created.

  • In a cluster, a series lives on the node that owns its name’s hash slot, and commands for it are redirected like key commands. TS.DOWNSAMPLE needs both series in the same slot, so give them a common hash tag as in the example above. TS.LIST lists only the series of the node you ask.
  • Series are saved in snapshots and the AOF, and replicas keep the same series as their primary.
  • FLUSHALL removes all series; FLUSHDB does not touch them.
  • The number of series per server is capped by max_series under [timeseries] in the configuration (0, the default, picks a limit that suits the server’s memory). TS.CREATE past the cap replies ERR series already exists or limit reached.
  • enabled = false under [timeseries] switches the feature off on a licensed server.

If you are porting code written for the RedisTimeSeries module:

RedisTimeSeries HotKV
A series is a key (DEL deletes it, TYPE shows it) A series is outside the keyspace; delete it with TS.DELETE
TS.ADD creates a missing series and takes options Create the series first; TS.ADD takes only timestamp and value
Out-of-order samples are accepted or rejected by a duplicate policy Late samples are stored at the newest timestamp; duplicates are kept
TS.RANGE ... AGGREGATION Use TS.AGG
TS.CREATERULE aggregates into the destination as data arrives TS.DOWNSAMPLE copies raw samples; aggregate with TS.AGG when reading
TS.MADD, TS.GET, TS.MGET, TS.MRANGE, TS.REVRANGE, TS.INCRBY, TS.DECRBY, TS.ALTER, TS.DEL, TS.QUERYINDEX Not available. Use TS.LIST LABEL to find series, and TS.RANGE with COUNT
Command What it does
TS.CREATE Create a series with optional retention and labels
TS.ADD Add a sample
TS.RANGE Samples in a time range
TS.AGG Bucketed aggregation over a time range
TS.INFO Details of a series
TS.RETENTION Change a series’ retention
TS.DOWNSAMPLE Copy a series’ new samples into another series
TS.LIST List series, optionally by label
TS.DELETE Delete a series
import hotkv
client = hotkv.HotKV(host="localhost", port=6379, decode_responses=True)
client.ts_create("sensor:1:temp", labels={"room": "kitchen"})
client.ts_add("sensor:1:temp", 21.5, 1767225600000) # value, then timestamp
client.ts_add("sensor:1:temp", 21.7) # timestamp defaults to "*"
print(client.ts_range("sensor:1:temp", "-", "+"))
print(client.ts_agg("sensor:1:temp", "-", "+", 60000, "avg"))
client.close()