Skip to content

Cache eviction with Lettuce does not support async / reactive eviction #3236

@cgual-omnidian

Description

@cgual-omnidian

I noticed this issue when using Spring Boot Webflux (v3.4.10), Spring Framework (v6.2.11) and Spring Data Redis (v3.4.10).
Our service is using a Redis cache with support provided by Lettuce (v6.4.2.RELEASE).

The service uses the @Cacheable and @CacheEvict annotations with reactive support:

    @Cacheable(RedisCacheConfig.IDs.ATTRIBUTES_CACHE)
    fun getAttributes(id: Identifier): Mono<List<Attribute>> =
        attributesService.getAttributesById(id)
           
    @CacheEvict(RedisCacheConfig.IDs.ATTRIBUTES_CACHE)
    fun evictAttributes(id: Identifier): Mono<Unit> = Mono.empty()

Calling code was evicting some keys if they happened to return an empty list:

   idList.associateWith { id ->
      attributesCache.getAttributes(id)
          .flatMap { attributes ->
                    // evict cached attributes from redis if the attributes list is empty
                    if (attributes.isEmpty()) {
                        attributesCache
                            .evictRelevantAssetAttributes(id)
                            .then(Mono.just(attributes))
                    } else {
                        Mono.just(attributes)
                    }
          }
          .awaitSingle()
   }

We underestimated the number of times that we would be evicting cache entries and the service eventually became unresponsive and started logging errors due to 1 minute cache command timeouts (for get, store & evictions).

Digging into the problem we noticed that the number of blocking JVM threads spiked during this time. Using a debugger it is possible to trace the eviction flow and it looks like there is a blocking operation going on during cache eviction.

The Spring Framework is interacting through Spring Data Redis via the Cache interface which notes in the JavaDocs for the evict method:

If the cache is supposed to be compatible with CompletableFuture and reactive interactions, the evict operation needs to be effectively non-blocking, with any backend write-through happening asynchronously.

It looks like using Spring Data Redis with Lettuce supports async/reactive retrieve & store operations via the AsynchronousCacheWriterDelegate class.

However, it also looks like calls to evict a key use the synchronous cache writer via the DefaultRedisCacheWriter which blocks when invoking the del command via LettuceKeyCommands in LettuceConnection.

Is there a version / configuration of Spring Data Redis that supports async cache evictions? or is that functionality missing?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions