Skip to content

slashdevops/c3e

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🚀 c3e — Cache with Cascading Expiration Engine

Go Reference Go Version Valkey CodeQL Advanced License

c3e is a high-performance, dependency-aware caching layer for Go built on top of Valkey (Redis-compatible). It adds dependency tracking with cascading invalidation, stale-while-revalidate, thundering-herd protection, TTL jitter, and type-safe operations on top of a plain cache.

✨ Features

  • 🔗 Dependency tracking & cascading invalidation — invalidate one entity and every entry that depends on it is dropped, transitively.
  • ⚡ Stale-while-revalidate — serve cached data instantly while refreshing in the background.
  • 🛡️ Thundering-herd protection — singleflight ensures one fetch per key under concurrent load.
  • 🎯 Type-safe operations — generics give compile-time safety.
  • 🎲 TTL jitter — randomized expiry prevents synchronized stampedes.
  • 🔥 Fast fallback — a short query timeout falls back to your source when the cache is slow or down.
  • 📈 Observability — an injectable slog logger plus metrics/tracing hooks.

📦 Installation

go get github.com/slashdevops/c3e

Requirements: Go 1.26+ · valkey-go v1.0.76+.

🚀 Quick start

package main

import (
    "context"
    "time"

    "github.com/slashdevops/c3e"
    "github.com/valkey-io/valkey-go"
)

type Project struct {
    ID    string `json:"id"`
    Name  string `json:"name"`
    Owner string `json:"owner"`
}

func main() {
    // 1. Valkey client.
    valkeyClient, err := valkey.NewClient(valkey.ClientOption{
        InitAddress:  []string{"localhost:6379"},
        DisableRetry: true, // fail fast instead of queueing when the cache is down
    })
    if err != nil {
        panic(err)
    }
    defer valkeyClient.Close()

    // 2. Cache stack.
    cacheManager, err := c3e.NewCacheManager(valkeyClient, false)
    if err != nil {
        panic(err)
    }
    cache, err := c3e.NewSafeCacheManager(cacheManager, c3e.SafeCacheManagerConfig{
        HardTTL:       5 * time.Minute, // absolute expiration
        SoftTTL:       3 * time.Minute, // stale-after (≈60% of HardTTL)
        JitterPercent: 0.1,             // 10% TTL jitter
    })
    if err != nil {
        panic(err)
    }

    // 3. Read through the cache. On a miss, the fetcher runs and its result is
    //    cached together with the entities it depends on.
    ctx := context.Background()
    id := c3e.CacheIdentifier{Type: "project", ID: "123"}

    project, err := c3e.GetSafe(ctx, cache, id,
        func(ctx context.Context) (Project, []c3e.CacheIdentifier, error) {
            p := Project{ID: "123", Name: "Eagle", Owner: "user-456"}
            deps := []c3e.CacheIdentifier{{Type: "user", ID: p.Owner}}
            return p, deps, nil
        })
    if err != nil {
        panic(err)
    }
    _ = project

    // When the owner changes, every entry depending on it is invalidated.
    _ = cache.Invalidate(ctx, c3e.CacheIdentifier{Type: "user", ID: "user-456"})
}

🏗️ How it works

c3e layers a low-level Valkey primitive under an application-facing, stale-while-revalidate API:

flowchart TD
    App["Your application"]
    subgraph c3e["c3e"]
        direction TB
        S["<b>SafeCacheManager</b><br/><i>stale-while-revalidate · singleflight · jitter · hooks</i>"]
        C["<b>CacheManager</b><br/><i>data + dependency graph</i>"]
        R["<b>CacheRepository</b><br/><i>Valkey/Redis client</i>"]
        S --> C --> R
    end
    App -->|"Get / Invalidate"| S
    R --> VK[("Valkey / Redis")]
    S -. "miss · stale · error" .-> DB[("Source of truth")]
Loading

Every Get resolves to one outcome, reported to the OnGet hook as a Result:

flowchart TD
    G(["Get(id, fetcher)"]) --> L{"lookup<br/>(bounded by QueryTimeout)"}
    L -->|"fresh · age ≤ SoftTTL"| H["serve cached<br/><b>→ hit</b>"]
    L -->|"stale · SoftTTL &lt; age ≤ HardTTL"| ST["serve stale now<br/>+ refresh in background<br/><b>→ stale</b>"]
    L -->|"not cached"| M["fetch · cache · serve<br/><b>→ miss</b>"]
    L -->|"slow / error"| E["fetch · serve<br/><b>→ timeout / error</b>"]
Loading

Fresh and stale both serve immediately; miss/timeout/error fall back to the source with concurrent callers collapsed into a single fetch. For the full picture — the key scheme and the dependency reverse index — see docs/architecture.md.

📚 Documentation

Full documentation lives in docs/:

API reference: pkg.go.dev/github.com/slashdevops/c3e.

🤝 Contributing

Contributions are welcome. Please make sure the suite is green before opening a pull request:

go test -race ./...                                   # unit tests
docker run -d --rm -p 6379:6379 valkey/valkey:latest  # for the integration tag
go test -race -tags=integration ./...

📄 License

Licensed under the Apache License 2.0 — see LICENSE.

🙏 Acknowledgments

About

C3E (Cache with Cascading Expiration Engine) is a high-performance, production-ready caching layer for Go applications built on top of [Valkey](https://valkey.io/). It provides advanced features like dependency tracking, stale-while-revalidate, thundering herd protection, and type-safe operations.

Resources

Security policy

Stars

Watchers

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages