Memcached Protocol Reference
Faultbox speaks enough of the Memcached text protocol to inject faults on
the wire between your service and a real Memcached server. It does not
provide a Starlark-side client — your service-under-test uses its own
Memcached library (gomemcache, pylibmc, etc.) and Faultbox transparently
proxies that traffic, matching on command + key to inject errors, delays,
or drops on specific commands.
Interface declaration:
cache = service("memcached",
interface("main", "memcached", 11211),
image = "memcached:1.6",
healthcheck = tcp("localhost:11211"),
)
How faultbox sees Memcached traffic
The proxy parses the Memcached text-protocol command line
(<command> <key> [<flags> <exptime> <bytes>]\r\n) and routes each command
through the fault-rule table before forwarding to the real server. Storage
commands (set, add, replace, append, prepend, cas) that carry a
data block get the block forwarded as well.
Recognized commands for fault matching:
- Retrieval:
GET,GETS,GAT,GATS - Storage:
SET,ADD,REPLACE,APPEND,PREPEND,CAS - Delete / counters:
DELETE,INCR,DECR,TOUCH - Admin:
FLUSH_ALL,STATS,VERSION,QUIT
The command name is compared case-insensitively via glob, so a rule keyed
on SET matches set foo 0 0 3\r\n on the wire.
Fault Rules
error(command=, key=, message=)
Return a Memcached protocol error for matching commands. The message is
sent verbatim as a single line; Memcached clients treat any line that
starts with ERROR, CLIENT_ERROR, or SERVER_ERROR as a protocol-level
error.
storage_fails = fault_assumption("storage_fails",
target = cache.main,
rules = [error(command="SET", message="SERVER_ERROR out of memory storing object")],
)
hot_key_fails = fault_assumption("hot_key_fails",
target = cache.main,
rules = [error(key="session:*", message="SERVER_ERROR busy")],
)
| Parameter | Type | Description |
|---|---|---|
command | string | Memcached command glob ("SET", "GET", "*") |
key | string | Key glob pattern ("session:*", "user:*") |
message | string | Response line (include the SERVER_ERROR / CLIENT_ERROR prefix your client expects) |
delay(command=, key=, delay=)
Delay matching commands by delay before forwarding them. Tests client
read-timeouts and the “cache is slower than the DB” failure mode.
slow_cache = fault_assumption("slow_cache",
target = cache.main,
rules = [delay(command="GET", delay="500ms")],
)
drop(command=, key=)
Close the TCP connection mid-command. Forces the client’s connection pool to evict and reopen; exercises reconnect + backoff paths.
cache_partitioned = fault_assumption("cache_partitioned",
target = cache.main,
rules = [drop(command="*")],
)
Recipes
Canonical Memcached failure wrappers ship in the stdlib. Load them via
@faultbox/recipes/memcached.star:
load("@faultbox/recipes/memcached.star", "memcached")
oom = fault_assumption("oom",
target = cache.main,
rules = [memcached.server_error(command = "set", key = "user:*")],
)
Full list: @faultbox/recipes/memcached.star.
Covered scenarios: server_error (out of memory), client_error,
not_stored, exists (stale CAS), item_too_large, busy,
slow_command, connection_drop.
Seed / Reset Patterns
Memcached is purely in-memory — a flush_all wipes the whole cache.
Typical pattern: reuse the container across tests, flush_all + re-seed
on each reset.
cache = service("memcached",
interface("main", "memcached", 11211),
image = "memcached:1.6",
healthcheck = tcp("localhost:11211"),
reuse = True,
reset = lambda: exec("echo 'flush_all' | nc -q1 $HOST 11211"),
)
Notes
- No Starlark client methods. Unlike Redis or Postgres, there are no
cache.main.get()/cache.main.set()helpers in the Starlark runtime. Put an ordinary Memcached client in your SUT; Faultbox only intercepts the wire protocol to inject faults. - Binary protocol is not supported. Only the text protocol is parsed. Most production clients default to text — binary must be explicitly configured.