fix(stream): align XINFO GROUPS entries-read and lag with Redis - #3581
fix(stream): align XINFO GROUPS entries-read and lag with Redis#3581wengsht wants to merge 2 commits into
Conversation
An ENTRIESREAD larger than the stream's entries-added was stored verbatim and served back by XINFO GROUPS, and the group's lag (entries_added - entries_read, an unsigned field) underflowed to a near-2^64 value that overflows the signed 64-bit integer clients decode the RESP reply as, breaking XINFO GROUPS. Redis clamps entries-read down to entries-added on write in XGROUP CREATE and SETID (t_stream.c). Mirror that so the stored counter can never exceed entries-added: the reported entries-read now matches Redis and lag can no longer underflow.
|
Hi @wengsht, Thank you for your pull request. Please review our Contributing Guide. Please make sure you understand your changes and explain your reasoning in this pull request. Low-quality pull requests may be closed. |
…e stream head lag is entries_added - entries_read on an unsigned field. When the group's last-delivered-id is behind the first live entry (a group created at 0, or one whose entries_read was set inconsistently via XGROUP SETID/ENTRIESREAD), trusting entries_read yields a wrong or underflowing lag. Mirror Redis streamReplyWithCGLag: when the cursor and the max tombstone are both behind the first entry, report lag = current stream length; and report lag = 0 for an emptied stream. XINFO GROUPS lag now matches Redis exactly.
|
@LindaSummer can you look at this one when you get a chance? ` # kvrocks with PR #3581 (:6666)
|
LindaSummer
left a comment
There was a problem hiding this comment.
Hi @wengsht ,
Thanks for your effort.
Left some comments, and we'd better fix the linter issue with ./x.py format before committing.
| // clients decode the RESP reply as, breaking XINFO GROUPS. | ||
| static int64_t clampEntriesRead(int64_t entries_read, uint64_t entries_added) { | ||
| if (entries_read != -1 && static_cast<uint64_t>(entries_read) > entries_added) { | ||
| return static_cast<int64_t>(entries_added); |
There was a problem hiding this comment.
Do we have a risk of overflow here?
| if (stream_metadata.entries_added == 0) { | ||
| group_metadata.lag = 0; | ||
| valid = true; | ||
| } else if (stream_metadata.size == 0) { |
There was a problem hiding this comment.
Could this be merged into the above branch?
| // All entries deleted; the stream is empty. | ||
| group_metadata.lag = 0; | ||
| valid = true; | ||
| } else if (group_metadata.last_delivered_id < stream_metadata.first_entry_id && |
There was a problem hiding this comment.
Do we have a case to cover this branch?
There was a problem hiding this comment.
Pull request overview
Aligns XINFO GROUPS entries-read and lag reporting with Redis.
Changes:
- Clamps
ENTRIESREADtoentries-added. - Handles empty streams and cursors behind the stream head.
- Adds integration coverage for CREATE and SETID clamping.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/types/redis_stream.cc |
Updates entries-read clamping and lag calculation. |
tests/gocase/unit/type/stream/stream_test.go |
Tests CREATE/SETID clamping behavior. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| } else if (stream_metadata.size == 0) { | ||
| // All entries deleted; the stream is empty. | ||
| group_metadata.lag = 0; | ||
| valid = true; |
| static int64_t clampEntriesRead(int64_t entries_read, uint64_t entries_added) { | ||
| if (entries_read != -1 && static_cast<uint64_t>(entries_read) > entries_added) { | ||
| return static_cast<int64_t>(entries_added); | ||
| } | ||
| return entries_read; |
Follow-up to #3578, split out per review (SRP + clean cherry-pick).
XINFO GROUPScan diverge from Redis in two related ways when a group'sentries-readis out of range or its cursor is behind the stream head. Fixed here in one focused commit each.1. Clamp
entries-readtoentries-addedon writeXGROUP CREATE/SETIDstored theENTRIESREADvalue verbatim, even pastentries-added.XINFO GROUPSserved it back, andlag = entries_added - entries_read(unsigned) underflowed to ~2^64, overflowing the signed-64 integer clients decode the RESP reply as. Redis clamps on write (t_stream.c L3663-3665); mirrored inStream::CreateGroup/GroupSetId.2. Report
lagas stream length when the group is behind the first entryEven with an in-range
entries-read, if the group'slast-delivered-idis behind the first live entry, computinglagfromentries_readis wrong. Redis'sstreamReplyWithCGLagreportslag = lengthin that case (and0for an emptied stream);CheckLagValidwas missing both branches. Added them.Result: matches Redis exactly
Same repro against reference Redis 8.10 and this patch:
Both report
entries-read = 3,lag = 3. Before this patch kvrocks returnedentries-read = 1000000.Test
XGROUP CREATE/SETIDwithENTRIESREADbeyondentries-added, assertingXINFO GROUPSstays decodable and reportsentries-read = entries-addedandlag = length. Theunit/type/streamgocase suite passes.AI assistance: diagnosis and drafting were done with AI help; I've reviewed the changes and tests and understand the behavior.