Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

slashdevops/c3e

Open more actions menu

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
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

Morty Proxy This is a proxified and sanitized view of the page, visit original site.