### Basic Otter Cache Usage in Go Source: https://github.com/maypok86/otter/blob/main/docs/user-guide/v2/getting-started.md Demonstrates the fundamental operations of the Otter cache in Go. It shows how to create a cache with specified options, set a key-value pair, retrieve a value if present, and invalidate (delete) a key. This example requires the otter library to be installed. ```go package main import ( "fmt" "github.com/maypok86/otter/v2" ) func main() { // Create a cache with basic configuration cache := otter.Must(&otter.Options[string, string]{ MaximumSize: 10_000, InitialCapacity: 1_000, }) // Set a value cache.Set("key", "value") // Get a value if value, ok := cache.GetIfPresent("key"); ok { fmt.Printf("Value: %s\n", value) } // Delete a value if value, invalidated := cache.Invalidate("key"); invalidated { fmt.Printf("Deleted value: %s\n", value) } } ``` -------------------------------- ### Install Otter v2 with Go Source: https://github.com/maypok86/otter/blob/main/docs/user-guide/v2/getting-started.md Installs the latest stable version of the Otter library for Go projects using the go get command. This ensures you have the most recent features and bug fixes. Otter v2 supports the two most recent minor versions of Go. ```bash go get -u github.com/maypok86/otter/v2 ``` -------------------------------- ### Statistics and event handling in Go Source: https://github.com/maypok86/otter/blob/main/docs/user-guide/v2/examples.md Shows how to track cache performance using stats.Counter and how to hook into cache lifecycle events like atomic deletions. ```go // Refer to ./docs/examples/stats-counter/main.go // Refer to ./docs/examples/on-atomic-deletion/main.go ``` -------------------------------- ### Cache loading patterns in Go Source: https://github.com/maypok86/otter/blob/main/docs/user-guide/v2/examples.md Illustrates basic, bulk, and concurrent loading patterns. Includes handling of ErrNotFound and loading additional keys to populate the cache efficiently. ```go // Refer to ./docs/examples/basic-loading/main.go // Refer to ./docs/examples/err-not-found/main.go // Refer to ./docs/examples/loading-linearizability/main.go // Refer to ./docs/examples/bulk-loading/main.go // Refer to ./docs/examples/loading-additional-keys/main.go ``` -------------------------------- ### Weight-based eviction in Go Source: https://github.com/maypok86/otter/blob/main/docs/user-guide/v2/examples.md Demonstrates how to configure the cache to evict entries based on their assigned weight. This is useful for memory-constrained environments where entries have varying sizes. ```go // Refer to ./docs/examples/weight/main.go for implementation details ``` -------------------------------- ### Cache iteration and extension in Go Source: https://github.com/maypok86/otter/blob/main/docs/user-guide/v2/examples.md Provides examples for invalidating cache entries using custom functions and extending the cache functionality for real-world wrapper implementations. ```go // Refer to ./docs/examples/invalidate-by-func/main.go // Refer to ./docs/examples/extension/main.go // Refer to ./docs/examples/wrapper/main.go ``` -------------------------------- ### Create a Basic Go Cache with Size-Based Eviction Source: https://context7.com/maypok86/otter/llms.txt Demonstrates creating a basic in-memory cache in Go using Otter. It configures size-based eviction with a maximum of 10,000 entries and an initial capacity of 1,000. The example shows setting, getting, and invalidating cache entries, as well as checking the estimated size. ```go package main import ( "fmt" "github.com/maypok86/otter/v2" ) func main() { // Create a cache with basic configuration cache := otter.Must(&otter.Options[string, string]{ MaximumSize: 10_000, // Maximum number of entries InitialCapacity: 1_000, // Pre-allocate capacity }) // Set a value cache.Set("key", "value") // Get a value if value, ok := cache.GetIfPresent("key"); ok { fmt.Printf("Value: %s\n", value) // Output: Value: value } // Delete a value if value, invalidated := cache.Invalidate("key"); invalidated { fmt.Printf("Deleted value: %s\n", value) // Output: Deleted value: value } // Get estimated size fmt.Println("Size:", cache.EstimatedSize()) // Output: Size: 0 } ``` -------------------------------- ### Go API Usage Example for Otter Cache Source: https://github.com/maypok86/otter/blob/main/README.md Demonstrates how to create, configure, and use the Otter cache in Go. It covers setting cache options, testing expiration, cache stampede protection with concurrent gets, and background refresh functionality. This example requires the 'github.com/maypok86/otter/v2' package. ```go 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") } } ``` -------------------------------- ### Cache refresh mechanisms in Go Source: https://github.com/maypok86/otter/blob/main/docs/user-guide/v2/examples.md Demonstrates how to perform automatic refreshes during retrieval and manual cache refreshes. These methods ensure data freshness without blocking read operations. ```go // Refer to ./docs/examples/get-with-refresh/main.go // Refer to ./docs/examples/manual-refresh/main.go ``` -------------------------------- ### Entry pinning in Go Source: https://github.com/maypok86/otter/blob/main/docs/user-guide/v2/examples.md Shows how to pin specific entries in the cache to prevent them from being evicted. This ensures critical data remains available regardless of cache pressure. ```go // Refer to ./docs/examples/entry-pinning/main.go for implementation details ``` -------------------------------- ### Time-based eviction strategies in Go Source: https://github.com/maypok86/otter/blob/main/docs/user-guide/v2/examples.md Covers various expiration policies including expiration after creation, last write, and last access. It also demonstrates the use of a custom ExpiryCalculator for complex TTL logic. ```go // Refer to ./docs/examples/expire-after-create/main.go // Refer to ./docs/examples/expire-after-write/main.go // Refer to ./docs/examples/expire-after-access/main.go // Refer to ./docs/examples/custom-expiration/main.go ``` -------------------------------- ### Install Otter library via Go modules Source: https://github.com/maypok86/otter/blob/main/README.md Commands to install the Otter caching library into a Go project. Users can choose between version 1 or the latest stable version 2. ```shell go get -u github.com/maypok86/otter go get -u github.com/maypok86/otter/v2 ``` -------------------------------- ### Asynchronous Cache Refresh in Go Source: https://context7.com/maypok86/otter/llms.txt Shows how to configure cache entries to refresh in the background before they expire using `RefreshCalculator`. This allows `Get` operations to return stale data immediately while a new value is being fetched asynchronously, improving read performance for frequently accessed, slowly changing data. ```go package main import ( "context" "fmt" "sync/atomic" "time" "github.com/maypok86/otter/v2" ) func main() { cache := otter.Must(&otter.Options[string, int]{ // Entries expire 10 minutes after write ExpiryCalculator: otter.ExpiryWriting[string, int](10 * time.Minute), // Refresh eligible 500ms after write RefreshCalculator: otter.RefreshWriting[string, int](500 * time.Millisecond), }) var version atomic.Int32 version.Store(1) loader := otter.LoaderFunc[string, int](func(ctx context.Context, key string) (int, error) { time.Sleep(200 * time.Millisecond) // Simulate slow refresh return int(version.Add(1)), nil }) ctx := context.Background() // Initial set cache.Set("data", 1) fmt.Println("Initial value:", 1) // Wait until refresh is due time.Sleep(600 * time.Millisecond) // This Get triggers async refresh but returns current (stale) value value, _ := cache.Get(ctx, "data", loader) fmt.Println("During refresh:", value) // Output: During refresh: 1 // Wait for refresh to complete time.Sleep(300 * time.Millisecond) // Now get the refreshed value value, _ = cache.GetIfPresent("data") fmt.Println("After refresh:", value) // Output: After refresh: 2 } ``` -------------------------------- ### Implement Custom Cache Expiration Policy (Go) Source: https://context7.com/maypok86/otter/llms.txt Allows for fine-grained control over cache entry expiration by implementing the `ExpiryCalculator` interface. This enables defining different expiration durations based on whether an entry is created, updated, or read. The example shows a custom policy where new entries expire in 5 minutes, updated entries in 2 minutes, and reads do not alter the expiration time. ```go package main import ( "time" "github.com/maypok86/otter/v2" ) // Custom expiration policy type customExpiry struct{} func (e *customExpiry) ExpireAfterCreate(entry otter.Entry[string, int]) time.Duration { return 5 * time.Minute // New entries expire in 5 minutes } func (e *customExpiry) ExpireAfterUpdate(entry otter.Entry[string, int], oldValue int) time.Duration { return 2 * time.Minute // Updated entries expire in 2 minutes } func (e *customExpiry) ExpireAfterRead(entry otter.Entry[string, int]) time.Duration { return entry.ExpiresAfter() // Reads don't change expiration } func main() { cache := otter.Must(&otter.Options[string, int]{ ExpiryCalculator: &customExpiry{}, }) cache.Set("counter", 1) // Expires in 5 minutes cache.Set("counter", 2) // Now expires in 2 minutes cache.GetIfPresent("counter") // No change to expiration } ``` -------------------------------- ### Save and Load Cache Persistence in Go Source: https://github.com/maypok86/otter/blob/main/docs/user-guide/v2/features/persistence.md Demonstrates how to initialize an Otter cache and perform file-based persistence using LoadCacheFromFile and SaveCacheToFile. This approach helps maintain cache state across application restarts. ```go const filePath = "cache.gob" cache := otter.Must(&otter.Options[string, string]{ MaximumSize: 10_000, ExpiryCalculator: otter.ExpiryWriting[string, string](time.Hour), StatsRecorder: stats.NewCounter(), }) if err := otter.LoadCacheFromFile(cache, filePath); err != nil { panic(err) } // ... if err := otter.SaveCacheToFile(cache, filePath); err != nil { panic(err) } ``` -------------------------------- ### Accessing Extension Methods and Metadata in Otter Source: https://context7.com/maypok86/otter/llms.txt Demonstrates how to interact with advanced cache features such as entry metadata, silent reads, metrics tracking, and dynamic resizing. It shows how to perform per-entry expiration and refresh overrides. ```go package main import ( "fmt" "time" "github.com/maypok86/otter/v2" ) func main() { cache := otter.Must(&otter.Options[string, int]{ MaximumWeight: 100, Weigher: func(key string, value int) uint32 { return uint32(value) // Use value as weight }, ExpiryCalculator: otter.ExpiryWriting[string, int](time.Hour), }) cache.Set("item1", 30) cache.Set("item2", 40) // Get entry with full metadata if entry, ok := cache.GetEntry("item1"); ok { fmt.Printf("Key: %s\n", entry.Key) fmt.Printf("Value: %d\n", entry.Value) fmt.Printf("Weight: %d\n", entry.Weight) fmt.Printf("Expires in: %v\n", entry.ExpiresAfter()) } // Silent read (no stats/eviction policy update) entry, _ := cache.GetEntryQuietly("item2") fmt.Printf("Quiet read - Value: %d\n", entry.Value) // Check cache metrics fmt.Printf("Weighted size: %d\n", cache.WeightedSize()) // 70 fmt.Printf("Entry count: %d\n", cache.EstimatedSize()) // 2 fmt.Printf("Maximum: %d\n", cache.GetMaximum()) // 100 // Dynamic resizing cache.SetMaximum(50) // Triggers eviction if needed fmt.Printf("New maximum: %d\n", cache.GetMaximum()) // Override per-entry expiration cache.SetExpiresAfter("item1", 5*time.Minute) // Override per-entry refresh time cache.SetRefreshableAfter("item1", 2*time.Minute) } ``` -------------------------------- ### Efficient Bulk Loading with Deduplication in Go Source: https://context7.com/maypok86/otter/llms.txt Demonstrates how to load multiple cache entries in a single operation using `BulkGet`. The `BulkLoaderFunc` processes a slice of keys, allowing for efficient data retrieval and automatic deduplication of requests. This is ideal for fetching related data from a backend store. ```go package main import ( "context" "fmt" "github.com/maypok86/otter/v2" ) func main() { cache := otter.Must(&otter.Options[string, int]{}) ctx := context.Background() // Bulk loader processes multiple keys at once bulkLoader := otter.BulkLoaderFunc[string, int](func(ctx context.Context, keys []string) (map[string]int, error) { fmt.Printf("Loading %d keys: %v\n", len(keys), keys) result := make(map[string]int, len(keys)) for _, key := range keys { result[key] = len(key) // Value = length of key } return result, nil }) // Bulk get - loads all missing keys in one call keys := []string{"apple", "banana", "cherry"} values, err := cache.BulkGet(ctx, keys, bulkLoader) if err != nil { panic(err) } for k, v := range values { fmt.Printf("%s: %d\n", k, v) } // Output: // Loading 3 keys: [apple banana cherry] // apple: 5 // banana: 6 // cherry: 6 } ``` -------------------------------- ### Implementing Cache Persistence in Otter Source: https://context7.com/maypok86/otter/llms.txt Shows how to save and load cache state to a file using the GOB format, enabling warm restarts for applications. This requires using compatible options during cache initialization. ```go package main import ( "fmt" "time" "github.com/maypok86/otter/v2" "github.com/maypok86/otter/v2/stats" ) func main() { const filePath = "cache.gob" // Create cache with persistence-compatible options cache := otter.Must(&otter.Options[string, int]{ MaximumSize: 10_000, ExpiryCalculator: otter.ExpiryWriting[string, int](time.Hour), StatsRecorder: stats.NewCounter(), }) // Try to load from file (warm start) if err := otter.LoadCacheFromFile(cache, filePath); err != nil { fmt.Println("No existing cache, starting fresh") } // Use cache normally cache.Set("user:1", 100) cache.Set("user:2", 200) // Save cache before shutdown if err := otter.SaveCacheToFile(cache, filePath); err != nil { fmt.Printf("Failed to save cache: %v\n", err) } fmt.Println("Cache saved successfully") } ``` -------------------------------- ### Implement and use Loader with Otter Cache Source: https://github.com/maypok86/otter/blob/main/docs/user-guide/v2/features/loading.md Demonstrates how to define a loader function using otter.LoaderFunc and pass it to the cache.Get method. This pattern ensures that missing keys are automatically fetched from an external source. ```go // Define a loader function loader := otter.LoaderFunc[string, string](func(ctx context.Context, key string) (string, error) { // Load value from external source return "loaded value", nil }) // Get a value, loading it if not present value, err := cache.Get(context.Background(), "key", loader) ``` -------------------------------- ### Collect Cache Statistics Source: https://context7.com/maypok86/otter/llms.txt Shows how to monitor cache performance by attaching a stats recorder. It tracks hits, misses, and evictions to provide a snapshot of cache efficiency. ```go package main import ( "fmt" "github.com/maypok86/otter/v2" "github.com/maypok86/otter/v2/stats" ) func main() { counter := stats.NewCounter() cache := otter.Must(&otter.Options[string, int]{ MaximumSize: 1000, StatsRecorder: counter, }) for i := 0; i < 100; i++ { cache.Set(fmt.Sprintf("key%d", i), i) } for i := 0; i < 100; i++ { cache.GetIfPresent(fmt.Sprintf("key%d", i)) } for i := 100; i < 150; i++ { cache.GetIfPresent(fmt.Sprintf("key%d", i)) } snapshot := counter.Snapshot() fmt.Printf("Hits: %d\n", snapshot.Hits) fmt.Printf("Misses: %d\n", snapshot.Misses) fmt.Printf("Hit Ratio: %.2f\n", snapshot.HitRatio()) } ``` -------------------------------- ### Creating a Database Caching Wrapper with Otter Source: https://context7.com/maypok86/otter/llms.txt Demonstrates the repository pattern by wrapping a database access layer with an Otter cache. It utilizes the Loader interface to automatically fetch and cache data on cache misses. ```go package main import ( "context" "database/sql" "errors" "time" "github.com/maypok86/otter/v2" "github.com/maypok86/otter/v2/stats" ) type User struct { ID int64 Email string CreatedAt time.Time } type UserRepository interface { GetByID(ctx context.Context, id int64) (User, error) } // Database repository type dbUserRepo struct { db *sql.DB } func (r *dbUserRepo) GetByID(ctx context.Context, id int64) (User, error) { var user User err := r.db.QueryRowContext(ctx, "SELECT id, email, created_at FROM users WHERE id = $1", id, ).Scan(&user.ID, &user.Email, &user.CreatedAt) if errors.Is(err, sql.ErrNoRows) { return User{}, otter.ErrNotFound } return user, err } // Cached repository wrapper type cachedUserRepo struct { cache *otter.Cache[int64, User] loader otter.Loader[int64, User] } func NewCachedUserRepo(db *sql.DB) *cachedUserRepo { repo := &dbUserRepo{db: db} loader := otter.LoaderFunc[int64, User](func(ctx context.Context, id int64) (User, error) { return repo.GetByID(ctx, id) }) cache := otter.Must(&otter.Options[int64, User]{ MaximumSize: 10_000, ExpiryCalculator: otter.ExpiryWriting[int64, User](time.Hour), RefreshCalculator: otter.RefreshWriting[int64, User](50 * time.Minute), StatsRecorder: stats.NewCounter(), }) return &cachedUserRepo{cache: cache, loader: loader} } func (r *cachedUserRepo) GetByID(ctx context.Context, id int64) (User, error) { return r.cache.Get(ctx, id, r.loader) } func (r *cachedUserRepo) InvalidateUser(id int64) { r.cache.Invalidate(id) } ``` -------------------------------- ### Automatic Cache Loading with Stampede Protection (Go) Source: https://context7.com/maypok86/otter/llms.txt Demonstrates how to automatically load data into the cache when a requested key is not found, using a provided loader function. It also includes built-in cache stampede protection, ensuring that the loader is only called once even when multiple concurrent requests are made for the same missing key. This prevents overwhelming backend systems. ```go package main import ( "context" "fmt" "sync" "sync/atomic" "time" "github.com/maypok86/otter/v2" ) func main() { cache := otter.Must(&otter.Options[int, string]{}) var calls atomic.Int64 // Loader function called on cache misses loader := otter.LoaderFunc[int, string](func(ctx context.Context, key int) (string, error) { calls.Add(1) time.Sleep(500 * time.Millisecond) // Simulate slow operation return fmt.Sprintf("value-%d", key), nil }) // Simulate cache stampede - 100 concurrent requests for same key var wg sync.WaitGroup ctx := context.Background() for i := 0; i < 100; i++ { wg.Add(1) go func() { defer wg.Done() value, err := cache.Get(ctx, 42, loader) if err != nil { panic(err) } fmt.Println("Got:", value) }() } wg.Wait() // Loader called only ONCE despite 100 concurrent requests fmt.Println("Loader calls:", calls.Load()) // Output: Loader calls: 1 } ``` -------------------------------- ### Bulk Loading with Otter Cache Source: https://github.com/maypok86/otter/blob/main/docs/user-guide/v2/features/bulk.md Demonstrates how to define a BulkLoaderFunc to fetch multiple values from an external source and retrieve them from the cache using BulkGet. This method ensures that missing or expired keys are populated efficiently. ```go bulkLoader := otter.BulkLoaderFunc[string, string](func(ctx context.Context, keys []string) (map[string]string, error) { result := make(map[string]string, len(keys)) for _, key := range keys { result[key] = "loaded value for " + key } return result, nil }) values, err := cache.BulkGet(context.Background(), []string{"key1", "key2"}, bulkLoader) ``` -------------------------------- ### Perform Manual Refresh of Cache Entries Source: https://context7.com/maypok86/otter/llms.txt Demonstrates how to trigger manual refresh operations for individual keys or bulk sets using LoaderFunc and BulkLoaderFunc. This is useful for updating stale data asynchronously. ```go package main import ( "context" "fmt" "time" "github.com/maypok86/otter/v2" ) func main() { cache := otter.Must(&otter.Options[string, string]{}) ctx := context.Background() loader := otter.LoaderFunc[string, string](func(ctx context.Context, key string) (string, error) { return fmt.Sprintf("refreshed-%d", time.Now().Unix()), nil }) cache.Set("key", "initial") cache.Refresh("key", loader) time.Sleep(100 * time.Millisecond) if value, ok := cache.GetIfPresent("key"); ok { fmt.Println(value) } bulkLoader := otter.BulkLoaderFunc[string, string](func(ctx context.Context, keys []string) (map[string]string, error) { result := make(map[string]string) for _, k := range keys { result[k] = "bulk-refreshed" } return result, nil }) cache.Set("a", "1") cache.Set("b", "2") cache.BulkRefresh([]string{"a", "b"}, bulkLoader) } ``` -------------------------------- ### Retrieve Entry and Configure Refresh Source: https://github.com/maypok86/otter/blob/main/docs/user-guide/v2/features/extension.md Shows how to retrieve an entry without affecting cache statistics or eviction policies using GetEntryQuietly. It then demonstrates setting a custom refresh interval based on the entry's expiration time. ```go entry, ok := cache.GetEntryQuietly(key) if !ok { return } cache.SetRefreshableAfter(key, entry.ExpiresAfter() / 2) ``` -------------------------------- ### Bulk Refreshing Cache Entries Source: https://github.com/maypok86/otter/blob/main/docs/user-guide/v2/features/bulk.md Shows how to use the BulkRefresh method to asynchronously update multiple values in the cache. It requires a BulkLoaderFunc to provide the updated data for the specified keys. ```go bulkLoader := otter.BulkLoaderFunc[string, string](func(ctx context.Context, keys []string) (map[string]string, error) { result := make(map[string]string, len(keys)) for _, key := range keys { result[key] = "refreshed value for " + key } return result, nil }) cache.BulkRefresh([]string{"key1", "key2"}, bulkLoader) ``` -------------------------------- ### Go Cache with Entry Pinning in Otter Source: https://context7.com/maypok86/otter/llms.txt Shows how to implement entry pinning in a Go cache using Otter. Pinned entries are protected from eviction, even when the cache is full or under memory pressure. This is achieved by assigning a weight of 0 to the pinned entry in the `Weigher` function. ```go package main import "github.com/maypok86/otter/v2" func main() { pinnedKey := "important" cache := otter.Must(&otter.Options[string, int]{ MaximumWeight: 10, Weigher: func(key string, value int) uint32 { if key == pinnedKey { return 0 // Pinned entry has 0 weight (never evicted) } return 1 // Normal entries have weight 1 }, }) // Populate cache cache.Set(pinnedKey, 999) cache.Set("normal1", 1) cache.Set("normal2", 2) // Force eviction of all evictable entries cache.SetMaximum(0) // Pinned entry survives if v, ok := cache.GetIfPresent(pinnedKey); ok { fmt.Println("Pinned value:", v) // Output: Pinned value: 999 } // Normal entries evicted if _, ok := cache.GetIfPresent("normal1"); !ok { fmt.Println("normal1 was evicted") } } ``` -------------------------------- ### Go Cache with Weight-Based Eviction Source: https://context7.com/maypok86/otter/llms.txt Illustrates configuring a Go cache with weight-based eviction using Otter. This is useful when cache entries have varying memory footprints. The `Weigher` function calculates the weight based on the combined size of the key and value (byte slice). ```go package main import "github.com/maypok86/otter/v2" func main() { // Create cache with weight-based eviction cache := otter.Must(&otter.Options[string, []byte]{ MaximumWeight: 10 * 1024 * 1024, // 10MB total capacity Weigher: func(key string, value []byte) uint32 { return uint32(len(key) + len(value)) // Weight = key + value size }, }) // Add entries - cache tracks total weight cache.Set("small", []byte("hello")) // Weight: 5 + 5 = 10 cache.Set("large", make([]byte, 1024*1024)) // Weight: 5 + 1MB // Force pending operations cache.CleanUp() // Check weighted size fmt.Println("Weighted size:", cache.WeightedSize()) } ``` -------------------------------- ### Configure Cache with Refresh Calculator - Go Source: https://github.com/maypok86/otter/blob/main/docs/user-guide/v2/features/refresh.md This Go code snippet demonstrates how to initialize an Otter cache with a specific refresh interval using `otter.RefreshWriting`. The `RefreshCalculator` determines when a key becomes eligible for asynchronous refresh, which is initiated upon entry query. This allows for flexible cache management where entries can expire if not accessed, even if they are eligible for refresh. ```go cache := otter.Must(&otter.Options[string, string]{ ExpiryCalculator: otter.ExpiryWriting[string, string](time.Hour), RefreshCalculator: otter.RefreshWriting[string, string](30*time.Minute), }) ``` -------------------------------- ### Configure deletion handlers in Go Source: https://github.com/maypok86/otter/blob/main/docs/user-guide/v2/features/deletion.md Shows how to define OnDeletion and OnAtomicDeletion handlers within the Otter cache options. OnDeletion runs asynchronously, while OnAtomicDeletion executes synchronously during the deletion process. ```go cache := otter.Must(&otter.Options[string, string]{ OnDeletion: func(e otter.DeletionEvent[string, string]) { fmt.Printf("Key %s was deleted (%s)\n", e.Key, e.Cause) }, OnAtomicDeletion: func(e otter.DeletionEvent[string, string]) { fmt.Printf("Key %s was deleted (%s)\n", e.Key, e.Cause) }, }) ``` -------------------------------- ### Configure Otter Cache with OnAtomicDeletion Handler Source: https://github.com/maypok86/otter/blob/main/docs/user-guide/v2/features/compute.md Demonstrates how to initialize an Otter cache with an OnAtomicDeletion handler. This allows for intercepting entry evictions to perform custom logic, such as updating secondary storage. ```go cache := otter.Must(&otter.Options[string, string]{ OnAtomicDeletion: func(e otter.DeletionEvent[string, string]) { if !e.WasEvicted() { return } // atomically intercept the entry's eviction } }) ``` -------------------------------- ### Handle Deletion Events Source: https://context7.com/maypok86/otter/llms.txt Implements callbacks for cache entry deletions, supporting both asynchronous and atomic (synchronous) handlers. This is essential for resource cleanup or logging when items are evicted or invalidated. ```go package main import ( "fmt" "sync" "github.com/maypok86/otter/v2" ) func main() { var mu sync.Mutex deletions := make(map[otter.DeletionCause]int) cache := otter.Must(&otter.Options[string, int]{ MaximumSize: 5, OnDeletion: func(e otter.DeletionEvent[string, int]) { fmt.Printf("Async: Key %s deleted, cause: %s\n", e.Key, e.Cause) }, OnAtomicDeletion: func(e otter.DeletionEvent[string, int]) { mu.Lock() deletions[e.Cause]++ mu.Unlock() }, }) for i := 0; i < 10; i++ { cache.Set(fmt.Sprintf("key%d", i), i) } cache.CleanUp() cache.Invalidate("key9") } ``` -------------------------------- ### Enable Statistics Collection with Go Otter Cache Source: https://github.com/maypok86/otter/blob/main/docs/user-guide/v2/features/statistics.md This Go code snippet demonstrates how to initialize an Otter cache with statistics collection enabled using a `stats.Counter` as the `StatsRecorder`. The `stats.Counter` provides methods to track cache performance metrics. ```go cache := otter.Must(&otter.Options[string, string]{ StatsRecorder: stats.NewCounter(), }) ``` -------------------------------- ### Iterate Over Cache Entries Source: https://context7.com/maypok86/otter/llms.txt Demonstrates safe iteration over all cache entries. The Otter cache supports concurrent modifications, allowing items to be safely removed during the iteration process. ```go package main import ( "fmt" "github.com/maypok86/otter/v2" ) func main() { cache := otter.Must(&otter.Options[int, string]{}) for i := 0; i < 10; i++ { cache.Set(i, fmt.Sprintf("value-%d", i)) } for key, value := range cache.All() { fmt.Printf(" %d: %s\n", key, value) } for key := range cache.All() { if key%2 == 0 { cache.Invalidate(key) } } } ``` -------------------------------- ### Adjust Cache Maximum Capacity Source: https://github.com/maypok86/otter/blob/main/docs/user-guide/v2/features/extension.md Demonstrates how to dynamically update the maximum capacity of the cache. The cache will automatically trigger evictions to comply with the new threshold. ```go cache.SetMaximum(2 * cache.GetMaximum()) ``` -------------------------------- ### Iterate over cache entries in Go Source: https://github.com/maypok86/otter/blob/main/docs/user-guide/v2/features/iteration.md Demonstrates how to traverse all key-value pairs in an Otter cache and perform operations such as invalidation. This method is designed to be thread-safe and memory-efficient for concurrent environments. ```go for key, value := range cache.All() { cache.Invalidate(key) } ``` -------------------------------- ### Size-Based Eviction by Entry Weight (Go) Source: https://github.com/maypok86/otter/blob/main/docs/user-guide/v2/features/eviction.md Configures a cache to evict entries when the total weight of all entries exceeds a specified maximum. A custom weigher function is required to define the weight of each key-value pair. ```go cache := otter.Must(&otter.Options[string, string]{ MaximumWeight: 10 * 1024 * 1024, // 10MB Weigher: func(key string, value string) uint32 { return uint32(len(key)+len(value)) }, }) ``` -------------------------------- ### Invalidate cache entries in Go Source: https://github.com/maypok86/otter/blob/main/docs/user-guide/v2/features/deletion.md Demonstrates how to manually remove specific keys or clear the entire cache using the Invalidate and InvalidateAll methods. ```go // individual key cache.Invalidate(key) // all keys cache.InvalidateAll() ```