Skip to content

parMaster/mcache

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

35 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mcache codecov GitHub Go Report Card Go

mcache is a simple, fast, thread-safe in-memory cache library with by-key TTL written in Go.

Features

  • Thread-safe cache operations
  • Set key-value pairs with optional expiration time
  • Update a key-value pair unconditionally, even if the key is still live
  • Get values by key
  • Check if a key exists
  • Delete key-value pairs
  • Clear the entire cache
  • Get the current number of keys in the cache
  • Cleanup expired key-value pairs
  • Generic type support

Installation

Use go get to install the package:

go get github.com/parMaster/mcache/v2

Usage

Import the mcache package in your Go code:

import "github.com/parMaster/mcache/v2"

Create a new cache instance using the NewCache constructor, and use it to perform cache operations:

cache := mcache.NewCache[string]()
data, err := cache.Get("key")
if err == nil {
	return data
}
data = ExpensiveFunctionCall()
cache.Set("key", data, 5*time.Minute) // cache data for 5 minutes

Examples

See the examples directory for more examples.

API Reference

Interface

The Cacher interface is used to define the cache operations:

type Cacher[T any] interface {
	Set(key string, value T, ttl time.Duration) bool
	Update(key string, value T, ttl time.Duration) bool
	Get(key string) (T, error)
	Has(key string) error
	Del(key string) error
	Clear()
	Len() int
}

Set

Set a key-value pair in the cache. The key must be a string, value type defined during cache creation, ttl is time.Duration type. If ttl is 0, the key-value pair will not expire.:

cache.Set("key", "value", time.Duration(0))

If the key already exists and is not expired, false will be returned. If the key exists but is expired, the value will be updated.

You can also set a key-value pair with an expiration time (in seconds):

cache.Set("key", "value", time.Minute)

The value will automatically expire after the specified duration.

Update

Set a key-value pair unconditionally, replacing any existing value or expiration — including a live (non-expired) key, which Set would refuse:

updated := cache.Update("key", "value", time.Minute)

Returns false only if ttl is negative; the existing entry, if any, is left untouched in that case.

Get

Retrieve a value from the cache by key:

value, err := cache.Get("key")
if err != nil {
    // handle error
}

If the key does not exist, an error mcache.ErrKeyNotFound will be returned. If the key exists but is expired, an error mcache.ErrExpired will be returned, and the key-value pair will be deleted.

Either error or value could be checked to determine if the key exists. Error is easier to check when the value is a zero value.

Has

Check if a key exists in the cache:

err := cache.Has("key")
if err != nil {
    // possible errors:
    // mcache.ErrKeyNotFound - key doesn't exist
    // mcache.ErrExpired     - key existed but expired (and was deleted)
}

if err == nil {
    // key exists
}

Delete

Delete a key-value pair from the cache:

err := cache.Del("key")
if err != nil {
    // handle error
}

Clear

Clear the entire cache:

cache.Clear()

Len

Get the number of keys currently in the cache (including expired keys not yet cleaned up):

count := cache.Len()

Cleanup

Cleanup expired key-value pairs in the cache. You can call this method periodically to remove expired key-value pairs from the cache:

cache.Cleanup()

WithCleanup is a functional option to the NewCache constructor that allows you to specify a cleanup interval:

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
cache := mcache.NewCache(mcache.WithCleanup[string](ctx, time.Minute)) // cleanup every 60 seconds

It runs Cleanup in a background goroutine on the given interval, until ctx is cancelled.

Note: as of v2, Cleanup is not part of the Cacher interface — it's only callable on the concrete *Cache[T] returned by NewCache. This keeps the interface focused on operations any implementation must support, since Cleanup is meant to be driven by WithCleanup's background goroutine, not called ad hoc by interface-typed callers.

Migrating from v1

v2 consolidates all breaking changes since v1.0.0 into one release:

  • WithCleanup takes a context.Context as its first argument: WithCleanup[T](ctx, interval) instead of WithCleanup[T](interval). The background goroutine now stops when ctx is cancelled.
  • Has returns a single error instead of (bool, error). Check err == nil instead of the old bool.
  • Clear returns nothing instead of error (it never actually failed).
  • Cleanup is no longer part of the Cacher interface — call it on the concrete *Cache[T] if you need it directly, or use WithCleanup.
  • New: Update(key, value, ttl) bool for unconditional overwrites, and Len() int for the current key count.

Tests and Benchmarks

100% test coverage:

$ go test -cover -race -cpu 24 .

ok      github.com/parMaster/mcache/v2     5.634s  coverage: 100.0% of statements

Blinding fast and efficient:

$ go test -bench . -benchmem
goos: darwin
goarch: arm64
pkg: github.com/parMaster/mcache/v2
cpu: Apple M3 Pro
BenchmarkWrite-11            	 3989781	       322.6 ns/op	     159 B/op	       3 allocs/op
BenchmarkRead-11             	 6190872	       241.6 ns/op	      15 B/op	       1 allocs/op
BenchmarkRWD-11              	 4737552	       253.8 ns/op	      80 B/op	       6 allocs/op
BenchmarkConcurrentRWD-11    	 1000000	      3688 ns/op	    1128 B/op	      14 allocs/op
PASS
ok  	github.com/parMaster/mcache/v2	13.823s

Contributing

Contributions are welcome! If you find any issues or have suggestions for improvements, please open an issue or submit a pull request.

License

This project is licensed under the MIT license.

About

Simple, fast, thread-safe in-memory cache with by-key TTL and generic value types

Topics

Resources

License

Stars

Watchers

Forks

Contributors

Languages