memcache - a memcached client
Import with import "memcache.j" as memcache;. A client for a memcached server, speaking its classic text protocol over the net system library. Store with an expiration (set / add), read (get), remove (delete), count atomically (incr / decr), and re-arm a key's expiry (touch). memcached is a volatile cache - keys expire on their exptime and the server evicts under memory pressure - so it suits sessions, rate limits, and derived data, not a system of record. Because it uses net, this module needs the default jennifer binary.
On
jennifer-tiny: "needs the defaultjenniferbinary" refers to the stock tiny build, which ships without a network driver - not a TinyGo limitation. Ajennifer-tinyrebuilt with a network stack runs this module too; see the note onnetand TinyGo.
import "memcache.j" as memcache;
def mc as memcache.Session init memcache.connect(memcache.Options{
host: "127.0.0.1", port: 11211});
memcache.set($mc, "greeting", "hello", 60); # 60-second TTL
io.printf("%s\n", memcache.get($mc, "greeting")); # hello
memcache.quit($mc);Runnable: examples/modules/memcache_demo.j.
Surface
A session is stateful: connect, issue commands, quit. Every store carries an exptime in seconds (0 = never expire, until evicted).
| Call / type | Notes |
|---|---|
memcache.Options | host, port (plaintext; the text protocol has no auth / TLS). |
memcache.Session | A live session over one connection (from connect). |
memcache.connect(opts) | Open a session. |
memcache.set(session, key, value, exptime) | Store value (replacing any existing), TTL exptime seconds. |
memcache.setBytes(session, key, value, exptime) | Store a raw bytes value byte-for-byte (binary counterpart to set). |
memcache.add(session, key, value, exptime) | Store only if the key is absent; returns whether it stored. |
memcache.get(session, key) | The string value, or "" when the key is absent / expired. |
memcache.getBytes(session, key) | The raw bytes value (byte-exact; empty when absent) - for a binary value. |
memcache.getMulti(session, keys) | Fetch several keys in one round-trip -> map of string to string (missing keys absent). |
memcache.gets(session, key) | Read with a CAS token -> memcache.Item (value, cas, found), for a check-and-set. |
memcache.cas(session, key, value, exptime, casId) | Store only if the CAS token still matches -> "stored" / "exists" / "not_found". |
memcache.delete(session, key) | Remove the key; returns whether it existed. |
memcache.incr(session, key, delta) | Atomically add delta; the new value, or -1 if the key is absent. |
memcache.decr(session, key, delta) | Atomically subtract delta (not below 0); -1 if absent. |
memcache.touch(session, key, exptime) | Re-arm the key's expiry; returns whether it existed. |
memcache.quit(session) | End the session and close. |
add, incr, and the primitives caches are built from
add stores only if the key does not already exist and reports which happened - the atomic building block for a lock ("did I win the key?") or a create-if-new. incr / decr are atomic server-side counters; memcached will not create a missing counter, so incr on an absent key returns -1 and the caller decides whether to add an initial value:
def n as int init memcache.incr($mc, "hits", 1);
if ($n == -1) { # first hit this window
memcache.add($mc, "hits", "1", 60);
$n = 1;
}That incr-then-add shape is exactly what the planned ratelimit module builds on, and add + a TTL is what the planned session module uses to mint a session; both are small modules designed to sit on top of this client.
Errors
A protocol error reply (ERROR / CLIENT_ERROR / SERVER_ERROR) throws a catchable Error (kind "memcache"); set also throws if the server does not answer STORED. A network failure surfaces as the underlying net error.
Binary values and check-and-set
get / set speak text: a value is decoded as UTF-8, which is exact for JSON, numbers, and identifiers, and throws on a non-UTF-8 value (strict). For an arbitrary binary value, use setBytes (a bytes value stored byte-for-byte) and getBytes (bytes, never decoded); getBytes returns empty bytes for a missing key.
gets + cas are the optimistic-concurrency primitive: gets returns the value with a CAS token, and cas stores only if that token still matches (nobody else changed the value meanwhile). The loop is gets -> compute -> cas, retry on "exists":
def it as memcache.Item init memcache.gets($mc, "counter");
def next as string init convert.toString(convert.toInt($it.value) + 1);
def r as string init memcache.cas($mc, "counter", $next, 0, $it.cas);
# r is "stored" (done), "exists" (someone else won - re-gets and retry), or
# "not_found" (the key expired).getMulti(session, keys) fetches several keys in one round-trip as a map of string to string, with missing keys simply absent (test with maps.has).
Out of scope
- A working subset, not the full command set:
append/prependandstatsare reachable later; the basics plusgets/casand multi-keygetcover caches, sessions, counters, and locks. - No binary protocol and no SASL auth. Classic text protocol only.
- No connection pool. One
Sessionis one connection.
Timeouts
Every read carries an idle timeout (default 30 s) so a hung server fails with a catchable error instead of blocking the caller forever. connect sets Session.timeout (milliseconds); lower it for a tighter bound, or set it to 0 to disable:
def s as memcache.Session init memcache.connect($opts);
$s.timeout = 5000; # fail a read that stalls for 5 sSee also
- net.md - the transport
memcachebuilds on. - modules/index.md - the module catalog and import rules.