@Indexed secondary index keys are not expired when @RedisHash(timeToLive=…) entity expires
Opened this issue · 1 comments
Description
When a Redis hash entity is annotated with @RedisHash(timeToLive = …) and includes fields marked with @indexed, Spring Data Redis correctly sets a TTL on the primary hash key (e.g., Session:123). Upon expiration, the primary hash is removed automatically.
However, the secondary index sets (e.g., Session:userId:456) are never assigned a TTL and remain in Redis indefinitely, even after the associated entity has expired.
Current Behavior
@RedisHash(timeToLive = 3600)
public class Session {
@Id String id;
@Indexed String userId;
}
Generated Redis commands (on save):
HSET Session:123 id "123" userId "456"
EXPIRE Session:123 3600
SADD Session:userId:456 "123"
# ← No EXPIRE on the index key!
After 1 hour:
- Session:123 → deleted
- Session:userId:456 → still exists (stale index entry)
Impact
- Stale index data: Index points to non-existent entities.
- Unbounded memory growth: For high-churn, short-lived entities (e.g., user sessions, auth tokens), index sets accumulate forever.
- Operational burden: Requires manual cleanup or external expiration logic.
Proposed Solution
Extend @indexed with an optional flag:
@Indexed(expireWithEntity = boolean, default = false)
When expireWithEntity = true, Spring Data Redis should:
- Apply the same TTL as the entity to each generated index set key upon save.
- Optionally clean up empty index sets on expiration (if feasible).
Example:
@RedisHash(timeToLive = 3600)
public class Session {
@Id private String id;
@Indexed(expireWithEntity = true)
private String userId;
}
Expected Redis commands:
HSET Session:123 ...
EXPIRE Session:123 3600
SADD Session:userId:456 "123"
EXPIRE Session:userId:456 3600 # ← Now expires with entity
Backward Compatibility
- Default value: expireWithEntity = false
- Existing applications unchanged
- Opt-in only for use cases needing index TTL synchronization
Expiring the secondary index also affects other objects that might have a different (or no) TTL at all. Expiration removes the index and it would render the index broken for other objects, we cannot do that.
There's no way to expire individual items in a set, on the other side, while there is Hash Field expiry, there's no way to concatenate entries in a SUNION/SINTERSECT style that is needed for querying indexed items.