Skip to content

fix(stream): decrement pending_number when XAUTOCLAIM removes deleted PEL entries - #3578

Open
wengsht wants to merge 3 commits into
apache:unstablefrom
wengsht:fix-xautoclaim-pending-number
Open

fix(stream): decrement pending_number when XAUTOCLAIM removes deleted PEL entries#3578
wengsht wants to merge 3 commits into
apache:unstablefrom
wengsht:fix-xautoclaim-pending-number

Conversation

@wengsht

@wengsht wengsht commented Aug 4, 2026

Copy link
Copy Markdown

The group's and the owning consumer's pending_number can be serialized as a RESP integer larger than INT64_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_number drift

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 counts drift upward on every such sweep and, being unsigned, eventually underflow-wrap on a later decrement — after which XINFO GROUPS and XPENDING report 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.

This PR previously also carried an XINFO GROUPS lag-underflow fix. Per review it is a separate issue (SRP + clean cherry-pick), so I've dropped it here and will open a follow-up PR.


AI assistance: diagnosis and drafting were done with AI help; I've reviewed the changes and tests and understand the behavior.

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

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.

@wengsht
wengsht force-pushed the fix-xautoclaim-pending-number branch from 383cfa8 to ce83a6e Compare August 4, 2026 21:58
… 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>
@wengsht
wengsht force-pushed the fix-xautoclaim-pending-number branch from ce83a6e to 809bc37 Compare August 4, 2026 22:02
@wengsht

wengsht commented Aug 4, 2026

Copy link
Copy Markdown
Author

@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.

@wengsht wengsht changed the title fix(stream): decrement pending_number when XAUTOCLAIM removes deleted entries fix(stream): fix consumer-group counter overflows (XAUTOCLAIM pending_number, XINFO GROUPS lag) Aug 5, 2026
@jihuayu
jihuayu requested review from LindaSummer and a lite review from Copilot August 5, 2026 01:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 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 XAUTOCLAIM to decrement per-consumer and per-group pending_number when sweeping dangling PEL entries (trimmed/XDEL’d entries), using saturating subtraction to avoid wrap.
  • Fix XINFO GROUPS lag computation to avoid unsigned underflow when entries_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.

Comment thread tests/gocase/unit/type/stream/stream_test.go
@wengsht
wengsht force-pushed the fix-xautoclaim-pending-number branch from de3bb5f to 8d8ad67 Compare August 5, 2026 01:33
@git-hulk
git-hulk requested a review from torwig August 5, 2026 02:04
@git-hulk git-hulk changed the title fix(stream): fix consumer-group counter overflows (XAUTOCLAIM pending_number, XINFO GROUPS lag) fix(stream): consumer-group counter overflows (XAUTOCLAIM pending_number, XINFO GROUPS lag) Aug 5, 2026
@LindaSummer

Copy link
Copy Markdown
Member

Hi @wengsht ,

Thanks for your effort in this problem.
I will review this patch in recent days.

If you want to run a full CI by yourself, you could create a PR in your forked repo and enable its github action.
This will accelerate your development without pending by upstream CI gate.

@wengsht

wengsht commented Aug 5, 2026

Copy link
Copy Markdown
Author

Hi @wengsht ,

Thanks for your effort in this problem. I will review this patch in recent days.

If you want to run a full CI by yourself, you could create a PR in your forked repo and enable its github action. This will accelerate your development without pending by upstream CI gate.

Thanks for the tip!

Ran CI:
https://github.com/wengsht/kvrocks/pull/1/checks

@LindaSummer LindaSummer left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hi @wengsht ,

Thanks very much for your effort for these two issues.

I have several suggestions for this patch.

  1. 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.
  2. The fix for the entries-read has a case not aligning with Redis's behavior.

For the XAUTOCLAIM fix, it generally looks good to me.

Have a wonderful day!

Comment thread src/types/redis_stream.cc Outdated
// 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) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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) 3

Here 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) 3

The entries-read should be the entries_added if entries_read is greater than entries_added.

Here is redis implemetation.

https://github.com/redis/redis/blob/8f365337d48c8a264bc9f3381c7b8cc12ffb51d2/src/t_stream.c#L3663-L3665

@wengsht wengsht Aug 9, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

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

    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) 3

wengsht added 2 commits August 9, 2026 10:13
…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>
@wengsht
wengsht force-pushed the fix-xautoclaim-pending-number branch from b140b8b to cd37ad1 Compare August 9, 2026 17:13
@wengsht wengsht changed the title fix(stream): consumer-group counter overflows (XAUTOCLAIM pending_number, XINFO GROUPS lag) fix(stream): decrement pending_number when XAUTOCLAIM removes deleted PEL entries Aug 9, 2026
@wengsht
wengsht requested a review from LindaSummer August 9, 2026 18:16
@wengsht wengsht closed this Aug 9, 2026
@wengsht wengsht reopened this Aug 9, 2026
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.

3 participants