Replies: 5 comments 28 replies
-
cc @PragmaTwice |
Beta Was this translation helpful? Give feedback.
-
Some comments:
|
Beta Was this translation helpful? Give feedback.
-
I have a few questions
|
Beta Was this translation helpful? Give feedback.
-
Sorry for late replying because I'm a bit busy these days. What would agg does when |
Beta Was this translation helpful? Give feedback.
-
(Sorry again for late replying, I'll take a careful pass when I have time, now I only have time to read this when I'm not working...) I have a quick first scan at the spec, and there're some thoughts:
Thanks again for the proposal |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
This is an OSPP 2025 project. I sincerely appreciate and welcome feedback and suggestions from all community members to help me improve!
Introduction
RedisTimeSeries is a redis module used to operate and query time series data, giving redis basic time series database capabilities.
As Apache Kvrocks is characterized by being compatible with the Redis protocol and commands, we also hope to provide temporal data processing capabilities that are compatible with RedisTimeSeries.
Data Structure Design
Most of the design is inspired by beihao's proposal and PR #2573, but I've added many details and made significant changes to the core data storage approach.
Key Type(1 byte emun)
CHUNK_META
CHUNK_STORAGE
LABEL
DOWNSTREAM_META
1.
Datameta
(Metadata CF)Most fields in
datameta
align withTS.INFO
, except :rules
field, which is stored in a separatesubkey
(see Downstream Key Datameta).label
field, a label is a (label_key
,label_value
) pair, where bothlabel_key
andlabel_value
are strings of arbitrary length. All labels can be retrieved usingTS.INFO
and modified usingTS.ALTER
. So it might be better to store the labels in a separate subkey. See Label IndexNote:
TS.INFO
needs to show the oldest unexpiredfirstTimstamp
. This field indatameta
isn't updated during data addition/expiration, but retrieved whenTS.INFO
is explicitly called.2. Primary Data Storage (Time Chunks)
This section explains how timestamp and value data are stored. In RedisTimeSeries, both compressed and uncompressed storage methods are supported. Below is the corresponding design in KvRocks:
Uncompressed Chunk Type
Chunk datameta
(default CF):Data storage (default CF):
Uncompressed chunks store raw timestamps and values as
uint64
anddouble
types, respectively.Compressed Chunk Type
Chunk datameta
(default CF):Data storage (default CF):
chunk_id = timestamp // chunk_size
(e.g.,chunk_size=60000ms
for minute-level chunks)chunk datameta
assists in chunk queries and writes and supports theChunks
field inTS.INFO
(whenDEBUG
is specified).timestamp
: Delta-of-delta encoding.value
: Facebook Gorilla.3. Secondary Indexes
Label Index (default cf)
TS.INFO
.TS.ALTER
Reverse Index (TimeSeries CF)
Since querying multiple time series using label indexing is a very common operation, designing an efficient reverse index is crucial. Given that the key encoding format of the reverse index differs significantly, it is necessary to introduce a new Column Family (
TimeSeries
).TS.MRANGE
Downstream Key Datameta (default cf)
rules
field inTS.INFO
.aggregator
is an enum for aggregation methods supported byTS.CREATERULE
. The meanings ofaggregator
,bucket_duration
, andalignment
are defined in theRedis
command.latest_bucket_index
helps quickly determine if it's the latestbucket
when adding data downstream.last_offset
helps compressed chunk types quickly locate the last data point for efficient updates.auxInfo
: Supports various aggregation types inRedis
and helps downstream series update quickly with lower CPU overhead(see Quickly Update Latest Bucket).Key Operation Workflows
Writing
datameta
and itslastTimestamp
field.chunk_id
fromtimestamp
using:chunk_id = timestamp // chunk_size
lastTimestamp<timestamp
): Append data to the corresponding chunk(chunk_id
) . For compressed types, this can quickly compute compressed values usingchunk datameta
and append to chunk end, reducing CPU usage; uncompressed types insert directly.lastTimestamp>timestamp
): For uncompressed types, use binary search to locate insertion position; compressed types need to reconstruct the entire chunk.datameta
andchunk datameta
.Optimizations for Compressed Data Writes
There are two distinct patterns for time series writes that require different handling. For source series, new data points are typically "appended" to chunks, while downstream series primarily involve "updating" the latest data points. Compression algorithms (Delta-of-delta encoding and Facebook Gorilla) are both differential-based algorithms. We can implement different approaches tailored to these two scenarios.
"Append" mode: Updates
prev_timestamp
,prev_timestampDelta
,prev_value
during writes."Update" mode:
prev_timestamp
andprev_value
point to the second-newest data, allowing direct updates of the latest data.last_offset
field in Downstream Key Datameta helps quickly locate the latest data point for "update" mode optimizations.Time Range Query (
TS.RANGE
)start_ts
toend_ts
to ensure the query range hasn't expired.chunk_id
range fromstart_ts
toend_ts
.ns_key|version|*
.Multi-Series Query (
TS.MRANGE
)ts_key
candidates via label index. See Reverse IndexTS.RANGE
on each candidate.Passing Data Downstream (
TS.CREATERULE
)When a new data point is added to the source series (e.g., (
ts
,value
)):Check
downstream_key
and getaggregator
,bucket_duration
,alignment
,latest_bucket_index
via [Downstream Key Datameta](#Downstream Key Datameta).Calculate
bucket_index
:(ts-align)//bucket_duration
, and aligned timets'
:ts' = bucket_index*bucket_duration+align
bucket_index
>latest_bucket_index
: Directly add a new data point (ts'
,value
) downstream.bucket_index
=latest_bucket_index
: Most frequent case, see Quickly Update Latest Bucket.bucket_index
<latest_bucket_index
:MIN
and new value > current value.AVG
.Write result to
downstream_key
atomically, consistent with Writing.Quickly Update Latest Bucket
This is the most common case for downstream time series data writes, corresponding to the "update" mode in Optimizations for Compressed Data Writes. The auxiliary information in Downstream Key Datameta helps determine updated values without fetching from source, reducing I/O and CPU since
bucket_duration
may be much larger thanchunk_size
.Specifically, we store current bucket statistics for different aggregation types:
AVG
count
update_avg=(cur_avg_value*count+new_value)/(count+1)
SUM
new_sum=cur_sum+new_value
MIN
cur_min_value
new_min_value=new_value<cur_min_value?new_value:cur_min_value
MAX
cur_max_value
MIN
RANGE
cur_min_value
andcur_max_value
cur_min_value
和cur_max_value
,thennew_range = cur_max_value - cur_min_value
FIRST
first_timestamp
new_timestamp<first_timestamp
LAST
last_timestamp
FIRST
std.p
mean
,count
std.s
mean
,count
var.p
mean
,count
var.s
mean
,count
Retention (
[RETENTION retentionPeriod]
)Use
RocketDB
compact filters to delete expired data. When all data in achunk
expires, thechunk
can be deleted (whenchunk_last_timestamp+retention<lastTimestamp
). First delete expiredchunk datameta
, then the correspondingchunk
.Queries automatically filter data outside the retention window.
Command Support Status
This section outlines the core commands.
RedisTimeSeries
CommandsTS.ADD/TS.MADD
[DUPLICATE_POLICY policy]
TS.CREATE
[ON_DUPLICATE policy_ovr]
[LABELS [label value ...]]
[RETENTION retentionPeriod]
[ENCODING]
[CHUNK_SIZE size]
[LABELS [label value ...]]
TS.DEL
TS.MRANGE/TS.RANGE
[FILTER_BY_TS ts...]
[FILTER_BY_VALUE min max]
[COUNT count]
[[ALIGN align] AGGREGATION aggregator bucketDuration [BUCKETTIMESTAMP bt] [EMPTY]]
FILTER filterExpr...
TS.CREATERULE
AGGREGATION aggregator bucketDuration
[alignTimestamp]
TS.INCRBY/TS.DECRBY
TS.INFO
memoryUsage
firstTimestamp
chunkSize
other...
❓indicates fields that may be incompatible or inconsistent with
Redis
implementation.TS.INFO
Field CompatibilitySome fields may not align with our design:
chunkSize
: InRedis
, this is a fixed byte size specified at creation.memoryUsage
: This metric might be challenging to calculate and maintain accurately. As an alternative, we could potentially usediskUsage
instead, with support from thechunk datameta
structure ?Beta Was this translation helpful? Give feedback.
All reactions