exposed-workshop / Visual Companion

Exposed + Local Cache + Redis

The cache strategy determines when Redis and the database reflect a read or write

Caching is not only a latency optimization. It defines where a value is read, when a change reaches the database, and how each layer converges after invalidation or failure.

L1 / Local cacheCaffeine or Redisson local cache
L2 / Remote cacheRedis
Database accessExposed
Rename user: Ada → GraceStrategy simulation
API Cache DB
L1 / Local cacheCaffeine / Redisson Local
miss
L2 / Remote cacheRedis
user:42 = Ada
Persistent / DBExposed Table
users[42] = Ada
    A miss loads the database value into both cache layers.Read complete

    Cache strategies

    JdbcCacheRepository implementations separate cache reads and database persistence into Read-Through, Write-Through, Read-Only, and Write-Behind strategies. Select one from the update frequency and acceptable persistence delay of each data type.

    Read-Through

    On a cache miss, EntityMapLoader queries through Exposed and fills Redis and the local cache.

    • Reduces repeated reads
    • First read includes DB latency

    Write-Through

    EntityMapWriter updates the cache and database in the request path.

    • DB persistence confirmed before response
    • Higher write latency

    Read-Only

    Cache stable data, invalidate it on change, and reload it on the next read.

    • No unnecessary write path
    • Invalidation must not be missed

    Write-Behind

    Accept the request, write Redis first, then let a batch persist through Exposed.

    • Lower response latency
    • Acceptance is not DB completion

    Cache strategy architecture

    The diagram connects request handling, repository strategy, storage, and verification to show where each strategy reads data and when it persists changes to the database.

    Cache strategy architecture across request runtime, repository strategy, storage, and verification
    Cache strategy path from request to persistence UserCacheRepository, UserCredentialsCacheRepository, and UserEventCacheRepository select Read/Write-Through, Read-Only, and Write-Behind before delegating to the shared Redisson repository and Exposed database path. Open the full-size diagram

    Implementing cache strategies with Exposed and Redis

    Near Cache consists of a local cache at L1 and Redis at L2. The Lettuce implementation uses Caffeine at L1, while the Redisson implementation uses the local cache managed by RLocalCachedMap. Exposed is not part of Near Cache; it is the database access layer invoked for cache misses and writes.

    Select the repository strategyUserCacheRepository uses READ_WRITE_THROUGH_WITH_NEAR_CACHE.
    Select the Redis client and local cacheRedisson uses RLocalCachedMap; selected Lettuce suspend paths use LettuceSuspendNearCache with Caffeine.
    Connect database readsEntityMapLoader executes an Exposed SELECT on a miss.
    Connect database writesEntityMapWriter runs the Exposed transaction for Write-Through or a Write-Behind batch.
    Review invalidation semanticsdeleteFromDBOnInvalidate determines whether invalidation also deletes the database row.
    11-high-performance / Redisson strategies
    UserCacheRepository
      READ_WRITE_THROUGH_WITH_NEAR_CACHE
    
    UserCredentialsCacheRepository
      READ_ONLY_WITH_NEAR_CACHE
    
    UserEventCacheRepository
      WRITE_BEHIND_WITH_NEAR_CACHE
    
    HTTP → JdbcCacheRepository
         → RLocalCachedMap
         → EntityMapLoader / EntityMapWriter
         → Exposed transaction → DB
    Redisson Near Cache
    Redisson local cache (L1)
    + Redis RLocalCachedMap (L2)
    + AbstractJdbcRedissonRepository
    + Exposed DB loader / writer
    Lettuce Near Cache
    Caffeine (L1)
    + Redis via Lettuce (L2)
    + LettuceSuspendNearCache
    + AbstractSuspendedJdbcLettuceRepository
    + Exposed DB loader / writer
    09-spring / Lettuce Redis cache
    LettuceCacheConfig
    + RedisCacheManager
    + Exposed CountryRepository
    
    + LettuceSuspendedCache
    + CachedCountrySuspendedRepository
    + Exposed suspended repository
    
    + No local L1 cache

    Measure Near Cache with Redis traffic and convergence delay

    Repeated reads in one JVM can finish in the local cache. The system still needs an explicit limit for stale local values, reconnection behavior, and writes that Redis has accepted but the database has not persisted.

    Temporarily stale local values

    Delayed Redisson invalidation messages or Lettuce RESP3 tracking notifications can leave JVM-local caches with different values.

    Write-Behind loss window

    A failure after the Redis write and before the database batch can lose a change that has not reached durable storage. Never label request acceptance as DB persistence.

    Invalidation versus deletion

    Review deleteFromDBOnInvalidate before calling invalidate. Separate cache eviction from business deletion.

    Benchmark chart comparing read-heavy and write-heavy average time for NoCache, ReadThrough, and WriteThrough
    PostgreSQL + HikariCP smoke benchmark CacheStrategyComparisonBenchmark compares NoCache, ReadThrough, and WriteThrough against a real PostgreSQL Testcontainer through HikariCP. Values are average time in us/op; lower is better. Open the full-size chart
    Strategy / PayloadREAD_HEAVYWRITE_HEAVY
    NoCache / 256B547.7 us/op505.3 us/op
    ReadThrough / 256B87.5 us/op478.6 us/op
    WriteThrough / 256B50.5 us/op448.6 us/op
    NoCache / 4KB482.6 us/op529.4 us/op
    ReadThrough / 4KB95.9 us/op497.9 us/op
    WriteThrough / 4KB55.9 us/op491.9 us/op

    Scope: READ_HEAVY uses 90% reads and 10% writes; WRITE_HEAVY uses 10% reads and 90% writes. This result does not measure Redis Near Cache, Write-Behind, production throughput, or an SLA.

    Run the examples to inspect persistence timing and Lettuce integration

    Testcontainers starts Redis. Modules 01 and 02 verify Redisson-backed persistence strategies. Module 06 combines Spring Cache with Lettuce, while module 07 applies LettuceSuspendedCache to an Exposed repository. Modules 06 and 07 are Redis-only cache examples without a local L1 cache.

    Redisson commands
    # Redisson + Exposed strategies
    ./gradlew :01-cache-strategies:test
    ./gradlew :01-cache-strategies:bootRun
    ./gradlew :02-cache-strategies-coroutines:test
    Lettuce example commands
    # Spring Cache + Lettuce + Exposed
    ./gradlew :06-spring-cache:test
    
    # LettuceSuspendedCache + Exposed
    ./gradlew :07-spring-suspended-cache:test