On this page

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")],
)
ParameterTypeDescription
commandstringMemcached command glob ("SET", "GET", "*")
keystringKey glob pattern ("session:*", "user:*")
messagestringResponse 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.