Fix IDMP retention for future stream IDs by knowrohit · Pull Request #15988 · redis/redis · GitHub
Skip to content

Fix IDMP retention for future stream IDs - #15988

Open
knowrohit wants to merge 1 commit into
redis:unstablefrom
knowrohit:fix-idmp-retention-15836
Open

knowrohit wants to merge 1 commit into
redis:unstablefrom
knowrohit:fix-idmp-retention-15836

Conversation

@knowrohit

@knowrohit knowrohit commented Oct 15, 2026 •

Copy link
Copy Markdown

Summary

IDMP expiration currently derives entry age from the stream ID millisecond component. An explicit future-dated stream ID can therefore retain the IDMP mapping indefinitely.

This change stores a separate insertion timestamp for each IDMP entry and uses it for expiration and persistence filtering. The timestamp is preserved across RDB loading, AOF rewrite/replay, and stream COPY.

Testing

  • make -j2
  • Direct Redis regression: future-dated stream ID expires according to insertion time and a retry receives a new ID.
  • Direct RDB reload validation.
  • Direct AOF rewrite/reload validation.
  • Focused Tcl test runner attempted, but the local harness repeatedly reports available ports as busy; direct Tcl socket probes and direct Redis checks work.

The patch is based on current unstable.


Note

Medium Risk
Changes stream IDMP TTL semantics and on-disk/AOF formats (new RDB type and extended XIDMPRECORD); backward-compatible load paths exist but persistence and replication behavior shift for future-dated IDs.

Overview
IDMP entries now expire by insertion time, not by the stream ID’s millisecond field, so future-dated message IDs can’t keep dedup mappings alive past IDMP-DURATION.

Each idmpEntry gains recorded_ms, set on XADD with IDMP to server.mstime and copied on stream duplication. Expiration, RDB save filtering, and background cleanup all compare recorded_ms to the TTL window instead of id.ms.

Persistence: new RDB stream type STREAM_LISTPACKS_6 stores recorded_ms per IDMP entry; older stream RDB types load without it (defaulting to current time). AOF rewrite emits an optional sixth argument on internal XIDMPRECORD so replay restores the timestamp. Command metadata moves to arity -5 with optional recorded-ms.

A Tcl test asserts that a future-dated stream ID still lets IDMP state expire after the configured duration.

Reviewed by Cursor Bugbot for commit ac7ebfb. Bugbot is set up for automated code reviews on this repo. Configure here.

@CLAassistant

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

Reviewed by Cursor Bugbot for commit ac7ebfb. Configure here.

Comment thread src/t_stream.c

/* Create entry for lookup and potential insertion */
entry = idmpEntryCreate(iid_str, iid_len, &s->alloc_size);
entry->recorded_ms = server.mstime;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

IDMP timestamp not replicated

High Severity

XADD with IDMP stores recorded_ms from the local clock and never propagates that timestamp. Replicas apply the same command later and stamp a later time, so IDMP expiry diverges by replication lag. After the primary drops a mapping, a retry of the same IID can append a new stream entry on the primary while the replica still treats it as a duplicate and skips it, silently diverging stream contents. RDB, AOF rewrite, and COPY preserve recorded_ms, but live replication does not.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ac7ebfb. Configure here.

Comment thread src/t_stream.c
while (producer->idmp_head != NULL) {
idmpEntry *entry = producer->idmp_head;
if (entry->id.ms <= expire_time) {
if (entry->recorded_ms <= expire_time) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

IDMP expiry assumes sorted timestamps

Medium Severity

Expiry and RDB filtering still only drop a prefix of the IDMP linked list and assume recorded_ms never decreases along it. Inserts always append, while recorded_ms is wall-clock time and XIDMPRECORD can supply an older stamp. A later entry with an earlier recorded_ms is then skipped until the head expires, so mappings can outlive IDMP-DURATION. Stream IDs used to keep this list ordered even when the clock stepped backward; recorded_ms does not.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ac7ebfb. Configure here.

@fcostaoliveira

Copy link
Copy Markdown
Collaborator

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