[Home](../README.md) | Prev: [@RedisHash](05-redis-hash.md) | Next: [Pub/Sub](07-pub-sub.md) # 6. Hash TTL and keyspace events `@RedisHash(value = "session", timeToLive = 2)` gives every saved [Session](../src/main/java/com/ankurm/redis/hash/Session.java) a two-second TTL. Redis will delete the hash when the time is up. The question is what happens to everything Spring Data Redis wrote *around* the hash: the id in the `session` set, the `session:user:ankur` index, the `session:s1:idx` bookkeeping set (see [chapter 5](05-redis-hash.md)). ## With Boot's defaults: nothing Boot enables Redis repositories with `enableKeyspaceEvents = OFF` ([16-serializers-javap.txt](output/16-serializers-javap.txt), "Defaults of @EnableRedisRepositories"). Redis expires the hash and nobody tells the application ([09-hash-ttl-events-off.txt](output/09-hash-ttl-events-off.txt)): ``` $ redis-cli -p 6390 KEYS '*' | sort session session:s1:idx session:user:ankur $ redis-cli -p 6390 SMEMBERS session | sort s1 ... sessions.findById("s1") = Optional.empty sessions.count() = 1 ``` `findById` is right (there is no hash), `count()` is wrong (the id is still in the set), and `session:user:ankur` still lists an id that resolves to nothing. On a busy system this accumulates. ## With ON_STARTUP: cleanup `@EnableRedisRepositories(enableKeyspaceEvents = ON_STARTUP)` changes three things ([10-hash-ttl-events-on.txt](output/10-hash-ttl-events-on.txt), test config in [HashTtlEventsOnTest](../src/test/java/com/ankurm/redis/HashTtlEventsOnTest.java)): 1. **The server setting.** Redis only publishes expiry notifications if `notify-keyspace-events` is set. It is empty by default, and Spring Data Redis sets it at startup (the annotation's `keyspaceNotificationsConfigParameter` defaults to `Ex`; Redis reports it back as `xE`): ``` $ redis-cli -p 6390 --no-raw CONFIG GET notify-keyspace-events 1) "notify-keyspace-events" 2) "xE" ``` Some managed Redis services restrict `CONFIG SET`; this repository does not run against one, so check yours. The `EnableKeyspaceEvents` enum also has an `ON_DEMAND` value, which is not exercised here. 2. **A phantom key.** Once a hash has expired there is nothing left to read, so Spring Data Redis writes a copy, `session:s1:phantom`, that outlives the hash by five minutes (302 seconds for a 2-second TTL). When the expiry notification arrives, Spring reads the phantom copy, removes the index entries, and deletes the phantom. 3. **An application event.** `RedisKeyExpiredEvent` is published with the deleted entity as its value, so an `@EventListener` can react (audit, cleanup, notify): ``` RedisKeyExpiredEvent: keyspace=session id=s1 value=Session[id=s1, user=ankur] ``` After it, `DBSIZE` is 0 and `count()` is 0. ## The catch The expiry notification is delivered over Pub/Sub, which is fire-and-forget: a subscriber that is not connected when the message is published never sees it ([chapter 7](07-pub-sub.md) shows that with a stopped listener). If your application is down when a session expires, or the connection drops, the cleanup for that entry is not guaranteed. This repository does not test a missed notification. If exactness matters, do not build correctness on keyspace events; run a periodic job. ## Going deeper - [HashTtlEventsOffTest](../src/test/java/com/ankurm/redis/HashTtlEventsOffTest.java) - [Redis keyspace notifications](https://redis.io/docs/latest/develop/pubsub/keyspace-notifications/) - [Spring Data Redis: Expirations and Time To Live](https://docs.spring.io/spring-data/redis/reference/redis/redis-repositories/expirations.html) [Home](../README.md) | Prev: [@RedisHash](05-redis-hash.md) | Next: [Pub/Sub](07-pub-sub.md)