cache-memcached
Memcached backend for cache, covering the parts of the interface Memcached supports.
go get github.com/ubgo/cache-memcached
- Language
- Go
- Go version
- 1.24
- Core deps
- 2 direct
- Latest tag
- v0.1.0
- License
- Apache-2.0
- Go modules
- 1
Highlights from the README
- Interop, not greenfield. If you already run Memcached, this lets that fleet back any code written against
cache.Cachewithout standing up Redis or Postgres. - Honest about limits. TTL introspection, prefix delete, and key iteration are impossible over the Memcached protocol. This adapter returns an explicit
cache.ErrUnsupported(or an iterator whose firstNext()is false withErr() == ErrUnsupported) instead of faking them or silently returning empty. - Correct counter semantics. Memcached counters are unsigned and decrement floors at
0; this adapter implements the missing-key initialisation dance atomically soIncr/Decrbehave per the contract (missing key = 0) within Memcached’s rules. - Server-free tests. A complete in-process fake exercises every supported op;
MEMCACHED_ADDRenables optional real-server checks.
Code from the README
package main
import (
"context"
"errors"
"log"
"time"
"github.com/bradfitz/gomemcache/memcache"
"github.com/ubgo/cache"
memcachedcache "github.com/ubgo/cache-memcached"
)
func main() {
mc := memcache.New("localhost:11211")
c := memcachedcache.New(mc)
defer c.Close()
ctx := context.Background()
_ = c.Set(ctx, "k", []byte("v"), time.Minute)
b, err := c.Get(ctx, "k")
if err != nil {
log.Fatal(err)
}
log.Printf("%s", b)
// Unsupported ops are explicit, never silent:
if _, err := c.TTL(ctx, "k"); errors.Is(err, cache.ErrUnsupported) {
log.Println("Memcached cannot report remaining TTL")
}
}ctx := context.Background()
// --- Works ---
b, err := c.Get(ctx, "k") // ErrNotFound on miss
m, err := c.GetMulti(ctx, []string{"a", "b"}) // missing keys absent from map
ok, err := c.Has(ctx, "k")
err = c.Set(ctx, "k", []byte("v"), time.Minute) // ttl<=0 => no expiry (Expiration 0)
err = c.SetMulti(ctx, map[string]cache.Item{"a": {Value: []byte("1")}})
created, err := c.SetNX(ctx, "k", []byte("v"), time.Minute) // ADD; false if exists
err = c.Expire(ctx, "k", time.Hour) // TOUCH; ErrNotFound if absent
err = c.Touch(ctx, "k") // Expire(key, 1h)
n, err := c.Incr(ctx, "hits", 1) // missing key initialised to delta
n, err = c.Decr(ctx, "hits", 2) // floors at 0 (unsigned counter)
err = c.Del(ctx, "a", "b") // DELETE; miss tolerated
err = c.Flush(ctx) // flush_all
err = c.Ping(ctx)
err = c.Close() // idempotent; does not close mc
// --- ErrUnsupported by protocol design ---
_, err = c.TTL(ctx, "k") // cache.ErrUnsupported
err = c.DeleteByPrefix(ctx, "user:") // cache.ErrUnsupported
it := c.Iterate(ctx, cache.IterateOpts{})
_ = it.Next() // false
_ = it.Err() // cache.ErrUnsupportedFrom the cache-memcached README.
At a glance (from the README)
cache.Cache op | Memcached mapping | Status |
|---|---|---|
Get / GetMulti / Has | get | works |
Set / SetMulti | set | works |
SetNX | add (ErrNotStored ⇒ key exists ⇒ false) | works |
Expire / Touch | touch | works |
Incr / Decr | incr / decr + missing-key add init | works (Decr floors at 0) |
Del | delete (miss tolerated) | works |
Flush | flush_all | works |
Ping / Close | version / local flag | works |
TTL(key) | — no command exists | ErrUnsupported |
DeleteByPrefix | — no server-side scan | ErrUnsupported |
Iterate | — no key enumeration | ErrUnsupported (iterator: Next()=false, Err()=ErrUnsupported) |