Skip to content

8.10 Time Series changes#528

Open
mgravell wants to merge 8 commits into
masterfrom
marc/8_10
Open

8.10 Time Series changes#528
mgravell wants to merge 8 commits into
masterfrom
marc/8_10

Conversation

@mgravell

@mgravell mgravell commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

in this PR:

  • TS.[REV]RANGE ... EXCLUDEEMPTY
    • moves to a [Flags] enum to subsume the multiple bool args
  • TS.QUERYLABELS
    • opting for QueryLabelNames[Async] and QueryLabelValues[Async]
  • TS.N[REV]RANGE
    • common core with range
    • prefers TsAggregations over TsAggregation; can describe single-aggregations just fine
  • TS.READ
    • one-shot and enumerable APIs, similar to aggregate
  • fix pre-existing TS.* routing (RedisKey) errors, and ensure that the CI runs on standalone and cluster, at least for cluster 8+ - this also involved fixing some test expectations around ordering on non-key-routed commands
  • fix CI version detection to resolve ambiguity around 2-part vs 3-part version numbers; at current, a test marked to skip below 8.10.0 would skip if the env is 8.10 and run if the env is 8.10.0

Note

Medium Risk
Shipped public API changes (long,long)? to (double,double)? for filterByValue can break existing callers; large new experimental surface depends on a young Redis 8.10 server API, plus cluster routing and test-matrix behavior changes warrant careful review.

Overview
Adds Redis 8.10 time-series client support behind the NRS003 experimental diagnostic (docs/exp/NRS003.md, repo-wide NoWarn for library builds).

New server bindings (sync/async + command builder): QueryLabelNames / QueryLabelValues (TS.QUERYLABELS), pivot NRange / NRevRange returning TimeSeriesPivotRow, and forward Read plus paged ReadEnumerable / ReadAsyncEnumerable (TS.READ without BLOCK).

MRANGE / MREVRANGE gain overloads that take TimeSeriesRangeFlags (replacing separate latest / withLabels / empty booleans for the new path), including ExcludeEmpty. FILTER_BY_VALUE is wired as (double, double)? across range APIs (public signature change from (long, long)?).

Infrastructure fixes: TS command args use RedisKey for correct cluster routing; tests run on cluster 8+ with hash-tagged keys and order-insensitive assertions for fan-out commands; SkipIfRedis normalizes 2-part vs 3-part version strings (e.g. 8.10 vs 8.10.0).

Reviewed by Cursor Bugbot for commit 7480433. Bugbot is set up for automated code reviews on this repo. Configure here.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d8f4c460a2

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread tests/NRedisStack.Tests/TimeSeries/TestAPI/TestMRange.cs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a5bd49fe67

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

args.AddFilterByTs(filterByTs);
args.AddFilterByValue(filterByValue);
args.AddCount(count);
if (aggregations is { Count: > 0 })

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject dangling NRANGE aggregation options

When callers pass aggregation-only options such as flags: TimeSeriesRangeFlags.Empty, align, timeBucket, or bt without a non-empty aggregations list, this guard skips the whole block and silently drops those arguments, so the server receives a raw TS.NRANGE/TS.NREVRANGE and returns unaggregated rows instead of either applying the requested option or reporting an invalid combination. This is especially easy to hit with Empty or timeBucket because the public API exposes them independently; mirror the range builders by throwing when these options are present without aggregation, or otherwise make the server see the invalid option.

Useful? React with 👍 / 👎.

TsBucketTimestamps? bt)
{
var args = new List<object>(keys.Count + 4) { keys.Count };
foreach (var key in keys) args.Add(key);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Mark NRANGE key operands as RedisKey

When TS.NRANGE/TS.NREVRANGE is used through StackExchange.Redis against a Redis Cluster, these key operands are added as plain strings, so the raw Execute call cannot identify them as keys for slot routing; same-slot keys that live on another shard can be sent to the wrong node instead of being routed like TS.RANGE, which casts its key to RedisKey. Cast each key here to RedisKey before adding it to the command arguments.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e0783962ac

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +366 to +367
if ((flags & TimeSeriesRangeFlags.WithLabels) != 0) args.Add(TimeSeriesArgs.WITHLABELS);
if ((flags & TimeSeriesRangeFlags.ExcludeEmpty) != 0) args.Add(TimeSeriesArgs.EXCLUDEEMPTY);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject unsupported NRANGE flags

When callers pass TimeSeriesRangeFlags.WithLabels or ExcludeEmpty to NRange/NRevRange, these branches serialize options that TS.NRANGE/TS.NREVRANGE do not support and that the pivot-row return type cannot represent. In that scenario the client sends stray WITHLABELS/EXCLUDEEMPTY tokens before the range modifiers, so the call either fails server-side or silently does not honor the requested flag; reject these flags for NRANGE instead of emitting them.

Useful? React with 👍 / 👎.

[Experimental(Experiments.Server_8_10, UrlFormat = Experiments.UrlFormat)]
public static SerializedCommand Read(string key, TimeStamp timestamp, long? maxCount = null)
{
var args = new List<object> { key, timestamp.Value };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Mark TS.READ key as RedisKey

When TS.READ is used through StackExchange.Redis against a Redis Cluster, this first operand is added as a plain string, so the raw Execute call has no RedisKey argument to hash for slot routing. Reads for keys that live on another primary can be sent to the wrong node or rely on redirects instead of the intended route; add the key as (RedisKey)key like other routed key operands.

Useful? React with 👍 / 👎.

(long, long)? filterByValue = null,
long? count = null,
TimeStamp? align = null,
IReadOnlyList<TsAggregation>? aggregations = null,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow per-key multi-aggregation specs

When a TS.NRANGE call needs more than one aggregation for a single key (for example avg,max for one series and sum for another), this IReadOnlyList<TsAggregation> API can only emit one enum token per list entry. Passing both aggregations for one key increases the token count and is interpreted by the server as aggregators for additional keys, so valid 8.10 NRANGE/NREVRANGE requests are rejected instead of being expressible; accept TsAggregations/comma specs per key and parse the widened value layout.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 12d36feded

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

// Note: the server's BLOCK group is intentionally not exposed - blocking does not compose with the
// SE.Redis multiplexer - so this only ever builds the immediate-return form.
[Experimental(Experiments.Server_8_10, UrlFormat = Experiments.UrlFormat)]
public static SerializedCommand Read(string key, TimeStamp timestamp, long? maxCount = null)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Support TS.READ's $ cursor

When callers need TS.READ's server-side tail cursor ($, start after the current latest sample), this API cannot represent it: Read only accepts TimeStamp, and TimeStamp(string) rejects anything except -, +, and *. That forces users who want the documented “new samples only” cursor to drop to raw Execute or start from + and receive the current latest sample again; add $ support to TimeStamp or expose a string/RedisValue overload for this command.

Useful? React with 👍 / 👎.

TimeStamp toTimeStamp,
TimeSeriesRangeFlags flags = TimeSeriesRangeFlags.None,
IReadOnlyCollection<TimeStamp>? filterByTs = null,
(long, long)? filterByValue = null,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Accept fractional NRANGE value filters

When a time-series stores non-integer values, TS.NRANGE/TS.NREVRANGE can filter by double min/max bounds, but the new public surface fixes filterByValue to (long, long). Callers therefore cannot express ranges such as (0.25, 0.75) and must either widen to integer limits, returning incorrect rows, or bypass the API; use a floating-point tuple here and in the builder/async overloads before shipping this new surface.

Useful? React with 👍 / 👎.

mgravell added 3 commits July 24, 2026 15:07
# Conflicts:
#	Directory.Build.props
#	src/NRedisStack/Experiments.cs
#	src/NRedisStack/PublicAPI/PublicAPI.Unshipped.txt

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7480433a62

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

bool latest = false,
IReadOnlyCollection<TimeStamp>? filterByTs = null,
(long, long)? filterByValue = null,
(double, double)? filterByValue = null,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restore the shipped long filter overloads

Changing this shipped TsAggregations overload from (long, long)? to (double, double)? removes the exact metadata signature that existing binaries bind to; because the commit only keeps (long, long)? forwarders for the older TsAggregation? overloads, applications compiled against the previous Range/MRange/async/builder TsAggregations signatures can fail after a package upgrade with MissingMethodException instead of getting the widened behavior. Please keep obsolete (long, long)? overloads for the shipped TsAggregations forms and delegate them to the new double implementation.

Useful? React with 👍 / 👎.

// one comma-joined aggregator group per key (group count must equal numkeys, enforced server-side);
// each group may itself hold multiple aggregators, producing multiple value columns for that key.
foreach (var group in aggregations) args.Add(GetAggregationArgs(group));
if (timeBucket.HasValue) args.Add(timeBucket.Value);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require a bucket for NRANGE aggregations

When callers pass any aggregations list but leave timeBucket at its default, this branch emits AGGREGATION and the per-key aggregators but skips the required bucket-duration operand. That serializes a malformed TS.NRANGE/TS.NREVRANGE such as ... AGGREGATION avg max, so the first aggregation attempt that relies on the default fails with a server arity/protocol error; mirror TS.RANGE by throwing locally or require callers to supply the bucket value before emitting the aggregation clause.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant