Add CLAIM parameter to XREADGROUP for automatic pending entry claiming#14402
Merged
sundb merged 49 commits intoredis:unstablefrom Oct 21, 2025
Merged
Add CLAIM parameter to XREADGROUP for automatic pending entry claiming#14402sundb merged 49 commits intoredis:unstablefrom
sundb merged 49 commits intoredis:unstablefrom
Conversation
|
Hi, I’m Jit, a friendly security platform designed to help developers build secure applications from day zero with an MVS (Minimal viable security) mindset. In case there are security findings, they will be communicated to you as a comment inside the PR. Hope you’ll enjoy using Jit. Questions? Comments? Want to learn more? Get in touch with us. |
sundb
reviewed
Oct 17, 2025
sundb
reviewed
Oct 17, 2025
sundb
reviewed
Oct 17, 2025
sundb
reviewed
Oct 17, 2025
sundb
reviewed
Oct 18, 2025
sundb
reviewed
Oct 18, 2025
sundb
reviewed
Oct 19, 2025
sundb
reviewed
Oct 20, 2025
sundb
approved these changes
Oct 20, 2025
sundb
reviewed
Oct 21, 2025
Collaborator
Collaborator
Author
|
It looks like there is a problem with downloading repository metadata for CentOS. I will rerun it after some time. |
3 tasks
sundb
added a commit
that referenced
this pull request
Nov 7, 2025
The added logic from #14402 introduced overhead to the XREADGROUP even when the added feature is not used. This PR tries to mitigate it, by removing unnecessary streamEncodeID() calls and redundant byte-swapping operations from the stream iterator hot path. By comparing stream IDs directly in native-endian form, we eliminate repeated encoding and memcmp() calls that were responsible for a significant portion of total CPU time during stream iteration. Additionally, endian conversion helpers are modernized to leverage compiler-provided intrinsics (__builtin_bswap*) for single-instruction byte-swaps on supported compilers. --------- Co-authored-by: debing.sun <debing.sun@redis.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
This PR enhances Redis Streams consumer groups by adding an optional CLAIM parameter to the
XREADGROUPcommand, enabling automatic claiming of idle pending entries alongside normal message consumption in a single operation.Problem Statement
Current Redis Streams consumer group implementations require developers to manually orchestrate multiple commands to handle both pending and new entries:
XPENDINGto discover idle pending entriesXCLAIM/XAUTOCLAIMto claim idle entriesXREADGROUPto consume new entriesThis multi-command approach creates:
Solution
Extends XREADGROUP with a new optional CLAIM parameter:
XREADGROUP GROUP group consumer [COUNT count] [BLOCK milliseconds] [NOACK] [CLAIM min-idle-time] STREAMS key [key ...] id [id ...]When CLAIM min-idle-time is specified, the command operates in two phases:
Response Format Changes
When the CLAIM option is used, the response format is extended to include delivery metadata for each entry:
Standard XREADGROUP response (without CLAIM):
XREADGROUP response with CLAIM:
Response structure with CLAIM:
Field 1: Stream entry ID (unchanged)
Field 2: Field-value pairs (unchanged)
Field 3: Idle time in milliseconds - the number of milliseconds elapsed since this entry was last delivered to a consumer
Field 4: Delivery count - the number of times this entry has been delivered:
0for new messages that haven't been delivered before1+for claimed messages (previously unacknowledged entries)Purpose of the new fields:
These fields enable intelligent client-side processing decisions:
Together, these fields provide the visibility needed to build robust, self-healing consumer systems without requiring additional XPENDING queries.
Note: If the ID parameter is not
>, the command returns entries that are pending for the consumer, and the CLAIM option is ignored. In this case, the response follows the standard format without the additional delivery metadata fields.Key Benefits
Impact on Existing Commands
The XCLAIM and XAUTOCLAIM commands may potentially benefit from the new pel_by_time index for improved performance, such optimizations require further investigation and testing. Enhancements to XCLAIM and XAUTOCLAIM are postponed for future work.
Performance Benchmarks
Latency Performance
Comprehensive performance testing demonstrates significant improvements over the traditional XAUTOCLAIM approach:
Test Methodology
Two identical test scenarios were executed to compare XAUTOCLAIM against XREADGROUP with CLAIM:
Test Setup:
Test 1 - XAUTOCLAIM (Traditional Approach):
Test 2 - XREADGROUP with CLAIM (New Approach):
Performance Analysis
The new XREADGROUP CLAIM implementation delivers 22.5x faster average performance compared to XAUTOCLAIM:
This performance improvement is achieved through the time-ordered PEL index (pel_by_time), which enables O(log n + k) retrieval of idle entries versus XAUTOCLAIM's less efficient scanning approach.
Memory Performance
To evaluate the memory overhead of the pel_by_time index, comprehensive memory testing was conducted comparing Redis with and without the index under realistic workload conditions.
Test Methodology:
Test Results - Without pel_by_time Index:
Test Results - With pel_by_time Index:
Memory Performance Analysis:
The pel_by_time index introduces a measurable but reasonable memory overhead:
Used Memory Impact:
Per-Entry Memory Breakdown:
The theoretical minimum for the pel_by_time index is 32 bytes per entry (composite key only, no node values). The observed 18.6 bytes per entry overhead is lower than the theoretical maximum, suggesting effective rax tree compression is occurring despite the 5ms delays between reads.
Technical Implementation
New Data Structure: Time-Ordered PEL Index (
pel_by_time)To efficiently identify and claim idle pending entries, this PR introduces a new rax tree structure to the consumer group implementation:
Structure Design:
delivery_time(timestamp when entry was last delivered)streamId(stream entry ID)Key Format:
delivery_time+streamId(concatenated)Node Value: None - all necessary information is encoded in the key itself for memory efficiency
Key Properties:
Uniqueness Guarantee: While multiple pending entries may share the same
delivery_time, thestreamIdcomponent ensures each key is globally unique within the tree.Lexicographical Ordering: The rax tree naturally orders nodes lexicographically by key. Since
delivery_timeforms the prefix of each key, entries are automatically sorted by delivery time, with oldest entries appearing first in the tree.Efficient Range Operations: This time-based ordering enables highly efficient range searches. To find all entries idle for at least
min-idle-timemilliseconds, we simply perform a range query from the tree's beginning up tocurrent_time - min-idle-time.Fast Retrieval:
Once idle entries are identified via the
pel_by_timeindex, the embeddedstreamIdin each key is used to quickly retrieve the full pending message data structure for the subsequentXREADGROUPclaim operation.Performance Characteristics:
This dual-index approach (existing PEL structures plus the new time-ordered index) allows XREADGROUP with CLAIM to efficiently identify claimable entries without scanning the entire PEL, making the operation suitable for consumer groups with large pending entry lists.
COUNT Behavior with CLAIM
When the
COUNToption is used in conjunction withCLAIM, the command follows a two-phase execution strategy to maximize the specified count limit:Phase 1: Claim Idle Pending Entries
Phase 2: Fetch New Messages (if needed)
COUNTlimit has not been satisfied by claimed pending entries, the command proceeds to read new messages from the streamremaining_count = COUNT - claimed_entriesThis prioritization ensures that idle pending entries are always processed first, preventing indefinite message stalling while still allowing consumers to process new messages efficiently when pending entries are scarce.
BLOCK Behavior with CLAIM
When the CLAIM option is used in conjunction with the BLOCK option, the command exhibits sophisticated blocking behavior that responds to both new messages and pending entries becoming claimable:
Blocking State Management:
If there are no immediately claimable pending entries and no new messages available in the stream, the
XREADGROUPcommand enters a blocking state for the specified duration. However, the implementation must handle a critical scenario: pending entries that become idle (and thus claimable) while the command is blocked must trigger an early wakeup to serve those entries.Implementation:
stream_claim_pending_keysDictionaryTo enable this reactive blocking behavior, a new
stream_claim_pending_keysdictionary is introduced to theredisDbstructure:Multi-Client Coordination:
When multiple XREADGROUP commands with BLOCK and CLAIM are executed concurrently on the same stream, the dictionary value stores the shortest claimable time across all waiting clients. This ensures the earliest possible wakeup when any pending entry becomes available for claiming.
Wakeup Mechanism:
handleClaimableStreamEntriesThe
handleClaimableStreamEntriesfunction is invoked regularly fromblockedBeforeSleepto monitor and react to claimable entries:stream_claim_pending_keysdictionaryclaimable_time ≤ current_time, callssignalKeyAsReadyto wake up all clients blocked on that streamResource Contention Handling:
When the number of claimable entries is insufficient to satisfy all awakened clients:
stream_claim_pending_keysdictionary with the new timestampThis design ensures fair resource distribution and prevents busy-waiting while maintaining responsiveness to both new messages and aging pending entries.