Skip to main content

Cache — Valkey / Spring Data Redis

Model City caches read-heavy master data in a distributed cache to keep hot reads off PostgreSQL. The cache is off by default and behaves identically in both topologies.

Stack​

LayerTechnology
Cache serverValkey (Redis-protocol compatible)
Java clientLettuce (managed by Spring Boot)
Spring dependencyspring-boot-starter-data-redis
Value serializationGenericJacksonJsonRedisSerializer (Jackson 3, default typing)
Extra modulePageJacksonModule — round-trips Page<T> / PageImpl
Why the standard Redis starter (not the Valkey fork)

The Valkey Boot starter is a Boot 3.5 fork whose auto-configuration fails under Boot 4. Valkey is protocol-compatible with Redis, so the standard Redis starter works unchanged.

Global toggle​

The whole cache is enabled or disabled with a single property:

modelcity:
cache:
enabled: ${CACHE_ENABLED:false} # false by default

ModelCityCacheAutoConfiguration (in model-city-commons) is @ConditionalOnProperty(prefix = "modelcity.cache", name = "enabled", havingValue = "true") and @ConditionalOnClass(RedisConnectionFactory.class). When disabled, no CacheManager is registered, so every @Cacheable / @CacheEvict annotation across the modules is inert and each call hits the database. The Redis health indicator is bound to the same flag so a service deployed without Valkey does not report DOWN.

Connection​

Configured under the standard Spring Boot namespace spring.data.redis.*:

VariableDefaultPurpose
CACHE_ENABLEDfalseEnables the cache and the Redis health indicator.
CACHE_DEFAULT_TTL30mDefault TTL for caches without a group.
VALKEY_HOSTlocalhostValkey host.
VALKEY_PORT6379Port.
VALKEY_USERNAME / VALKEY_PASSWORD(empty)Credentials (if ACL is enabled).
VALKEY_DATABASE0Redis database index.
VALKEY_TIMEOUT2sConnection/command timeout.

Cache names and TTLs​

The canonical cache names live in CacheNames; the TTL groups are defined in ModelCityCacheAutoConfiguration and each can be overridden with modelcity.cache.ttls.<cacheName>=<duration>.

TTL groupDurationCaches
Long60 mincityPlace, cityPlaces, cityRoute, cityRoutes, cityRoutePlaces, publicSpace, publicSpaces, reservableResources, userCars, sanction, userSanctions
Medium15 minpublicQuestion, publicQuestions, userProfile, userList, citizenExists
Short5 minevent, events, securityAlerts

Keys are per language​

Every cache that serves localizable content prefixes the resolved language code to the key, so es/en/fr versions never mix — e.g. cityPlace::en-1, events::en-MUSIC-true-0, userProfile::en-auth0|…. Writes invalidate all languages of an entity (detail evicts use allEntries=true, not key="#id"). See Internationalization.

Where the annotations live​

@Cacheable / @CacheEvict are declared on the use cases (@Service), not the controllers, and the pattern is symmetric across topologies. Representative examples:

Use caseAnnotation
GetCityPlaceUseCase@Cacheable(cityPlace, key="#id")
GetCityPlacesUseCase@Cacheable(cityPlaces, key="#category + '-' + #page")
CreateCityPlaceUseCase@CacheEvict(cityPlaces, allEntries=true) + @CacheEvict(cityRoutePlaces, allEntries=true)
GetUserUseCase@Cacheable(userProfile, key="#targetUserId")
RegisterUserUseCaseevicts citizenExists, userProfile, userList
Overrides must re-declare caching

@Cacheable/@Transactional live on the platform default implementation. A city that overrides a use case and wants the same caching must re-declare those annotations — annotations are not inherited across separate implementations. See the Extensibility Guide and Disable caching for a use case.

Resilience​

The cache manager installs a ResilientCacheErrorHandler:

  • A cache GET error (e.g. a stale entry written by a different serializer version) is logged, the bad key is evicted, and the call falls through to the real method.
  • PUT / EVICT / CLEAR errors are only logged, so a transient Valkey failure never breaks a write path.

Null values are not cached (disableCachingNullValues), and evicts are transaction-aware (transactionAware), so they participate in the surrounding Spring transaction.

Never cached​

Excluded on purpose for security or high volatility: operation-authorizations/* (OTP), reservations/* and street-reservations/* (live availability), event-tickets/* / citizen-tickets/* (live stock/history), certificate-verifications (mTLS), and the Stripe webhooks (non-idempotent payment effects).