cl-memcached
quasi/cl-memcachedFast, thread-safe interface to the Memcached object caching system.
25 stars
4 forks
Common Lisp
79 views
SKILL.md
type: skill-template version: 1.0.0 domain: caching language: Common Lisp library: cl-memcached generated: 2026-01-21
cl-memcached Skill Template
Skill for LLM agents implementing memcached caching in Common Lisp applications. Use this skill when working with cl-memcached for caching, sessions, counters, or distributed data.
When to Invoke This Skill
Use this skill when the task involves:
- Storing/retrieving data from memcached
- Implementing caching layers in Lisp applications
- Using atomic counters or CAS operations
- Optimizing performance with connection pooling
- Handling distributed cache failures
- Implementing session storage
- Batch operations or pipelining
Do NOT use this skill for:
- Pure database work (use database-specific skills)
- Non-caching distributed systems
- Applications not using memcached
Core Competencies
This skill covers:
- Connection Management: Creating connections, pooling, lifecycle
- Storage Operations: SET, ADD, REPLACE, APPEND, PREPEND, CAS
- Retrieval Operations: GET, GETS (with CAS tokens)
- Atomic Operations: INCR, DECR, atomic counters
- Key Management: TTL/expiration, deletion, touching
- Error Handling: Connection failures, type validation
- Performance: Connection pooling, pipelining, batch operations
- Protocols: TEXT protocol (classic) and META protocol (modern)
Architecture Summary
High-Level Design
digraph cl_memcached {
rankdir=LR;
Application [shape=box];
PublicAPI [shape=box, label="Public API (mc-set, mc-get, ...)"];
TextProtocol [shape=box, label="TEXT Protocol Handler"];
MetaProtocol [shape=box, label="META Protocol Handler"];
ConnectionPool [shape=box, label="Connection Pool"];
SocketIO [shape=box, label="Binary Socket I/O"];
Memcached [shape=ellipse, label="Memcached Server"];
Application -> PublicAPI;
PublicAPI -> TextProtocol;
PublicAPI -> MetaProtocol;
TextProtocol -> ConnectionPool;
MetaProtocol -> ConnectionPool;
ConnectionPool -> SocketIO;
SocketIO -> Memcached;
}
Component Responsibilities
| Component | Responsibility |
|---|---|
| Public API | Type conversion, global variable management, function entry points |
| Protocol Handlers | Wire format encoding/decoding, command construction, response parsing |
| Connection Pool | Connection reuse, lifecycle management, thread safety |
| Socket I/O | Binary stream handling, encoding, multi-implementation compatibility |
Key Files
- cl-memcached.lisp (711 lines): All implementation
- packages.lisp (57 lines): Exports 44 public symbols
- tests.lisp (529 lines): 27 comprehensive tests
Dependencies
- usocket: Socket I/O
- babel: Encoding/decoding
- split-sequence: String utilities
- pooler: Connection pooling
Operation Reference
Storage (Set/Add/Replace/CAS)
;; Basic storage
(mc-set "key" "value" :timeout 3600) ;; Returns "STORED"
;; Add only if missing
(mc-add "key" "value") ;; "STORED" or "NOT_STORED"
;; Replace only if exists
(mc-replace "key" "new-value") ;; "STORED" or "NOT_STORED"
;; Compare-and-set (atomic, safe for concurrency)
(mc-cas "key" "new-value" cas-token) ;; "STORED", "EXISTS", or "NOT_FOUND"
Retrieval
;; Get multiple values (returns list or nil)
(mc-get+ (list "k1" "k2" "k3")) ;; List of memcache-response objects
;; Get with CAS tokens (for safe updates)
(mc-gets+ (list "k1" "k2")) ;; Same as mc-get+ but with CAS tokens
;; Convenience: single value as string
(mc-get-value "key") ;; Returns string or nil
Counters (Atomic)
;; Initialize counter
(mc-set "counter" "0")
;; Increment
(mc-incr "counter") ;; Returns 1
(mc-incr "counter" 10) ;; Returns 11
;; Decrement (never negative, clamps at 0)
(mc-decr "counter" 5) ;; Returns 6
Deletion & Maintenance
;; Delete key
(mc-del "key") ;; "DELETED" or "NOT_FOUND"
;; Update expiration without modifying data
(mc-touch "key" 3600) ;; "TOUCHED" or "NOT_FOUND"
;; Delete all keys (dangerous!)
(mc-flush-all) ;; "OK"
Statistics
;; Server stats
(mc-stats) ;; Returns alist of stats
;; Per-slab statistics
(mc-stats-items) ;; Item count per slab
(mc-stats-slabs) ;; Slab allocation stats
(mc-stats-sizes) ;; Item size distribution
Protocol Comparison
TEXT Protocol (Classic)
;; Simple, widely compatible
(cl-memcached:mc-set "key" "value")
(cl-memcached:mc-get+ (list "key"))
(cl-memcached:mc-del "key")
Characteristics:
- ASCII command format
- Single-response per operation
- Compatible with all memcached servers
- Simpler error codes
META Protocol (Modern)
;; Modern, supports advanced features
(cl-memcached:mc-meta-set "key" "value" :ttl 3600)
(cl-memcached:mc-meta-get "key")
(cl-memcached:mc-meta-delete "key")
Characteristics:
- More concise wire format
- Pipelining support
- Advanced flags (recache-on-miss, stale-data)
- Better for batch operations
Choose META for: New code, pipelining, advanced features Choose TEXT for: Compatibility, existing code, simplicity
Exemplar Operations
Pattern A: Cache-Aside (Try Cache, Fall Back to Source)
(defun get-with-cache (key source-fn)
"Retrieve from cache or compute and cache."
(handler-case
;; Try cache first
(let ((response (first (cl-memcached:mc-get+ (list key)))))
(when response
(return-from get-with-cache
(babel:octets-to-string (cl-memcached:mc-data response)))))
;; Cache unavailable, use source
(cl-memcached:memcached-server-unreachable ()
nil)) ;; Fall through to source
;; Compute from source
(let ((value (funcall source-fn key)))
;; Try to cache (best effort)
(handler-case
(cl-memcached:mc-set key value :timeout 3600)
(cl-memcached:memcached-server-unreachable ()
nil)) ;; OK if cache fails
value))
Pattern B: Atomic Counter with Verification
(defun increment-counter (key)
"Safely increment counter, verifying initialization."
;; Initialize if needed
(let ((exists (not (null (cl-memcached:mc-get+ (list key))))))
(unless exists
(cl-memcached:mc-set key "0")))
;; Increment atomically
(let ((new-value (cl-memcached:mc-incr key 1)))
(format t "Counter is now: ~D~%" new-value)
new-value))
Pattern C: CAS Update Loop
(defun update-with-cas (key update-fn)
"Update value safely using CAS loop."
(loop
(let* ((response (first (cl-memcached:mc-gets+ (list key))))
(cas-token (when response (cl-memcached:mc-cas-unique response)))
(old-value (when response
(babel:octets-to-string
(cl-memcached:mc-data response)))))
(unless response
;; Key doesn't exist, try add
(let ((new-value (funcall update-fn nil)))
(when (string= (cl-memcached:mc-add key new-value) "STORED")
(return new-value))))
;; Update with CAS
(let* ((new-value (funcall update-fn old-value))
(result (cl-memcached:mc-cas key new-value cas-token)))
(case (read-from-string result)
(STORED (return new-value))
(EXISTS (format t "Conflict, retrying~%"))
(NOT_FOUND (format t "Key deleted, retrying~%")))))))
Pattern D: Batch Operations with Pipelining
(defun batch-set (key-values)
"Set multiple values efficiently via pipelining."
(cl-memcached:mc-with-connection (s)
;; Send all operations (no responses)
(dolist (kv key-values)
(destructuring-bind (key value) kv
(cl-memcached:mc-meta-set key value :stream s :quiet t)))
;; Flush pipeline
(cl-memcached:mc-meta-noop :stream s)
(format t "Set ~D key-value pairs efficiently~%" (length key-values))))
Error Handling Patterns
Handle Connection Failure
(handler-case
(cl-memcached:mc-set "key" "value")
(cl-memcached:memcached-server-unreachable (e)
(format *error-output* "Cache error: ~A~%" e)
;; Fallback logic here
))
Handle Type Errors
(handler-case
(cl-memcached:mc-get+ "not-a-list") ;; BUG: should be list
(cl-memcached:cl-mc-error (e)
(format *error-output* "Programming error: ~A~%" e)
;; Fix the bug, don't suppress
))
Ignore Missing Keys (Normal)
(let ((response (first (cl-memcached:mc-get+ (list "key")))))
(if response
;; Key found
(format t "Value: ~A~%"
(babel:octets-to-string (cl-memcached:mc-data response)))
;; Key not found (not an error)
(format t "Key not in cache~%")))
Global Configuration
;; Default connection
(setf cl-memcached:*memcache*
(cl-memcached:make-memcache :host "cache.example.com" :port 11211))
;; Enable connection pooling (5-7x speedup)
(setf cl-memcached:*mc-use-pool* t)
;; Change default encoding (UTF-8 by default)
(setf cl-memcached:*mc-default-encoding* :latin1)
Performance Tuning
1. Enable Connection Pooling
(setf cl-memcached:*mc-use-pool* t)
;; Expected: 5-7x speedup
2. Use Pipelining for Batches
;; Without pipelining: N round-trips
(dotimes (i 1000)
(cl-memcached:mc-set (format nil "k~D" i) "v"))
;; With pipelining: O(1) round-trips
(cl-memcached:mc-with-connection (s)
(dotimes (i 1000)
(cl-memcached:mc-meta-set (format nil "k~D" i) "v"
:stream s :quiet t))
(cl-memcached:mc-meta-noop :stream s))
3. Use Batch GET Operations
;; Without batching: 3 requests
(mc-get+ (list "k1"))
(mc-get+ (list "k2"))
(mc-get+ (list "k3"))
;; With batching: 1 request
(mc-get+ (list "k1" "k2" "k3"))
4. Increase Pool Size for High Concurrency
(setf mc (cl-memcached:make-memcache
:host "cache.example.com"
:pool-size 20)) ;; Increased from 5
Type Constraints (CRITICAL)
| Type | Constraint | Example |
|---|---|---|
| Keys | String, 1-250 chars | "user:123" |
| Values | Octets (for mc-store) or string (for mc-set) | #(72 101 108 108 111) or "hello" |
| TTL | Integer seconds, 0 = never expire | 3600 |
| CAS token | String from previous gets+ | From mc-cas-unique |
| Keys-list | List, not string | (list "k1" "k2") not "k1 k2" |
Debugging Checklist
When operations fail:
[ ] Is memcached server running? (echo "stats" | nc localhost 11211)
[ ] Are host/port correct? (matches mc-make-memcache args)
[ ] Are keys strings? (not symbols or numbers)
[ ] Are values strings or octets? (not structures)
[ ] Is keys-list a list? (not a string)
[ ] Did you decode the response? (use babel:octets-to-string)
[ ] Is TTL a number? (not a string)
[ ] Did you initialize counter before increment? (mc-set first)
[ ] Did you check CAS response code? (STORED, EXISTS, or NOT_FOUND)
[ ] Is pooling enabled? (setf *mc-use-pool* t)
Conformance Requirements
For implementation:
- All string keys (RULE-001)
- All values stored as octets (RULE-002)
- GET operations accept lists (RULE-003)
- CAS only with
:cascommand (RULE-004) - Counters initialized before use (RULE-005)
- TTL=0 means never expire (RULE-006)
- Connection cleanup guaranteed (RULE-007)
- Both protocols semantically equivalent (RULE-008)
- Missing keys return nil, not error (RULE-009)
- Counters atomic (RULE-010)
See CLAUDE.md for complete specification.
Implementation Workflow
Step 1: Setup
(ql:quickload :cl-memcached)
(setf cl-memcached:*mc-use-pool* t) ;; Enable pooling
Step 2: Connect
(defvar *cache*
(cl-memcached:make-memcache :host "localhost" :port 11211))
Step 3: Store
(cl-memcached:mc-set "key" "value" :timeout 3600 :memcache *cache*)
Step 4: Retrieve
(let ((response (first (cl-memcached:mc-get+ (list "key") :memcache *cache*))))
(when response
(format t "~A~%" (babel:octets-to-string (cl-memcached:mc-data response)))))
Step 5: Handle Errors
(handler-case
(cl-memcached:mc-set "key" "value")
(cl-memcached:memcached-server-unreachable ()
;; Fallback or retry
))
Reference Documents
- CLAUDE.md: Complete implementation specification
- canon/canon.yaml: Full architecture and decisions
- docs/: Human-readable guides and tutorials
- canon/features/*/contracts/: Detailed API contracts
- canon/features/*/scenarios/: Usage examples and patterns
Verification
Run tests:
(asdf:test-system :cl-memcached)
Check invariants:
;; Data round-trip
(let ((original "test"))
(mc-set "k" original)
(assert (string= original
(babel:octets-to-string (mc-data (first (mc-get+ (list "k"))))))))
;; Counter atomicity
(mc-set "c" "0")
(loop repeat 100 do (mc-incr "c"))
(assert (= 100 (parse-integer (mc-get-value "c"))))
Skill Version: 1.0.0 Library Version: 1.0.0 Confidence: 0.94 Last Updated: 2026-01-21