Skip to content

SET ... KEEPTTL loses the TTL on WAL replay / standby (command is marked IsOverwrite) #504

@liunyl

Description

@liunyl

SetCommand::IsOverwrite() returns true unconditionally, including for SET key val KEEPTTL. Overwrite commands are pruned in the log record and replayed with ignore_previous_version (the object is rebuilt from scratch rather than mutated in place), so on recovery / standby-forward the kept TTL is dropped and the key becomes immortal.

Evidence

  • include/redis_command.h:1379-1382SetCommand::IsOverwrite() returns true (no exception for KEEPTTL).
  • Overwrite pruning + fresh-object rebuild on replay: data_substrate/tx_service/include/cc/cc_entry.h (overwrite handling, ignore_previous_version).

The live execution path (CommitOn) reads the existing object to preserve its TTL when KEEPTTL is set, but a replayed/forwarded SET … KEEPTTL rebuilds the object with no prior version available → TTL lost.

Repro

SET k v EX 100; SET k v2 KEEPTTL; then crash + replay (or observe on a standby) → TTL k returns -1 (no expiry) instead of the remaining ~100s.

Fix: SET … KEEPTTL must not be treated as a full overwrite (it depends on prior state), or the replay/forward image must carry the resolved absolute TTL.


Found during a code audit (docs PR #492). IsOverwrite()==true verified; the replay-rebuild interaction is the documented overwrite-pruning behavior.

🤖 Found with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions