Databases 11 min read

Redis Expiration Listener: Scenario Testing and Performance Analysis

This article examines the pitfalls of using Redis key‑expiration notifications for delayed business actions, presents a Docker‑based benchmark, provides Spring‑Boot code for scheduling and listening to expirations, and analyzes how notification latency grows as the number of keys increases.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
Redis Expiration Listener: Scenario Testing and Performance Analysis

Redis Expiration Listener Scenario

Many business requirements involve executing an action after a certain delay, such as closing an order 30 minutes after it is placed. Although numerous demos show how to use Redis expiration notifications for this purpose, the approach has a major drawback: Redis does not guarantee that a key will be deleted exactly at the configured time, which can lead to delayed notifications.

Test Environment

Redis runs inside a Docker container with a single CPU and 512 MB memory. The following benchmark is executed inside the container:

redis-benchmark -t set -r 100000 -n 1000000

The benchmark output shows the throughput and configuration details. Note that the benchmark threads should not run inside the Docker container; otherwise both the benchmark and Redis main thread each consume about 50 % CPU.

Test Code

The Java Spring‑Boot service creates keys every hour at minute 56, each set to expire after 8 seconds, and ensures all expirations occur before minute 55 of the next hour.

@Service
@Slf4j
public class RedisJob {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    public DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    public LocalDateTime end = LocalDateTime.of(LocalDate.of(2020,5,12), LocalTime.of(8,0));

    @Scheduled(cron = "0 56 * * * ?")
    public void initKeys() {
        LocalDateTime now = LocalDateTime.now();
        ValueOperations
operations = stringRedisTemplate.opsForValue();
        log.info("开始设置key");
        LocalDateTime begin = now.withMinute(0).withSecond(0).withNano(0);
        for (int i = 1; i < 17; i++) {
            setExpireKey(begin.plusHours(i), 8, operations);
        }
        log.info("设置完毕: " + Duration.between(now, LocalDateTime.now()));
    }

    private void setExpireKey(LocalDateTime expireTime, int step, ValueOperations
operations) {
        LocalDateTime localDateTime = LocalDateTime.now().withNano(0);
        String nowTime = dateTimeFormatter.format(localDateTime);
        while (expireTime.getMinute() < 55) {
            operations.set(nowTime + "@" + dateTimeFormatter.format(expireTime), "A",
                Duration.between(expireTime, LocalDateTime.now()).abs());
            expireTime = expireTime.plusSeconds(step);
        }
    }
}

The listener class extends KeyExpirationEventMessageListener and logs the expired key, the current database size, and the delay between the scheduled expiration time and the actual deletion time.

@Slf4j
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
    public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }
    public DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public void onMessage(Message message, byte[] pattern) {
        String keyName = new String(message.getBody());
        LocalDateTime parse = LocalDateTime.parse(keyName.split("@")[1], dateTimeFormatter);
        long seconds = Duration.between(parse, LocalDateTime.now()).getSeconds();
        stringRedisTemplate.execute((RedisCallback
) connection -> {
            Long size = connection.dbSize();
            log.info("过期key:" + keyName + " ,当前size:" + size + " ,滞后时间" + seconds);
            return null;
        });
    }
}

The Redis message listener container is configured with a thread pool of 100 threads to handle expiration events efficiently.

@Bean
public RedisMessageListenerContainer configRedisMessageListenerContainer(RedisConnectionFactory connectionFactory) {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(100);
    executor.setMaxPoolSize(100);
    executor.setQueueCapacity(100);
    executor.setKeepAliveSeconds(3600);
    executor.setThreadNamePrefix("redis");
    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    executor.initialize();
    RedisMessageListenerContainer container = new RedisMessageListenerContainer();
    container.setConnectionFactory(connectionFactory);
    container.setTaskExecutor(executor);
    return container;
}

Results

When the number of keys is below 10 000, expiration notifications are typically delivered within 10 seconds. With around 30 000 keys, some notifications are delayed up to 120 seconds. At 50 000 keys, most notifications lag by two minutes, which is unacceptable for most business scenarios.

Sample log excerpt (key count ≈ 57 000):

2020-05-13 22:16:48.383  : 过期key:2020-05-13 11:56:02@2020-05-13 22:14:08 ,当前size:57405 ,滞后时间160
2020-05-13 22:16:49.389  : 过期key:2020-05-13 11:56:02@2020-05-13 22:14:32 ,当前size:57404 ,滞后时间137
... (more lines omitted) ...

Conclusion

Redis generates expiration events only when the server actually deletes the key, not when the TTL reaches zero, as documented in "How Redis expires keys" and "Timing of expired events". Therefore, relying on expiration notifications for precise timing can be risky. Developers should read the official documentation, understand the limitations, and consider alternative scheduling mechanisms for critical business logic.

Reference: How Redis expires keys
Reference: Timing of expired events
JavaDatabaseRedisperformance testingSpring Bootthread poolKey Expiration
Java Architect Essentials
Written by

Java Architect Essentials

Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.