cache

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 19, 2026 License: Apache-2.0 Imports: 5 Imported by: 0

README

nexutils/cache

the cache module, part of GSF-nexutils, member of the tiny-frameworks family


The cache module implements a lightweight, thread-safe Least Recently Used (LRU) cache for Go, featuring Time-To-Live (TTL) expiration, background cleanup, and JSON persistence.


Features

  • LRU Strategy: Automatically evicts the least recently used items when capacity is reached.
  • TTL Support: Entries expire automatically after a defined duration.
  • Thread-Safe: Safe for concurrent use via sync.Mutex.
  • Loader Pattern: Simplifies data fetching with GetOrLoad and fallback options.
  • Persistence: Save and restore your cache state to/from JSON files.
  • Background Cleanup: Active goroutine to prune expired entries.

Installation

go get codeberg.org/tiny-frameworks/nexutils/cache


Quick Start

package main

import (
	"fmt"
	"time"

	"codeberg.org/tiny-frameworks/nexutils/cache"
)

func main() {
	// Initialize: capacity 100, 10m TTL, cleanup every 1m
	c := cache.New(100, 10*time.Minute, 1*time.Minute)
	defer c.StopCleanup()

	// Set a value
	c.Set("user_1", "Alice")

	// Get a value
	if val, found := c.Get("user_1"); found {
		fmt.Printf("Found: %v\n", val)
	}
}


Extensions

Lazy Loading (GetOrLoad)

Instead of checking for existence manually, provide a loader function. The cache handles the fetching and storage logic automatically:

val, err := c.GetOrLoad("api_data", func() (interface{}, error) {
	return fetchDataFromRemoteAPI()
})

Persistence

Easily persist your cache to disk to survive application restarts:

// Save to file
c.SaveToFile("backup.json")

// Load from file (only non-expired items are restored)
c.LoadFromFile("backup.json")


API Reference

Method Description
New(cap, ttl, interval) Creates a new cache with capacity, TTL, and cleanup interval.
Get(key) Returns the value and updates its LRU position.
Set(key, value) Saves a value and resets its TTL.
GetOrLoad(key, loader) Retrieves the value or loads it if missing using the loader function.
SaveToFile(path) Exports the cache contents to a JSON file.
LoadFromFile(path) Imports cache contents (only non-expired entries are kept).
StopCleanup() Stops the background cleanup goroutine gracefully.

Examples

The example_test.go contains runnable implementations covering key scenarios:

  1. Basic: Standard Get and Set operations.
  2. Lazy Loading: Using GetOrLoad to fetch missing data.
  3. Persistence: Demonstrating SaveToFile and LoadFromFile.
  4. TTL & Cleanup: Showcasing how the background cleaner works.

Run all tests and examples via:

go test -v ./...


How it works (LRU & TTL)

The cache combines a hash map for $O(1)$ fast access with a doubly linked list to track usage order.

  • Read access: An item is moved to the head of the list.
  • Write access: New items are pushed to the head; when capacity is reached, the tail item (least recently used) is evicted.
  • Expiration: The background routine checks for expired timestamps at defined intervals to efficiently free up memory.

Best Practices

Choosing a Cleanup Interval

  • Frequent (e.g., 10s): Ideal for small caches with high turnover where the memory footprint is critical.
  • Balanced (e.g., 1m – 5m): Recommended for most standard use cases.
  • Passive (e.g., 1h): Sufficient if the cache is large and expired items are likely to be evicted by the LRU logic anyway.

Type Assertions

Since the cache stores interface{} (or any), always use type assertions when retrieving values:

if val, found := c.Get("myKey"); found {
	data := val.(string) // Assert to your expected type
}


Organizational & Standards

  • Copyright: © 2026 Georg Hagn.
  • Namespace: codeberg.org/tiny-frameworks/nexutils/cache
  • License: Apache License, Version 2.0.

GSF-nexutils/cache is an independent open-source project and is not affiliated with any corporation of a similar name.


Contact

If you have questions or feedback, feel free to reach out:

📧 georghagn [at] tiny-frameworks.io


Documentation

Overview

Example (Base)

Example_base demonstrates the basic usage of the cache.

package main

import (
	"fmt"
	"time"

	"codeberg.org/tiny-frameworks/nexutils/cache"
)

func main() {
	// Cache with Capazity 3, TTL 50ms, Cleanup every 20ms
	c := cache.New(3, 50*time.Millisecond, 20*time.Millisecond)
	defer c.StopCleanup()

	c.Set("A", 1)
	c.Set("B", 2)
	c.Set("C", 3)

	// Immediately after setting → Retrieve values
	fmt.Println("Immediately after setting:")

	item, found := c.Get("A")
	fmt.Println("A:", item, found) // 1

	item, found = c.Get("B")
	fmt.Println("B:", item, found) // 2

	item, found = c.Get("C")
	fmt.Println("C:", item, found) // 3

	// Wait 4 seconds for the values ​​to expire.
	time.Sleep(80 * time.Millisecond)

	// After TTL expiration → retrieve values ​​(cleanup should have removed old entries)
	fmt.Println("\nAfter the TTL expires:")

	item, found = c.Get("A")
	fmt.Println("A:", item, found) // nil, because expired

	item, found = c.Get("B")
	fmt.Println("B:", item, found) // nil, because expired

	item, found = c.Get("C")
	fmt.Println("C:", item, found) // nil, because expired

	// Add new element → Cache clears automatically
	c.Set("D", 4)
	fmt.Println("\nAfter adding D:")

	item, found = c.Get("D")
	fmt.Println("D:", item, found) // ❌ nil, because expired

}
Output:
Immediately after setting:
A: 1 true
B: 2 true
C: 3 true

After the TTL expires:
A: <nil> false
B: <nil> false
C: <nil> false

After adding D:
D: 4 true
Example (GetOrReload)
package main

import (
	"fmt"
	"time"

	"errors"

	"codeberg.org/tiny-frameworks/nexutils/cache"
)

func main() {
	c := cache.New(3, 5*time.Second, 2*time.Second)
	defer c.StopCleanup()

	// Counter for deterministic behavior during testing
	callCount := 0

	// Loader that simulates the database being unreachable on the second call.
	loader := func() (any, error) {
		callCount++
		if callCount == 2 {
			return nil, errors.New("DB unreachable")
		}
		return fmt.Sprintf("Value loaded (Call %d)", callCount), nil
	}

	// 1st call: Value is successfully loaded from the loader and cached.
	val, err := c.GetOrLoad("user:42", loader)
	if err != nil {
		fmt.Println("Error:", err)
	} else {
		fmt.Println("Result:", val)
	}

	// 2nd call: Value comes directly from the cache (loader is not executed at all!)
	val, err = c.GetOrLoad("user:42", loader)
	if err != nil {
		fmt.Println("Error:", err)
	} else {
		fmt.Println("Result (from cache):", val)
	}

}
Output:
Result: Value loaded (Call 1)
Result (from cache): Value loaded (Call 1)
Example (Komplett)
package main

import (
	"fmt"
	"time"

	"codeberg.org/tiny-frameworks/nexutils/cache"
)

func main() {

	type cacheItem struct {
		ID    int
		Code  string
		Label string
	}

	// Cache with Capazity 3, TTL 5s, Cleanup alle 2s
	c := cache.New(3, 3*time.Second, 2*time.Second)
	defer c.StopCleanup()

	// -------- Simple Set/Get --------
	c.Set("foo", "bar")
	if val, ok := c.Get("foo"); ok {
		fmt.Println("Get foo:", val) // → "bar"
	}

	// ---complex Set/Get with struct --------
	item := &cacheItem{
		ID:    100,
		Code:  "CAB",
		Label: "Label for CAB 100",
	}

	c.Set("CAB", item)
	if val, ok := c.Get("CAB"); ok {
		fmt.Println("Get CAB:", val)
	}

	// -------- TTL Sequence Test --------
	c.Set("temp", "value")
	fmt.Println("Set temp: value")
	time.Sleep(4 * time.Second) // longer than TTL
	if _, ok := c.Get("temp"); !ok {
		fmt.Println("temp expired!")
	}

	// -------- GetOrLoad mit Loader --------
	val, err := c.GetOrLoad("user:1", func() (interface{}, error) {
		fmt.Println("Loader called for user:1")
		return "Alice", nil
	})
	fmt.Println("user:1 =", val, "err:", err)

	// Next access retrieves from cache; loader is NOT called.
	val, _ = c.GetOrLoad("user:1", func() (interface{}, error) {
		fmt.Println("This Loader should not run!")
		return "Bob", nil
	})
	fmt.Println("user:1 =", val)

	// -------- GetOrLoadWithFallback --------
	val, err = c.GetOrLoadWithFallback("user:2", func() (interface{}, error) {
		fmt.Println("Loader fail for user:2")
		return nil, fmt.Errorf("DB down")
	}, "FallbackUser")
	fmt.Println("user:2 =", val, "err:", err)

	// -------- Persistenz: Save/Load --------
	c.Set("session", "abc123")
	if err := c.SaveToFile("cache.json"); err != nil {
		fmt.Println("Error saving:", err)
	} else {
		fmt.Println("Cache in cache.json saved")
	}

	// Neuen Cache laden
	newCache := cache.New(3, 5*time.Second, 2*time.Second)
	defer newCache.StopCleanup()

	if err := newCache.LoadFromFile("cache.json"); err != nil {
		fmt.Println("Error loading:", err)
	} else if val, ok := newCache.Get("session"); ok {
		fmt.Println("Loaded value session:", val)
	}

}
Output:
Get foo: bar
Get CAB: &{100 CAB Label for CAB 100}
Set temp: value
temp expired!
Loader called for user:1
user:1 = Alice err: <nil>
user:1 = Alice
Loader fail for user:2
user:2 = FallbackUser err: DB down
Cache in cache.json saved
Loaded value session: abc123
Example (Persistencen)
package main

import (
	"fmt"
	"time"

	"codeberg.org/tiny-frameworks/nexutils/cache"
)

func main() {
	c := cache.New(3, 10*time.Second, 2*time.Second)

	// Set data
	c.Set("A", 1)
	c.Set("B", 2)
	c.Set("C", 3)

	// save Cache
	if err := c.SaveToFile("cache.json"); err != nil {
		fmt.Println("Error saving:", err)
	}

	// load new cache
	newCache := cache.New(3, 10*time.Second, 2*time.Second)
	if err := newCache.LoadFromFile("cache.json"); err != nil {
		fmt.Println("Error loading:", err)
	}

	// Retrieve values
	item, found := c.Get("A")
	fmt.Println("A:", item, found) // 1
	item, found = c.Get("B")
	fmt.Println("B:", item, found) // 2
	item, found = c.Get("C")
	fmt.Println("C:", item, found) // 3

}
Output:
A: 1 true
B: 2 true
C: 3 true

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type CacheEntry

type CacheEntry struct {
	Key       string
	Value     interface{}
	ExpiresAt time.Time
}

CacheEntry stores key, value, and expiry time

type LRUCache

type LRUCache struct {
	// contains filtered or unexported fields
}

LRUCache is mainstructure

func New

func New(capacity int, ttl time.Duration, cleanupInterval time.Duration) *LRUCache

New creates a new LRU cache

func (*LRUCache) Get

func (c *LRUCache) Get(key string) (interface{}, bool)

Get retrieves a value or false if nothing is found or the date has expired.

func (*LRUCache) GetOrLoad

func (c *LRUCache) GetOrLoad(key string, loader func() (interface{}, error)) (interface{}, error)

GetOrLoad: Retrieves a value from the cache or calls the loader. Only successful loader results are saved.

func (*LRUCache) GetOrLoadWithFallback

func (c *LRUCache) GetOrLoadWithFallback(
	key string,
	loader func() (interface{}, error),
	fallback interface{},
) (interface{}, error)

GetOrLoadWithFallback: like GetOrLoad, but provides a fallback in case of error

func (*LRUCache) LoadFromFile

func (c *LRUCache) LoadFromFile(filename string) error

LoadFromFile loads cache content from JSON file

func (*LRUCache) SaveToFile

func (c *LRUCache) SaveToFile(filename string) error

SaveToFile stores the cache as JSON

func (*LRUCache) Set

func (c *LRUCache) Set(key string, value interface{})

Set stores a value in the cache

func (*LRUCache) StopCleanup

func (c *LRUCache) StopCleanup()

StopCleanup ends the cleanup routine.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL