Skip to content

Architecture Overview

CoCache is a Level 2 Distributed Coherence Cache Framework for Java/Kotlin. It implements a two-level caching architecture that combines a fast local in-memory cache (L2) with a shared distributed cache (L1) and an upstream data source (L0). Cache coherence across application instances is maintained through an event bus that publishes CacheEvictedEvent messages whenever cache entries are modified.

Module Dependency Graph

The project is organized into 10 Gradle submodules, each with a clear responsibility:

mermaid
graph TD
    subgraph sg_10 ["Module Dependencies"]

        api["cocache-api<br>Core interfaces"]
        core["cocache-core<br>Default implementations"]
        spring["cocache-spring<br>Spring integration"]
        springCache["cocache-spring-cache<br>Spring Cache bridge"]
        springRedis["cocache-spring-redis<br>Redis implementation"]
        springBoot["cocache-spring-boot-starter<br>Auto-configuration"]
        test["cocache-test<br>Shared test specs"]
        bom["cocache-bom<br>Bill of Materials"]
        deps["cocache-dependencies<br>Version catalog"]
        example["cocache-example<br>Demo application"]
    end

    core --> api
    spring --> core
    springCache --> core
    springRedis --> core
    springRedis --> spring
    springBoot --> spring
    springBoot --> springCache
    springBoot --> springRedis
    test --> core
    example --> springBoot

    style api fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style core fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style spring fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style springCache fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style springRedis fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style springBoot fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style test fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style bom fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style deps fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style example fill:#2d333b,stroke:#6d5dfc,color:#e6edf3

The dependency flow is strictly layered: cocache-api defines interfaces at the bottom, cocache-core provides implementations, cocache-spring adds Spring Framework integration, and cocache-spring-redis / cocache-spring-boot-starter sit at the top for production use.

High-Level System Architecture

CoCache organizes caching into three layers:

mermaid
graph TB
    subgraph sg_11 ["Application Layer"]

        App["Application Code"]
        Proxy["Cache Proxy<br>JDK Dynamic Proxy"]
    end

    subgraph sg_12 ["Coherent Cache - DefaultCoherentCache"]

        L2["L2: ClientSideCache<br>Guava / Caffeine / Map"]
        KF["KeyFilter<br>Bloom Filter"]
        L1["L1: DistributedCache<br>Redis"]
        Lock["Fine-Grained Lock<br>ConcurrentHashMap"]
        L0["L0: CacheSource<br>DataSource / DB"]
    end

    subgraph sg_13 ["Coherence Layer"]

        EventBus["CacheEvictedEventBus<br>Guava EventBus / Redis Pub/Sub"]
        Subscriber["CacheEvictedSubscriber<br>Other Instances"]
    end

    App --> Proxy
    Proxy --> L2
    L2 -->|miss| KF
    KF -->|may exist| L1
    L1 -->|miss| Lock
    Lock -->|acquired| L0
    L0 -->|loaded| L1
    L1 -->|cached| L2

    L2 -.->|evict/set| EventBus
    EventBus -.->|notify| Subscriber
    Subscriber -.->|evict L2| L2

    style App fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style Proxy fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style L2 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style KF fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style L1 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style Lock fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style L0 fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style EventBus fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
    style Subscriber fill:#2d333b,stroke:#6d5dfc,color:#e6edf3
LayerNameRoleInterfaceKey Implementations
L0CacheSourceUpstream data source (DataSource/DB)CacheSource<K, V>NoOpCacheSource, custom implementations
L1DistributedCacheShared distributed cacheDistributedCache<V>RedisDistributedCache
L2ClientSideCacheLocal in-memory cacheClientSideCache<V>MapClientSideCache, GuavaClientSideCache, CaffeineClientSideCache

Cache Read Path

The read path flows L2 -> KeyFilter -> L1 -> Lock -> L0 with several optimization strategies:

mermaid
sequenceDiagram
autonumber
    participant App as Application
    participant CC as DefaultCoherentCache
    participant L2 as ClientSideCache
    participant KF as KeyFilter
    participant L1 as DistributedCache
    participant L0 as CacheSource
    participant EB as Event Bus

    App->>CC: getCache(key)
    CC->>L2: getCache(cacheKey)
    L2-->>CC: cacheValue (hit)
    CC-->>App: cacheValue

    Note over App,EB: If L2 miss, continue...
    CC->>L2: getCache(cacheKey)
    L2-->>CC: null (miss)

    alt Key says not exist
        CC->>KF: notExist(cacheKey)
        KF-->>CC: true
        CC-->>App: missingGuard (prevents penetration)
    end

    CC->>L1: getCache(cacheKey)
    L1-->>CC: cacheValue
    CC->>L2: setCache(cacheKey, cacheValue)
    CC-->>App: cacheValue

    Note over App,EB: If L1 miss, acquire lock and load from L0...
    CC->>CC: synchronized(lock) [fine-grained]
    CC->>L2: getCache(cacheKey) [double-check]
    L2-->>CC: null
    CC->>L1: getCache(cacheKey) [double-check]
    L1-->>CC: null
    CC->>L0: loadCacheValue(key)
    L0-->>CC: cacheValue
    CC->>L2: setCache(cacheKey, cacheValue)
    CC->>L1: setCache(cacheKey, cacheValue)
    CC->>EB: publish(CacheEvictedEvent)
    CC-->>App: cacheValue

Key Design Decisions

1. Fine-Grained Locking

Rather than synchronizing on the entire cache instance, CoCache uses a per-key lock stored in a ConcurrentHashMap<String, Any>. This prevents cache stampede (the "thundering herd" problem) while allowing concurrent access to different keys.

2. Missing Guard (Cache Penetration Prevention)

When a cache source returns null (key does not exist in the database), CoCache stores a special missingGuard cache value instead of leaving the key empty. This prevents repeated database queries for non-existent keys -- the well-known cache penetration problem. The KeyFilter interface (a Bloom filter adapter) provides an additional layer of defense by rejecting keys known not to exist before any cache lookup.

3. Event-Driven Coherence

Rather than relying on TTL expiration to eventually synchronize caches across instances, CoCache actively publishes CacheEvictedEvent through the CacheEvictedEventBus. Each DefaultCoherentCache subscribes to these events and evicts its local L2 cache when a peer modifies the same key. Self-published events are filtered out to avoid redundant local eviction. See Cache Coherence for details.

4. Proxy-Based Declarative Caching

Cache interfaces are declared as Kotlin/Java interfaces annotated with @CoCache. At application startup, EnableCoCacheRegistrar parses these annotations, constructs CoCacheMetadata, and creates JDK dynamic proxies backed by DefaultCoherentCache instances. This allows cache configuration to be fully declarative. See Proxy and Annotations for details.

5. TTL with Amplitude

Each cache entry carries a TTL plus a random ttlAmplitude offset. This jitter prevents synchronized expiration of many entries at once (the "cache avalanche" problem). The amplitude is added as a random value within [-ttlAmplitude, +ttlAmplitude].

Source References

FileLine(s)Description
settings.gradle.kts1-11Module declarations
build.gradle.kts1-219Root build config, JDK 17, Kotlin compiler flags
cocache-api/build.gradle.kts1No external dependencies (pure interfaces)
cocache-core/build.gradle.kts1-12Depends on cocache-api, Guava, Caffeine (compile-only)
cocache-spring/build.gradle.kts1-3Depends on cocache-core, Spring Context
cocache-spring-redis/build.gradle.kts1-10Depends on cocache-core, cocache-spring, Jackson, Spring Data Redis
cocache-spring-boot-starter/build.gradle.kts1-30Depends on cocache-spring, cocache-spring-cache, cocache-spring-redis, Spring Boot
DefaultCoherentCache.kt30-186Central coherent cache implementation
CoherentCache.kt25-32CoherentCache interface definition
CoherentCacheConfiguration.kt26-34Configuration data class with defaults

Released under the Apache License 2.0.