February 28, 2022·5 min read·engineeringvalkeyredisstreamsmonitoring

Redis consumer-group lag counts delivery rather than completion

Redis 7.0 RC2 adds a group lag metric based on logical delivery counters. It reports undelivered work separately from pending acknowledgements and returns null when it cannot establish a meaningful count.

A Redis stream entry can be waiting for its first delivery to a consumer group or waiting for acknowledgement after delivery. Those are different states of work. Redis 7.0 RC2 adds consumer-group lag reporting for the first, giving operators a way to distinguish a group falling behind the stream from consumers holding work they have already received.

The February 28 candidate's release notes introduce lag tracking and additional XINFO fields. They also add ENTRIESREAD to XGROUP CREATE and XGROUP SETID. The implementation needs that logical position because a stream ID is an identifier, not an ordinal count that can always be subtracted from another ID.

I like Redis's explicit data structures, but monitoring them requires equally explicit definitions. A single queue-depth number is insufficient when delivery and completion are separate operations. The new metric helps only if its meaning survives the journey into a dashboard or alert.

The implementation counts entries yet to be delivered

The RC2 stream implementation defines group lag as the number of messages yet to be delivered. Its calculation uses the stream's entries_added and the group's entries_read. When the logical read counter is valid and there is no relevant fragmentation ahead, lag is the difference between those counters.

This calculation is different from the pending-entry list. Delivery advances the group's position, while acknowledgement determines whether an already delivered entry remains pending. An entry can therefore stop contributing to lag before the application finishes processing it. A group with no undelivered entries can still have pending work.

That distinction has an immediate operational use. Rising lag indicates that group delivery is not keeping up with additions. Pending entries describe responsibility for work already assigned. The two observations can lead to different questions about the consumer application. Combining them into one number would remove information that the stream model deliberately retains.

The counters also explain why current stream length is not sufficient. Entries can leave the stream after being added. The implementation tracks the number added over its history separately from the entries currently present. Group position needs to be expressed in the same logical history if subtraction is to mean anything.

Deletion can make the count unavailable

The implementation does not invent a lag value when it cannot establish one. streamReplyWithCGLag returns null if the counter cannot be obtained reliably. Its checks account for tombstones and the group's position relative to the current stream boundaries.

The helper streamEstimateDistanceFromFirstEverEntry attempts to recover a logical counter from an ID. Some positions are recognizable. An ID equal to the stream's last ID can use the total entries-added count. A position at the first current entry can be related to the retained length when the relevant interval has no deletions. Arbitrary positions in the middle do not necessarily provide enough information.

This is why null needs a different interpretation from zero. Zero is a known result indicating no undelivered work under the metric's definition. Null says that Redis cannot currently supply that count. A monitoring pipeline that substitutes zero would claim the group is caught up precisely when the source has declined to make that claim.

The behavior also avoids scanning the entire retained stream merely to produce a convenient-looking integer. The calculation relies on maintained metadata and recognized boundary cases. Its limitations are part of the interface, not an indication that every unknown value is a server malfunction.

Administrative position changes need logical metadata

The new ENTRIESREAD argument gives group creation and position changes a way to specify the associated counter. XSETID also gains metadata arguments for entries added and the maximum deleted ID. Those additions reflect the same requirement. Restoring or adjusting a position needs more context when Redis is expected to report its distance through the stream's history.

For a small stream created for this example, the following sequence demonstrates the distinction between lag and pending work. The fixed IDs make the order explicit.

XADD lag-demo 1-0 body first
XADD lag-demo 2-0 body second
XGROUP CREATE lag-demo workers 0
XINFO GROUPS lag-demo
XREADGROUP GROUP workers consumer-a COUNT 1 STREAMS lag-demo >
XINFO GROUPS lag-demo
XPENDING lag-demo workers

Before delivery, both entries are available to the group. Reading one new entry advances delivery and adds that entry to the pending list. XPENDING reports pending work, while XINFO GROUPS exposes the group's lag and logical position. Acknowledgement is a subsequent action, so it should not be inferred merely from the delivery call returning an entry.

The example also separates two administrative observations that an application might otherwise conflate. A group can continue receiving new entries while earlier deliveries remain pending. Conversely, a consumer that stops requesting new entries can leave the group behind even if it has acknowledged everything it previously received. Neither condition can be identified from stream length alone.

A dashboard can therefore retain lag and pending counts as distinct measurements, together with whether lag is known. This does not require inventing a combined backlog formula. Summing fields without stating their meaning would hide the difference between entries awaiting assignment and entries already assigned. The server now supplies information about both stages, and the application supplies the definition of completion that makes an acknowledgement appropriate.

Deleted pending entries get explicit handling too

RC2 also changes XCLAIM and XAUTOCLAIM to skip deleted entries and remove them from the pending-entry list. XAUTOCLAIM returns an additional element containing deleted IDs. This connects cleanup of delivery bookkeeping to the fact that a stream entry may disappear independently of its pending record.

A client parsing the claim reply needs to account for that new element. The release notes list the behavior as a potentially breaking change, which is appropriate for a command that applications may decode positionally. Receiving fewer live entries does not necessarily mean that the command did no useful bookkeeping work.

I prefer an unavailable lag value to a guessed one when deletion has removed the information needed for an exact interpretation. The candidate gives monitoring a clear distinction between known lag and unavailable lag, while its pending-entry behavior describes work already delivered. Preserving those meanings lets an operator observe consumer progress without confusing it with application completion.