Skip to content

slashdevops/c3e

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

5 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

License

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Sponsor this project

Packages

 
 
 

Contributors

Languages