A high-performance memcache client library for Go that uses memcache's Meta Text Protocol and supports namespacing, compression, and context cancellation out of the box.
- Features
- Installation
- Quick Start
- Basic Operations
- Advanced Features
- Configuration
- Error Handling
- Meta Text Protocol
- Development
- Examples
- Contributing
- License
- References
- âś… Meta Text Protocol - Full support for memcache's modern protocol
- âś… Context Support - All operations support
context.Contextfor cancellation and timeouts - âś… Namespacing - Built-in namespace support for cache invalidation
- âś… Compression - Optional compression with configurable thresholds
- âś… Connection Pooling - Efficient connection pooling with automatic management
- âś… Binary Key Encoding - Optional binary key encoding for better performance
- âś… Type Safety - Strong typing with comprehensive error handling
- âś… Atomic Operations - Safe append, prepend, and replace operations
- âś… Optimistic Locking - CAS (Compare-And-Swap) for concurrent updates
- âś… Batch Operations - Efficient multi-key retrieval with
GetMulti
go get github.com/kinescope/mcpackage main
import (
"context"
"fmt"
"log"
"github.com/kinescope/mc"
)
func main() {
// Create a new client
client, err := mc.New(&mc.Options{
Addrs: []string{"127.0.0.1:11211"},
})
if err != nil {
log.Fatal(err)
}
defer client.Close()
ctx := context.Background()
// Set a value
err = client.Set(ctx, &mc.Item{
Key: "user:123",
Value: []byte("John Doe"),
Flags: 0,
}, mc.WithExpiration(3600))
if err != nil {
log.Fatal(err)
}
// Get a value
item, err := client.Get(ctx, "user:123")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Value: %s\n", item.Value)
}ctx := context.Background()
// Set
err := client.Set(ctx, &mc.Item{
Key: "key",
Value: []byte("value"),
}, mc.WithExpiration(60))
// Get
item, err := client.Get(ctx, "key")
if err != nil {
if err == mc.ErrCacheMiss {
// Key not found
}
}
// Delete
err = client.Del(ctx, "key")// Add (only if key doesn't exist)
err := client.Add(ctx, &mc.Item{
Key: "key",
Value: []byte("value"),
})
// Replace (only if key exists)
err := client.Replace(ctx, &mc.Item{
Key: "key",
Value: []byte("new value"),
})
// Append data to existing item
err := client.Append(ctx, &mc.Item{
Key: "key",
Value: []byte(" appended"),
})
// Prepend data to existing item
err := client.Prepend(ctx, &mc.Item{
Key: "key",
Value: []byte("prepended "),
})
// Append with autovivify (create if doesn't exist)
// WithExpiration enables autovivify for append/prepend operations
err := client.Append(ctx, &mc.Item{
Key: "key",
Value: []byte("value"),
}, mc.WithExpiration(3600)) // Create with 1 hour TTL if missing
// Prepend with autovivify (create if doesn't exist)
err := client.Prepend(ctx, &mc.Item{
Key: "key",
Value: []byte("prefix"),
}, mc.WithExpiration(3600)) // Create with 1 hour TTL if missing// Increment
newValue, err := client.Inc(ctx, "counter", 1, 3600, mc.WithInitialValue(0))
// Decrement
newValue, err := client.Dec(ctx, "counter", 1, 3600)keys := []string{"key1", "key2", "key3"}
items, err := client.GetMulti(ctx, keys)
for key, item := range items {
fmt.Printf("%s: %s\n", key, item.Value)
}Namespacing allows you to invalidate all cache entries for a namespace with a single operation. This is useful for cache invalidation by user, tenant, or any logical grouping.
ctx := context.Background()
userID := "123"
// Store items with namespace
err := client.Set(ctx, &mc.Item{
Key: "name",
Value: []byte("John"),
}, mc.WithNamespace("user:"+userID))
err = client.Set(ctx, &mc.Item{
Key: "email",
Value: []byte("[email protected]"),
}, mc.WithNamespace("user:"+userID))
// Invalidate all items for this user
err = client.PurgeNamespace(ctx, "user:"+userID)
// Both "name" and "email" keys are now invalidatedFor more information, see the memcache namespacing documentation.
Enable compression for values larger than a specified threshold:
err := client.Set(ctx, &mc.Item{
Key: "large_data",
Value: largeData,
}, mc.WithCompression(1024)) // Compress if value > 1KBYou can also provide custom compression functions:
client, err := mc.New(&mc.Options{
Addrs: []string{"127.0.0.1:11211"},
Compression: struct {
Compress func([]byte) ([]byte, error)
Decompress func([]byte) ([]byte, error)
}{
Compress: myCompress,
Decompress: myDecompress,
},
})Require an item to be set a minimum number of times before it can be retrieved:
// Set with min uses
err := client.Set(ctx, &mc.Item{
Key: "key",
Value: []byte("value"),
}, mc.WithMinUses(3))
// First 2 Get calls will return ErrCacheMiss
// After 3rd Set, Get will succeedUse CAS for safe concurrent updates:
// Get item with CAS value
item, err := client.Get(ctx, "key", mc.WithCAS())
if err == nil {
// Modify the value
item.Value = []byte("new value")
// Update only if CAS matches (no concurrent modification)
err = client.CompareAndSwap(ctx, item)
if err == mc.ErrCASConflict {
// Another client modified the item, retry
}
}For forced updates, use Set instead of CompareAndSwap:
// Set bypasses CAS check for forced updates
err := client.Set(ctx, &mc.Item{
Key: "key",
Value: []byte("forced value"),
}, mc.WithExpiration(3600))Prevent cache stampede using early recache:
item, err := client.Get(ctx, "key", mc.WithEarlyRecache(60))
if item.Won() {
// This client won the recache - refresh in background
go refreshCache(ctx, client, "key")
} else {
// Other clients get cached data immediately
useCachedData(item.Value)
}Serve stale data when items have expired to prevent cache misses:
item, err := client.Get(ctx, "key", mc.WithEarlyRecache(60))
if item.Stale() {
// Serve stale data while refreshing in background
if item.Won() {
go refreshCache(ctx, client, "key")
}
useData(item.Value)
}Identify and manage frequently accessed (hot) cache keys. For hot keys, avoid deletion as it causes cache misses. Instead, update values in place or use early recache:
// Track hot keys using hit status and last access time
item, err := client.Get(ctx, "key", mc.WithHit(), mc.WithLastAccess())
if item.Hit() && item.LastAccess() < 10 {
// Key is hot - update value without deletion to avoid cache misses
// This keeps the key available while refreshing data
client.Set(ctx, &mc.Item{
Key: "key",
Value: freshData,
}, mc.WithExpiration(3600))
// Alternative: Use early recache for background refresh
// The key remains available while being refreshed
}Get the time since last access for cache optimization:
item, err := client.Get(ctx, "key", mc.WithLastAccess())
fmt.Printf("Last accessed %d seconds ago\n", item.LastAccess())Track hit status to identify frequently accessed items:
item, err := client.Get(ctx, "key", mc.WithHit())
if item.Hit() {
// Key has been accessed before - consider it hot
// Extend TTL, pre-warm, or monitor for optimization
}All operations support context.Context for cancellation and timeouts:
// With timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
item, err := client.Get(ctx, "key")
if err != nil {
if err == context.DeadlineExceeded {
// Operation timed out
}
}
// With cancellation
ctx, cancel := context.WithCancel(context.Background())
go func() {
time.Sleep(1 * time.Second)
cancel() // Cancel the operation
}()
item, err := client.Get(ctx, "key")client, err := mc.New(&mc.Options{
// Server addresses
Addrs: []string{"127.0.0.1:11211", "127.0.0.1:11212"},
// Connection settings
DialTimeout: 200 * time.Millisecond,
ConnMaxLifetime: time.Minute,
MaxIdleConnsPerAddr: 10,
// Disable binary key encoding (use plain text keys)
DisableBinaryEncodedKeys: false,
// Custom server selection function
PickServer: func(key string) []string {
// Custom logic to select servers
return []string{"127.0.0.1:11211"}
},
// Custom compression
Compression: struct {
Compress func([]byte) ([]byte, error)
Decompress func([]byte) ([]byte, error)
}{
Compress: gzip.Compress,
Decompress: gzip.Decompress,
},
})The library provides typed errors for common scenarios:
item, err := client.Get(ctx, "key")
switch err {
case mc.ErrCacheMiss:
// Key not found
case mc.ErrNotStored:
// Item not stored (e.g., Add failed because key exists)
case mc.ErrCASConflict:
// Compare-and-swap conflict
case mc.ErrMalformedKey:
// Invalid key format
case mc.ErrNoServers:
// No servers available
case nil:
// Success
default:
// Other error
}Always close the client when done to release resources:
client, err := mc.New(&mc.Options{
Addrs: []string{"127.0.0.1:11211"},
})
defer client.Close() // Closes all connections in the poolThis library uses memcache's Meta Text Protocol, a modern, efficient protocol that solves several problems with the traditional ASCII protocol:
- Reduced Network Overhead - More compact command format, fewer round trips
- Atomic Operations - Safe append, prepend, and replace operations
- Rich Metadata - CAS values, flags, TTL, last access time, hit status in a single request
- Conditional Operations - Compare-and-Swap (CAS) for optimistic locking
- Efficient Multi-Get - Batch key retrieval with the
mncommand - Early Recaching - Built-in support for preventing cache stampedes
- Binary Key Support - Efficient binary-encoded keys
- Opaque Values - Request/response correlation for async operations
- Flexible Expiration - Update TTL without retrieving the value
- Better Error Handling - Detailed error responses and status codes
- Hot Key Detection - Hit tracking and last access time
- Stale Data Serving - Serve expired data to prevent cache misses
mg- Meta Get (retrieve items)ms- Meta Set (store items)ma- Meta Arithmetic (increment/decrement)md- Meta Delete (delete items)mn- Meta No-op (end of multi-get batch)
# Run all tests
make test
# Run tests with race detector
make test-race
# Run tests with coverage
make test-coverage
# Run benchmarks
make test-bench# Format code
make fmt
# Run linter
make lint
# Run all checks
make checkmake protoSee the example_test.go file for comprehensive examples including:
- Basic operations (Set, Get, Delete, Add, Replace, Append, Prepend)
- Compare-and-Swap operations
- Increment/Decrement with initial values
- Multi-key retrieval
- Namespace management
- Compression
- Early recache and stampeding herd prevention
- Serve stale data
- Hot key cache invalidation
- CAS consistency patterns
- Context and timeout handling
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
This project is licensed under the MIT License - see the LICENSE file for details.