groupcache

package module
v2.7.12 Latest Latest
Warning

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

Go to latest
Published: Dec 11, 2025 License: Apache-2.0 Imports: 19 Imported by: 13

README

license Go Report Card Go Reference

groupcache

A modified version of group cache with support for context.Context, go modules, and explicit key removal and expiration. See the CHANGELOG for a complete list of modifications.

Summary

groupcache is a caching and cache-filling library, intended as a replacement for memcached in many cases.

For API docs and examples, see http://godoc.org/github.com/modernprogram/groupcache/v2

Modifications from mailgun/groupcache to modernprogram/groupcache
  • Support for "workspace": explicit state controlled by the user. The application can release groupcache resources by dropping references to the workspace.

  • Additional metric EvictionsNonExpiredOnMemFull accounts number of evictions for non-expired items on mem full condition. This metrics helps in right sizing the memory limit for the cache. When the cache hits mem full condition, it evicts all expired keys from both main cache and hot cache, thus reducing the metric Bytes in order to depict how much cache space was being held by expired keys.

  • New option ExpiredKeysEvictionInterval sets interval for periodic eviction of expired keys. If unset, defaults to 30-minute period. Set to -1 to disable periodic eviction of expired keys.

  • New getter argument info allows the caller to pass optional user-supplied per-request context information that is propagated to the getter load function.

  • Fix for distributed deadklock: https://github.com/mailgun/groupcache/issues/72

Modifications from original library
  • Support for explicit key removal from a group. Remove() requests are first sent to the peer who owns the key, then the remove request is forwarded to every peer in the groupcache. NOTE: This is a best case design since it is possible a temporary network disruption could occur resulting in remove requests never making it their peers. In practice this scenario is very rare and the system remains very consistent. In case of an inconsistency placing a expiration time on your values will ensure the cluster eventually becomes consistent again.

  • Support for expired values. SetBytes(), SetProto() and SetString() now accept an optional time.Time which represents a time in the future when the value will expire. If you don't want expiration, pass the zero value for time.Time (for instance, time.Time{}). Expiration is handled by the LRU Cache when a Get() on a key is requested. This means no network coordination of expired values is needed. However this does require that time on all nodes in the cluster is synchronized for consistent expiration of values.

  • Now always populating the hotcache. A more complex algorithm is unnecessary when the LRU cache will ensure the most used values remain in the cache. The evict code ensures the hotcache never overcrowds the maincache.

Comparing Groupcache to memcached

Like memcached, groupcache:
  • shards by key to select which peer is responsible for that key
Unlike memcached, groupcache:
  • does not require running a separate set of servers, thus massively reducing deployment/configuration pain. groupcache is a client library as well as a server. It connects to its own peers.

  • comes with a cache filling mechanism. Whereas memcached just says "Sorry, cache miss", often resulting in a thundering herd of database (or whatever) loads from an unbounded number of clients (which has resulted in several fun outages), groupcache coordinates cache fills such that only one load in one process of an entire replicated set of processes populates the cache, then multiplexes the loaded value to all callers.

  • does not support versioned values. If key "foo" is value "bar", key "foo" must always be "bar".

Loading process

In a nutshell, a groupcache lookup of Get("foo") looks like:

(On machine #5 of a set of N machines running the same code)

  1. Is the value of "foo" in local memory because it's super hot? If so, use it.

  2. Is the value of "foo" in local memory because peer #5 (the current peer) is the owner of it? If so, use it.

  3. Amongst all the peers in my set of N, am I the owner of the key "foo"? (e.g. does it consistent hash to 5?) If so, load it. If other callers come in, via the same process or via RPC requests from peers, they block waiting for the load to finish and get the same answer. If not, RPC to the peer that's the owner and get the answer. If the RPC fails, just load it locally (still with local dup suppression).

Example

import (
    "context"
    "fmt"
    "log"
    "time"

    "github.com/modernprogram/groupcache/v2"
)

func ExampleUsage() {

    // NOTE: It is important to pass the same peer `http://192.168.1.1:8080` to `NewHTTPPoolOpts`
    // which is provided to `pool.Set()` so the pool can identify which of the peers is our instance.
    // The pool will not operate correctly if it can't identify which peer is our instance.
    
    // Pool keeps track of peers in our cluster and identifies which peer owns a key.
    pool := groupcache.NewHTTPPoolOpts("http://192.168.1.1:8080", &groupcache.HTTPPoolOptions{})

    // Add more peers to the cluster You MUST Ensure our instance is included in this list else
    // determining who owns the key accross the cluster will not be consistent, and the pool won't
    // be able to determine if our instance owns the key.
    pool.Set("http://192.168.1.1:8080", "http://192.168.1.2:8080", "http://192.168.1.3:8080")

    server := http.Server{
        Addr:    "192.168.1.1:8080",
        Handler: pool,
    }

    // Start a HTTP server to listen for peer requests from the groupcache
    go func() {
        log.Printf("Serving....\n")
        if err := server.ListenAndServe(); err != nil {
            log.Fatal(err)
        }
    }()
    defer server.Shutdown(context.Background())

    // Create a new group cache with a max cache size of 3MB
    group := groupcache.NewGroup("users", 3000000, groupcache.GetterFunc(
        func(ctx context.Context, id string, dest groupcache.Sink) error {

            // Returns a protobuf struct `User`
            user, err := fetchUserFromMongo(ctx, id)
            if err != nil {
                return err
            }

            // Set the user in the groupcache to expire after 5 minutes
            return dest.SetProto(&user, time.Now().Add(time.Minute*5))
        },
    ))

    var user User

    ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*500)
    defer cancel()

    if err := group.Get(ctx, "12345", groupcache.ProtoSink(&user)); err != nil {
        log.Fatal(err)
    }

    fmt.Printf("-- User --\n")
    fmt.Printf("Id: %s\n", user.Id)
    fmt.Printf("Name: %s\n", user.Name)
    fmt.Printf("Age: %d\n", user.Age)
    fmt.Printf("IsSuper: %t\n", user.IsSuper)

    // Remove the key from the groupcache
    if err := group.Remove(ctx, "12345"); err != nil {
        log.Fatal(err)
    }
}

Note

The call to groupcache.NewHTTPPoolOpts() is a bit misleading. NewHTTPPoolOpts() creates a new pool internally within the groupcache package where it is uitilized by any groups created. The pool returned is only a pointer to the internallly registered pool so the caller can update the peers in the pool as needed.

Documentation

Overview

Package groupcache provides a data loading mechanism with caching and de-duplication that works across a set of peer processes.

Each data Get first consults its local cache, otherwise delegates to the requested key's canonical owner, which then checks its cache or finally gets the data. In the common case, many concurrent cache misses across a set of peers for the same key result in just one cache fill.

Example
/*
	// Keep track of peers in our cluster and add our instance to the pool `http://localhost:8080`
	pool := groupcache.NewHTTPPoolOpts("http://localhost:8080", &groupcache.HTTPPoolOptions{})

	// Add more peers to the cluster
	//pool.Set("http://peer1:8080", "http://peer2:8080")

	server := http.Server{
		Addr:    "localhost:8080",
		Handler: pool,
	}

	// Start a HTTP server to listen for peer requests from the groupcache
	go func() {
		log.Printf("Serving....\n")
		if err := server.ListenAndServe(); err != nil {
			log.Fatal(err)
		}
	}()
	defer server.Shutdown(context.Background())
*/

// Create a new group cache with a max cache size of 3MB
const purgeExpired = true
group := groupcache.NewGroupWithWorkspace(groupcache.Options{
	Workspace:       groupcache.DefaultWorkspace,
	Name:            "users",
	PurgeExpired:    purgeExpired,
	CacheBytesLimit: 3_000_000,
	Getter: groupcache.GetterFunc(
		func(ctx context.Context, id string, dest groupcache.Sink, info *groupcache.Info) error {

			// In a real scenario we might fetch the value from a database.
			/*if user, err := fetchUserFromMongo(ctx, id); err != nil {
				return err
			}*/

			user := User{
				Id:      "12345",
				Name:    "John Doe",
				Age:     40,
				IsSuper: true,
			}

			// Set the user in the groupcache to expire after 5 minutes
			if err := dest.SetProto(&user, time.Now().Add(time.Minute*5)); err != nil {
				return err
			}
			return nil
		},
	),
})

var user User

ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()

if err := group.Get(ctx, "12345", groupcache.ProtoSink(&user), nil); err != nil {
	log.Fatal(err)
}

fmt.Printf("-- User --\n")
fmt.Printf("Id: %s\n", user.Id)
fmt.Printf("Name: %s\n", user.Name)
fmt.Printf("Age: %d\n", user.Age)
fmt.Printf("IsSuper: %t\n", user.IsSuper)

/*
	// Remove the key from the groupcache
	if err := group.Remove(ctx, "12345"); err != nil {
		fmt.Printf("Remove Err: %s\n", err)
		log.Fatal(err)
	}
*/
Output:

-- User --
Id: 12345
Name: John Doe
Age: 40
IsSuper: true

Index

Examples

Constants

This section is empty.

Variables

View Source
var DefaultWorkspace = NewWorkspace()

DefaultWorkspace is the default workspace, useful for tests. If your application does not need to recreate groupcache resources, you can use this default workspace as well.

View Source
var NowFunc lru.NowFunc = time.Now

NowFunc returns the current time which is used by the LRU to determine if the value has expired. This can be overridden by tests to ensure items are evicted when expired.

Functions

func RegisterPeerPickerWithWorkspace added in v2.5.3

func RegisterPeerPickerWithWorkspace(ws *Workspace, fn func() PeerPicker)

RegisterPeerPickerWithWorkspace registers the peer initialization function. It is called once, when the first group is created. Either RegisterPeerPickerWithWorkspace or RegisterPerGroupPeerPickerWithWorkspace should be called exactly once, but not both.

Types

type ByteView

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

A ByteView holds an immutable view of bytes. Internally it wraps either a []byte or a string, but that detail is invisible to callers.

A ByteView is meant to be used as a value type, not a pointer (like a time.Time).

func (ByteView) At

func (v ByteView) At(i int) byte

At returns the byte at index i.

func (ByteView) ByteSlice

func (v ByteView) ByteSlice() []byte

ByteSlice returns a copy of the data as a byte slice.

func (ByteView) Copy

func (v ByteView) Copy(dest []byte) int

Copy copies b into dest and returns the number of bytes copied.

func (ByteView) Equal

func (v ByteView) Equal(b2 ByteView) bool

Equal returns whether the bytes in b are the same as the bytes in b2.

func (ByteView) EqualBytes

func (v ByteView) EqualBytes(b2 []byte) bool

EqualBytes returns whether the bytes in b are the same as the bytes in b2.

func (ByteView) EqualString

func (v ByteView) EqualString(s string) bool

EqualString returns whether the bytes in b are the same as the bytes in s.

func (ByteView) Expire

func (v ByteView) Expire() time.Time

Expire returns the expire time associated with this view.

func (ByteView) Len

func (v ByteView) Len() int

Len returns the view's length.

func (ByteView) ReadAt

func (v ByteView) ReadAt(p []byte, off int64) (n int, err error)

ReadAt implements io.ReaderAt on the bytes in v.

func (ByteView) Reader

func (v ByteView) Reader() io.ReadSeeker

Reader returns an io.ReadSeeker for the bytes in v.

func (ByteView) Slice

func (v ByteView) Slice(from, to int) ByteView

Slice slices the view between the provided from and to indices.

func (ByteView) SliceFrom

func (v ByteView) SliceFrom(from int) ByteView

SliceFrom slices the view from the provided index until the end.

func (ByteView) String

func (v ByteView) String() string

String returns the data as a string, making a copy if necessary.

func (ByteView) WriteTo

func (v ByteView) WriteTo(w io.Writer) (n int64, err error)

WriteTo implements io.WriterTo on the bytes in v.

type CacheStats

type CacheStats struct {
	Bytes                        int64
	Items                        int64
	Gets                         int64
	Hits                         int64
	Evictions                    int64
	EvictionsNonExpiredOnMemFull int64 // number of evictions for non-expired items on mem full condition
}

CacheStats are returned by stats accessors on Group.

type CacheType

type CacheType int

CacheType represents a type of cache.

const (
	// MainCache is the cache for items that this peer is the
	// owner for.
	MainCache CacheType = iota + 1

	// HotCache is the cache for items that seem popular
	// enough to replicate to this node, even though it's not the
	// owner.
	HotCache
)

type ErrNotFound

type ErrNotFound struct {
	Msg string
}

ErrNotFound should be returned from an implementation of `GetterFunc` to indicate the requested value is not available. When remote HTTP calls are made to retrieve values from other groupcache instances, returning this error will indicate to groupcache that the value requested is not available, and it should NOT attempt to call `GetterFunc` locally.

func (*ErrNotFound) Error

func (e *ErrNotFound) Error() string

func (*ErrNotFound) Is

func (e *ErrNotFound) Is(target error) bool

type ErrRemoteCall

type ErrRemoteCall struct {
	Msg string
}

ErrRemoteCall is returned from `group.Get()` when a remote GetterFunc returns an error. When this happens `group.Get()` does not attempt to retrieve the value via our local GetterFunc.

func (*ErrRemoteCall) Error

func (e *ErrRemoteCall) Error() string

func (*ErrRemoteCall) Is

func (e *ErrRemoteCall) Is(target error) bool

type Getter

type Getter interface {
	// Get returns the value identified by key, populating dest.
	//
	// The returned data must be unversioned. That is, key must
	// uniquely describe the loaded data, without an implicit
	// current time, and without relying on cache expiration
	// mechanisms.
	Get(ctx context.Context, key string, dest Sink, info *Info) error
}

A Getter loads data for a key.

type GetterFunc

type GetterFunc func(ctx context.Context, key string, dest Sink, info *Info) error

A GetterFunc implements Getter with a function.

func (GetterFunc) Get

func (f GetterFunc) Get(ctx context.Context, key string, dest Sink, info *Info) error

type Group

type Group struct {

	// Stats are statistics on the group.
	Stats Stats
	// contains filtered or unexported fields
}

A Group is a cache namespace and associated data loaded spread over a group of 1 or more machines.

func GetGroupWithWorkspace added in v2.5.3

func GetGroupWithWorkspace(ws *Workspace, name string) *Group

GetGroupWithWorkspace returns the named group previously created with NewGroup, or nil if there's no such group.

func GetGroups added in v2.7.6

func GetGroups(ws *Workspace) []*Group

GetGroupWiths returns all groups previously created with NewGroup, or nil if there is no group.

func NewGroupWithWorkspace added in v2.5.3

func NewGroupWithWorkspace(options Options) *Group

NewGroupWithWorkspace creates a coordinated group-aware Getter from a Getter.

The returned Getter tries (but does not guarantee) to run only one Get call at once for a given key across an entire set of peer processes. Concurrent callers both in the local process and in other processes receive copies of the answer once the original Get completes.

The group name must be unique for each getter.

func (*Group) CacheStats

func (g *Group) CacheStats(which CacheType) CacheStats

CacheStats returns stats about the provided cache within the group.

func (*Group) Get

func (g *Group) Get(ctx context.Context, key string, dest Sink, info *Info) error

Get retrieves key for library caller, thus crosstalk is allowed. info holds optional user-supplied per-request context fields that are propagated to the peer getter load function.

func (*Group) GetForPeer added in v2.6.5

func (g *Group) GetForPeer(ctx context.Context, key string, dest Sink, info *Info) error

GetForPeer retrieves key for peer in a crosstalk request, thus further crosstalk won't be allowed.

func (*Group) Name

func (g *Group) Name() string

Name returns the name of the group.

func (*Group) Remove

func (g *Group) Remove(ctx context.Context, key string) error

Remove clears the key from our cache then forwards the remove request to all peers.

func (*Group) Set

func (g *Group) Set(ctx context.Context, key string, value []byte, expire time.Time, hotCache bool) error

type HTTPPool

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

HTTPPool implements PeerPicker for a pool of HTTP peers.

func NewHTTPPoolOptsWithWorkspace added in v2.5.3

func NewHTTPPoolOptsWithWorkspace(ws *Workspace, self string, o *HTTPPoolOptions) *HTTPPool

NewHTTPPoolOptsWithWorkspace initializes an HTTP pool of peers with the given options. Unlike NewHTTPPoolWithWorkspace, this function does not register the created pool as an HTTP handler. The returned *HTTPPool implements http.Handler and must be registered using http.Handle.

func NewHTTPPoolWithWorkspace added in v2.5.3

func NewHTTPPoolWithWorkspace(ws *Workspace, self string) *HTTPPool

NewHTTPPoolWithWorkspace initializes an HTTP pool of peers, and registers itself as a PeerPicker. For convenience, it also registers itself as an http.Handler with http.DefaultServeMux. The self argument should be a valid base URL that points to the current server, for example "http://example.net:8000".

func (*HTTPPool) GetAll

func (p *HTTPPool) GetAll() []ProtoGetter

GetAll returns all the peers in the pool

func (*HTTPPool) PickPeer

func (p *HTTPPool) PickPeer(key string) (ProtoGetter, bool)

func (*HTTPPool) ServeHTTP

func (p *HTTPPool) ServeHTTP(w http.ResponseWriter, r *http.Request)

func (*HTTPPool) Set

func (p *HTTPPool) Set(peers ...string)

Set updates the pool's list of peers. Each peer value should be a valid base URL, for example "http://example.net:8000".

type HTTPPoolOptions

type HTTPPoolOptions struct {
	// BasePath specifies the HTTP path that will serve groupcache requests.
	// If blank, it defaults to "/_groupcache/".
	BasePath string

	// Replicas specifies the number of key replicas on the consistent hash.
	// If blank, it defaults to 50.
	Replicas int

	// HashFn specifies the hash function of the consistent hash.
	// If blank, it defaults to crc32.ChecksumIEEE.
	HashFn consistenthash.Hash

	// Transport optionally specifies an http.RoundTripper for the client
	// to use when it makes a request.
	// If nil, the client uses http.DefaultTransport.
	Transport func(context.Context) http.RoundTripper

	// Context optionally specifies a context for the server to use when it
	// receives a request.
	// If nil, uses the http.Request.Context()
	Context func(*http.Request) context.Context
}

HTTPPoolOptions are the configurations of a HTTPPool.

type Info added in v2.7.0

type Info struct {
	Ctx1 string
	Ctx2 string
}

Info defines optional user-supplied per-request context fields that are propagated to the peer getter load function.

type Logger

type Logger interface {
	Info(msg string, args ...any)
	Error(msg string, args ...any)
}

Logger is interface for pluggable logger. slog.Defaut() creates a logger that satifiest this interface.

type NoPeers

type NoPeers struct{}

NoPeers is an implementation of PeerPicker that never finds a peer.

func (NoPeers) GetAll

func (NoPeers) GetAll() []ProtoGetter

func (NoPeers) PickPeer

func (NoPeers) PickPeer(key string) (peer ProtoGetter, ok bool)

type Options added in v2.6.4

type Options struct {
	Workspace       *Workspace
	Name            string
	CacheBytesLimit int64
	Getter          Getter

	// HotCacheWeight and MainCacheWeight compose the ratio between the hot cache and the main cache.
	// When the cache memory limit is reached, this ratio is used to limit each cache to its limit.
	// One simple way to reason about this ratio is to think of one cache usage against
	// the other, and NOT one cache against the total sum.
	//
	// Consider some examples:
	// 1) mainWeigth=8 and hotWeight=1 => the hot cache limit is 1/8 of the main cache limit.
	// 2) mainWeight=2 and hotWeight=1 => the hot cache limit is 1/2 of the main cache limit.
	// 3) mainWeight=1 and hotWeight=1 => the hot cache limit is equal to the main cache limit.
	// 4) mainWeight=1 and hotWeight=2 => the hot cache limit is 2x the main cache limit.
	// 5) mainWeight=1 and hotWeight=8 => the hot cache limit is 8x the main cache limit.
	//
	// If unspecified, HotCacheWeight defaults to 1.
	// If unspecified, MainCacheWeight defaults to 8.
	HotCacheWeight  int64
	MainCacheWeight int64

	// PurgeExpired enables evicting expired keys on memory full condition.
	PurgeExpired bool

	// ExpiredKeysEvictionInterval sets interval for periodic eviction of expired keys.
	// If unset, defaults to 30-minute period.
	// Set to -1 to disable periodic eviction of expired keys.
	ExpiredKeysEvictionInterval time.Duration

	// Logger is optional pluggable logger.
	// If undefined, groupcache won't log anything.
	// If defined, groupcache will log errors retrieving keys from peers.
	// slog.Defaut() creates a logger that satifiest this interface.
	Logger Logger
}

Options define settings for group.

type PeerPicker

type PeerPicker interface {
	// PickPeer returns the peer that owns the specific key
	// and true to indicate that a remote peer was nominated.
	// It returns nil, false if the key owner is the current peer.
	PickPeer(key string) (peer ProtoGetter, ok bool)
	// GetAll returns all the peers in the group
	GetAll() []ProtoGetter
}

PeerPicker is the interface that must be implemented to locate the peer that owns a specific key.

type ProtoGetter

type ProtoGetter interface {
	Get(context context.Context, in *pb.GetRequest, out *pb.GetResponse) error
	Remove(context context.Context, in *pb.GetRequest) error
	Set(context context.Context, in *pb.SetRequest) error
	// GetURL returns the peer URL
	GetURL() string
}

ProtoGetter is the interface that must be implemented by a peer.

type Sink

type Sink interface {
	// SetString sets the value to s.
	SetString(s string, e time.Time) error

	// SetBytes sets the value to the contents of v.
	// The caller retains ownership of v.
	SetBytes(v []byte, e time.Time) error

	// SetProto sets the value to the encoded version of m.
	// The caller retains ownership of m.
	SetProto(m proto.Message, e time.Time) error
	// contains filtered or unexported methods
}

A Sink receives data from a Get call.

Implementation of Getter must call exactly one of the Set methods on success.

`e` sets an optional time in the future when the value will expire. If you don't want expiration, pass the zero value for `time.Time` (for instance, `time.Time{}`).

func AllocatingByteSliceSink

func AllocatingByteSliceSink(dst *[]byte) Sink

AllocatingByteSliceSink returns a Sink that allocates a byte slice to hold the received value and assigns it to *dst. The memory is not retained by groupcache.

func ByteViewSink

func ByteViewSink(dst *ByteView) Sink

ByteViewSink returns a Sink that populates a ByteView.

func ProtoSink

func ProtoSink(m proto.Message) Sink

ProtoSink returns a sink that unmarshals binary proto values into m.

func StringSink

func StringSink(sp *string) Sink

StringSink returns a Sink that populates the provided string pointer.

func TruncatingByteSliceSink

func TruncatingByteSliceSink(dst *[]byte) Sink

TruncatingByteSliceSink returns a Sink that writes up to len(*dst) bytes to *dst. If more bytes are available, they're silently truncated. If fewer bytes are available than len(*dst), *dst is shrunk to fit the number of bytes available.

type Stats

type Stats struct {
	Gets                     atomic.Int64 // any Get request, including from peers
	CacheHits                atomic.Int64 // either cache was good
	GetFromPeersLatencyLower atomic.Int64 // slowest duration to request value from peers
	PeerLoads                atomic.Int64 // either remote load or remote cache hit (not an error)
	PeerErrors               atomic.Int64
	Loads                    atomic.Int64 // (gets - cacheHits)
	LoadsDeduped             atomic.Int64 // after singleflight
	LocalLoads               atomic.Int64 // total good local loads
	LocalLoadErrs            atomic.Int64 // total bad local loads
	ServerRequests           atomic.Int64 // gets that came over the network from peers
	CrosstalkRefusals        atomic.Int64 // refusals for additional crosstalks
}

Stats are per-group statistics.

type Workspace added in v2.5.5

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

Workspace holds the "global" state for groupcache.

func NewWorkspace added in v2.5.3

func NewWorkspace() *Workspace

NewWorkspace creates new workspace.

Directories

Path Synopsis
cmd
server command
Package main implements a test server.
Package main implements a test server.
Package consistenthash provides an implementation of a ring hash.
Package consistenthash provides an implementation of a ring hash.
Package lru implements an LRU cache.
Package lru implements an LRU cache.
Package singleflight provides a duplicate function call suppression mechanism.
Package singleflight provides a duplicate function call suppression mechanism.