MCPcopy
hub / github.com/maypok86/otter

github.com/maypok86/otter @v2.3.0 sqlite

repository ↗ · DeepWiki ↗ · release v2.3.0 ↗
1,440 symbols 4,524 edges 148 files 289 documented · 20%
README


In-memory caching library

Go Reference GitHub Release Mentioned in Awesome Go

Otter is designed to provide an excellent developer experience while maintaining high performance. It aims to address the shortcomings of its predecessors and incorporates design principles from high-performance libraries in other languages (such as Caffeine).

📖 Contents

✨ Features

Performance-wise, Otter provides:

Otter also provides a highly configurable caching API, enabling any combination of these optional features:

📚 Usage

For more details, see our user's guide and browse the API docs for the latest release.

📋 Requirements

Otter requires Go version 1.24 or above.

🛠️ Installation

With v1

go get -u github.com/maypok86/otter

With v2

go get -u github.com/maypok86/otter/v2

See the release notes for details of the changes.

Note that otter only supports the two most recent minor versions of Go.

Otter follows semantic versioning for the documented public API on stable releases. v2 is the latest stable major version.

✏️ Examples

Otter uses a plain Options struct for cache configuration. Check out otter.Options for more details.

Note that all features are optional. You can create a cache that acts as a simple hash table wrapper, with near-zero memory overhead for unused features — thanks to node code generation.

API Usage Example

package main

import (
    "context"
    "time"

    "github.com/maypok86/otter/v2"
    "github.com/maypok86/otter/v2/stats"
)

func main() {
    ctx := context.Background()

    // Create statistics counter to track cache operations
    counter := stats.NewCounter()

    // Configure cache with:
    // - Capacity: 10,000 entries
    // - 1 second expiration after last access
    // - 500ms refresh interval after writes
    // - Stats collection enabled
    cache := otter.Must(&otter.Options[string, string]{
        MaximumSize:       10_000,
        ExpiryCalculator:  otter.ExpiryAccessing[string, string](time.Second),  // Reset timer on reads/writes
        RefreshCalculator: otter.RefreshWriting[string, string](500 * time.Millisecond),  // Refresh after writes
        StatsRecorder:     counter,  // Attach stats collector
    })

    // Phase 1: Test basic expiration
    // -----------------------------
    cache.Set("key", "value")  // Add initial value

    // Wait for expiration (1 second)
    time.Sleep(time.Second)

    // Verify entry expired
    if _, ok := cache.GetIfPresent("key"); ok {
        panic("key shouldn't be found")  // Should be expired
    }

    // Phase 2: Test cache stampede protection
    // --------------------------------------
    loader := func(ctx context.Context, key string) (string, error) {
        time.Sleep(200 * time.Millisecond)  // Simulate slow load
        return "value1", nil  // Return new value
    }

    // Concurrent Gets would deduplicate loader calls
    value, err := cache.Get(ctx, "key", otter.LoaderFunc[string, string](loader))
    if err != nil {
        panic(err)
    }
    if value != "value1" {
        panic("incorrect value")  // Should get newly loaded value
    }

    // Phase 3: Test background refresh
    // --------------------------------
    time.Sleep(500 * time.Millisecond)  // Wait until refresh needed

    // New loader that returns updated value
    loader = func(ctx context.Context, key string) (string, error) {
        time.Sleep(100 * time.Millisecond)  // Simulate refresh
        return "value2", nil  // Return refreshed value
    }

    // This triggers async refresh but returns current value
    value, err = cache.Get(ctx, "key", otter.LoaderFunc[string, string](loader))
    if err != nil {
        panic(err)
    }
    if value != "value1" {  // Should get old value while refreshing
        panic("loader shouldn't be called during Get")
    }

    // Wait for refresh to complete
    time.Sleep(110 * time.Millisecond)

    // Verify refreshed value
    v, ok := cache.GetIfPresent("key")
    if !ok {
        panic("key should be found")  // Should still be cached
    }
    if v != "value2" {  // Should now have refreshed value
        panic("refresh should be completed")
    }
}

You can find more usage examples here.

📊 Performance

The benchmark code can be found here.

🚀 Throughput

Throughput benchmarks are a Go port of the caffeine benchmarks. This microbenchmark compares the throughput of caches on a zipf distribution, which allows to show various inefficient places in implementations.

You can find results here.

🎯 Hit ratio

The hit ratio simulator tests caches on various traces: 1. Synthetic (Zipf distribution) 2. Traditional (widely known and used in various projects and papers)

You can find results here.

💾 Memory consumption

This benchmark quantifies the additional memory consumption across varying cache capacities.

You can find results here.

🏗️ Projects using Otter

Below is a list of known projects that use Otter:

  • Grafana: The open and composable observability and data visualization platform.
  • Centrifugo: Scalable real-time messaging server in a language-agnostic way.
  • FrankenPHP: The modern PHP app server.
  • Unkey: Open source API management platform.
  • Nuclei: A fast, open-source, and highly customizable vulnerability scanner.

🗃 Related works

Otter is based on the following papers:

👏 Contribute

Contributions are welcome as always, before submitting a new PR please make sure to open a new issue so community members can discuss it. For more information please see contribution guidelines.

Additionally, you might find existing open issues which can help with improvements.

This project follows a standard code of conduct so that you can understand what actions will and will not be tolerated.

📄 License

This project is Apache 2.0 licensed, as found in the LICENSE.

Extension points exported contracts — how you extend this code

Clock (Interface)
Clock is a time source that - Returns a time value representing the number of nanoseconds elapsed since some fixed but a [5 …
clock.go
Logger (Interface)
Logger is the interface used to get log output from otter. [4 implementers]
logger.go
Recorder (Interface)
Recorder accumulates statistics during the operation of a otter.Cache. [3 implementers]
stats/recorder.go
ExpiryCalculator (Interface)
ExpiryCalculator calculates when cache entries expire. A single expiration time is retained so that the lifetime of an e [1 …
expiry_calculator.go
RefreshCalculator (Interface)
RefreshCalculator calculates when cache entries will be reloaded. A single refresh time is retained so that the lifetime
refresh_calculator.go
Loader (Interface)
Loader computes or retrieves values, based on a key, for use in populating a [Cache].
loader.go
Option (FuncType)
Option applies options to the logger.
plugin/pslog/logger.go
Client (Interface)
(no doc) [2 implementers]
benchmarks/client/client.go

Core symbols most depended-on inside this repo

p
called by 229
cmd/generator/main.go
Load
called by 208
loader.go
Set
called by 119
benchmarks/client/client.go
Add
called by 99
internal/lossy/striped.go
Must
called by 91
cache.go
Value
called by 74
internal/hashmap/node.go
Get
called by 72
benchmarks/client/client.go
Key
called by 60
internal/hashmap/node.go

Shape

Method 924
Function 357
Struct 125
Interface 21
TypeAlias 9
FuncType 4

Languages

Go100%

Modules by API surface

cache_impl.go85 symbols
internal/hashmap/map_test.go44 symbols
cache_test.go44 symbols
internal/generated/node/manager.go41 symbols
internal/generated/node/bw.go36 symbols
internal/generated/node/bsr.go36 symbols
internal/generated/node/bser.go36 symbols
internal/generated/node/bse.go36 symbols
internal/generated/node/bs.go36 symbols
internal/generated/node/brw.go36 symbols
internal/generated/node/br.go36 symbols
internal/generated/node/bew.go36 symbols

Dependencies from manifests, versioned

github.com/Yiling-J/theine-gov0.6.1 · 1×
github.com/cespare/xxhash/v2v2.3.0 · 1×
github.com/chromedp/cdprotov0.0.0-2023101105015 · 1×
github.com/chromedp/sysutilv1.0.0 · 1×
github.com/dgryski/go-clockprov0.0.0-2014081712403 · 1×

For agents

$ claude mcp add otter \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact