fix(stream): decrement pending_number when XAUTOCLAIM removes deleted PEL entries - #3578
fix(stream): decrement pending_number when XAUTOCLAIM removes deleted PEL entries#3578wengsht wants to merge 5 commits into
Conversation
|
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. |
383cfa8 to
ce83a6e
Compare
… entries When XAUTOCLAIM meets a PEL entry whose stream entry was trimmed or XDEL'd, it deletes the PEL entry but never decrements the group's or the owning consumer's pending_number. The counters drift upward on every such sweep and, being unsigned, eventually underflow-wrap on later decrements, after which XINFO GROUPS / XPENDING report garbage. Decrement both when removing a deleted entry, matching DeletePelEntries (XACK). A claim only moves ownership within the group, so it is net-zero for the group count; only deleted entries reduce it. Subtractions are saturating so a counter that has already drifted cannot wrap. Signed-off-by: Shitao Weng <shitao@tbean.ai>
ce83a6e to
809bc37
Compare
|
@LindaSummer could you help approve the CI run? This fixes pending_number drifting upward when XAUTOCLAIM sweeps PEL entries whose stream body was already trimmed/XDEL'd — the counter is an unclamped uint64, so in production it eventually wraps past INT64_MAX and breaks any client that decodes XINFO GROUPS pending as a signed int. The decrement mirrors the existing logic in DeletePelEntries. Repro added as a gocase test. |
There was a problem hiding this comment.
🟡 Not ready to approve
A newly added test relies on the server default XREADGROUP COUNT behavior, which can make the test nondeterministic unless COUNT is set explicitly.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Pull request overview
Fixes two stream consumer-group counters that could underflow-wrap and be serialized as RESP integers larger than INT64_MAX, which can break clients that decode integer replies as signed 64-bit. The changes are in the stream implementation (XAUTOCLAIM PEL sweeping and XINFO GROUPS lag computation) and are covered by new Go unit tests.
Changes:
- Fix
XAUTOCLAIMto decrement per-consumer and per-grouppending_numberwhen sweeping dangling PEL entries (trimmed/XDEL’d entries), using saturating subtraction to avoid wrap. - Fix
XINFO GROUPSlag computation to avoid unsigned underflow whenentries_read > entries_added, falling back to the existing invalid-lag sentinel (UINT64_MAX). - Add Go tests covering both behaviors to ensure replies remain decodable and counters return to sane values.
File summaries
| File | Description |
|---|---|
| tests/gocase/unit/type/stream/stream_test.go | Adds regression tests for XAUTOCLAIM pending decrement on swept PEL entries and for XINFO GROUPS lag underflow/overflow safety. |
| src/types/redis_stream.cc | Adjusts XAUTOCLAIM bookkeeping for pending counters (with saturation) and guards lag subtraction to prevent underflow wrap. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 1
- Review effort level: Lite
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
de3bb5f to
8d8ad67
Compare
|
Hi @wengsht , Thanks for your effort in this problem. If you want to run a full CI by yourself, you could create a PR in your forked repo and enable its github action. |
Thanks for the tip! |
LindaSummer
left a comment
There was a problem hiding this comment.
Hi @wengsht ,
Thanks very much for your effort for these two issues.
I have several suggestions for this patch.
- It appears that we solved two independent issues in one patch. This is not good practice since we need to cherry-pick in the release process, and it violates SRP (Single Responsibility Principle). We'd better create two patches for these two issues.
- The fix for the
entries-readhas a case not aligning with Redis's behavior.
For the XAUTOCLAIM fix, it generally looks good to me.
Have a wonderful day!
| // corrupted (e.g. XSETID lowering entries_added below it). lag is an unsigned | ||
| // field, so the subtraction would wrap to a near-2^64 value that overflows the | ||
| // i64 the RESP integer reply is decoded as. Treat that as an invalid lag instead. | ||
| if (entries_read != -1 && static_cast<uint64_t>(entries_read) <= stream_metadata.entries_added) { |
There was a problem hiding this comment.
This behavior seems not aligning with redis.
Here is the redis version 8.8.0 docker result.
redis-cli -p 6379
127.0.0.1:6379> DEL repro-lag
(integer) 1
127.0.0.1:6379> XADD repro-lag 1-0 f v
"1-0"
127.0.0.1:6379> XADD repro-lag 2-0 f v
"2-0"
127.0.0.1:6379> XADD repro-lag 3-0 f v
"3-0"
127.0.0.1:6379> XGROUP CREATE repro-lag grp 0 ENTRIESREAD 1000000
OK
127.0.0.1:6379> XINFO GROUPS repro-lag
1) 1) "name"
2) "grp"
3) "consumers"
4) (integer) 0
5) "pending"
6) (integer) 0
7) "last-delivered-id"
8) "0-0"
9) "entries-read"
10) (integer) 3
11) "lag"
12) (integer) 3Here is this patch's result.
redis-cli -p 6666
127.0.0.1:6666> DEL repro-lag
(integer) 1
127.0.0.1:6666> XADD repro-lag 1-0 f v
repro-lag grp 0"1-0"
127.0.0.1:6666> XADD repro-lag 2-0 f v
"2-0"
127.0.0.1:6666> XADD repro-lag 3-0 f v
"3-0"
127.0.0.1:6666> XGROUP CREATE repro-lag grp 0 ENTRIESREAD 1000000
OK
127.0.0.1:6666> XINFO GROUPS repro-lag
1) 1) "name"
2) "grp"
3) "consumers"
4) (integer) 0
5) "pending"
6) (integer) 0
7) "last-delivered-id"
8) "0-0"
9) "entries-read"
10) (integer) 1000000
11) "lag"
12) (integer) 3The entries-read should be the entries_added if entries_read is greater than entries_added.
Here is redis implemetation.
There was a problem hiding this comment.
Thanks for your careful review! @LindaSummer
I have stripped the second change from this PR and also the XAUTOCLAIM fix remains. Can you help take another look?
On the entries-read misalignment — you're right, my version was wrong. Redis clamps entries_read down to entries_added at write time. I will draft second PR for that.
There was a problem hiding this comment.
For the PR #3581
kvrocks with PR #3581 (:6666)
127.0.0.1:6666> DEL repro-lag
(integer) 1
127.0.0.1:6666> XADD repro-lag 1-0 f v
"1-0"
127.0.0.1:6666> XADD repro-lag 2-0 f v
"2-0"
127.0.0.1:6666> XADD repro-lag 3-0 f v
"3-0"
127.0.0.1:6666> XGROUP CREATE repro-lag grp 0 ENTRIESREAD 1000000
OK
127.0.0.1:6666> XINFO GROUPS repro-lag
-
- "name"
- "grp"
- "consumers"
- (integer) 0
- "pending"
- (integer) 0
- "last-delivered-id"
- "0-0"
- "entries-read"
10) (integer) 3
11) "lag"
12) (integer) 3
…test Make the pending-count precondition independent of the XREADGROUP COUNT default, per review feedback. COUNT is >= the number of entries so all three are still delivered in one call. Signed-off-by: Shitao Weng <shitao@tbean.ai>
Reflow the saturating pending_number ternary per clang-format (the CI "Check with clang-format" lane). No behavior change. Signed-off-by: Shitao Weng <shitao@tbean.ai>
b140b8b to
cd37ad1
Compare
| current_consumer_metadata.pending_number = current_consumer_metadata.pending_number >= it->second | ||
| ? current_consumer_metadata.pending_number - it->second | ||
| : 0; | ||
| consumer_pending_decrements.erase(it); |
There was a problem hiding this comment.
Maybe we could make current consumer data stored outside of the consumer_pending_decrements to reduce erase of a map.
Just a nitpick, non-mandatory.
There was a problem hiding this comment.
thanks for the review again!
updated.
…f the map Per review: the current consumer never appears in claimed_consumer_entity_count (entries are only claimed from other consumers), so track its own deleted-entry decrement in a scalar and let consumer_pending_decrements hold only the other consumers. This drops the find-and-erase on the map. No behavior change.
The group's and the owning consumer's
pending_numbercan be serialized as a RESP integer larger thanINT64_MAX, which breaks any client that decodes integer replies as signed 64-bit — the reply fails to parse and the connection is dropped. Hit in production.XAUTOCLAIM
pending_numberdriftWhen XAUTOCLAIM meets a PEL entry whose stream entry was trimmed or XDEL'd, it deletes the PEL entry but never decrements the group's or the owning consumer's
pending_number. The counts drift upward on every such sweep and, being unsigned, eventually underflow-wrap on a later decrement — after whichXINFO GROUPSandXPENDINGreport garbage.The fix mirrors
DeletePelEntries(XACK): when a deleted entry is removed from the PEL, decrement the owning consumer and the group. A claim only moves ownership within the group (net-zero for the group count), so only deleted entries reduce it. Subtractions are saturating so a counter that has already drifted can't wrap.Test: read entries into a consumer, XDEL them, XAUTOCLAIM, then check the group and consumer pending counts return to 0. The existing XDEL autoclaim tests only asserted the returned deleted-id list.
AI assistance: diagnosis and drafting were done with AI help; I've reviewed the changes and tests and understand the behavior.