NAME

EV::Memcached - asynchronous memcached client on libev

SYNOPSIS

use EV;
use EV::Memcached;

my $mc = EV::Memcached->new(
    host     => '127.0.0.1',
    port     => 11211,
    on_error => sub { warn "memcached: @_" },
);

$mc->set('foo', 'bar', sub {
    my ($ok, $err) = @_;
    warn "set failed: $err" if $err;

    $mc->get('foo', sub {
        my ($value, $err) = @_;
        print "foo = $value\n";   # bar
        $mc->disconnect;
    });
});

EV::run;

DESCRIPTION

A pure-XS memcached client built on the EV event loop. Implements the memcached binary protocol directly -- no external C client library is needed. All commands are non-blocking; results are delivered through callbacks dispatched by the EV loop.

Highlights:

  • Binary protocol with pipelining, multi-get via GETKQ + NOOP fence, and fire-and-forget quiet variants (SETQ, FLUSHQ).

  • TCP and Unix socket transports, optional SASL PLAIN authentication (automatic re-auth on reconnect).

  • Flow control via max_pending, local waiting_queue with optional replay across reconnects, configurable connect / command / waiting timeouts.

  • Predictable lifecycle: pending callbacks always fire (with the disconnect reason on teardown), DESTROY is reentrancy-safe across callback contexts.

AnyEvent applications can use this module unchanged, since AnyEvent runs on top of EV when EV is loaded.

ENCODING

This module treats all keys and values as byte strings. Encode UTF-8 strings before passing them in:

use Encode;
$mc->set(foo => encode_utf8($val), sub { ... });
$mc->get('foo', sub {
    my $val = decode_utf8($_[0]);
});

CALLBACK CONVENTIONS

Every command callback receives ($result, $err). On success $err is undef; on protocol error $err holds a string like NOT_STORED or NOT_FOUND. On a cache miss for get/gat, both arguments are undef (a miss is not an error).

Callback exceptions are caught with G_EVAL and reported via warn so a stray die never unwinds the libev event loop. To abort on errors, set a flag and break the loop; do not rely on die propagating out of a callback.

CONSTRUCTOR

new(%options)

Construct an instance. All options are optional; with none, the client is unconfigured and you must call connect / connect_unix later. Specifying host (or path) at construction time triggers an immediate non-blocking connect.

my $mc = EV::Memcached->new(
    host     => '127.0.0.1',
    port     => 11211,
    on_error => sub { warn "@_" },
);

Connection

host => $str
port => $int (default 11211)

TCP host and port. Mutually exclusive with path.

path => $str

Unix socket path. Mutually exclusive with host.

loop => $ev_loop

EV loop to attach to. Default: EV::default_loop.

priority => $num (-2 to +2)

EV watcher priority. Higher = serviced before other EV watchers.

keepalive => $seconds

TCP keepalive idle time. Set to 0 to disable. Ignored on Unix sockets.

Timeouts and flow control

connect_timeout => $ms

Abort an in-progress non-blocking connect after this many milliseconds. 0 = no timeout (default). Does not apply to Unix sockets or to immediately-completing localhost connects.

command_timeout => $ms

Disconnect with "command timeout" error if no response arrives within this interval. The timer resets on every response from the server. 0 = no timeout (default).

max_pending => $num

Cap on concurrent in-flight commands. Excess commands are held in a local waiting queue. 0 = unlimited (default).

waiting_timeout => $ms

Maximum time a command may sit in the waiting queue before its callback fires with "waiting timeout". 0 = unlimited (default).

resume_waiting_on_reconnect => $bool

If true, the waiting queue survives a disconnect and is replayed on reconnect. Default: false.

Reconnect

reconnect => $bool

Enable automatic reconnection on transport errors.

reconnect_delay => $ms (default 1000)

Delay before each reconnect attempt. The delay is always honored via a timer; setting it to 0 still defers through the event loop (no synchronous retry recursion).

max_reconnect_attempts => $num

Give up after this many consecutive failures and emit "max reconnect attempts reached". 0 = unlimited (default).

Authentication

username => $str
password => $str

SASL PLAIN credentials. When both are set, the client authenticates after every successful connect (and reconnect). Pre-connect commands sit in the waiting queue until SASL completes. Requires a memcached build with SASL support and the -S flag.

Event handlers

on_error => $cb->($errstr)

Connection-level error callback. Default: write the message to STDERR via warn. Callbacks are run under G_EVAL, so any die in a custom handler is demoted to a warning -- use an explicit flag if you need to terminate.

on_connect => $cb->()

Fires once the connection is fully established (after SASL, when applicable).

on_disconnect => $cb->()

Fires after a disconnect, after pending callbacks have been cancelled. For server-initiated close, this fires before on_error.

LIFECYCLE

connect($host, [$port])

Connect to a TCP host. Port defaults to 11211. Stops any pending auto-reconnect timer and clears any prior path setting.

connect_unix($path)

Connect via Unix domain socket. Stops any pending auto-reconnect timer and clears any prior host setting.

disconnect

Disconnect cleanly. Cancels any pending reconnect, drains pending command callbacks with (undef, "disconnected"), then fires on_disconnect. For an intentional disconnect, on_error does not fire -- that distinction lets you tell user-initiated teardown from server-side close.

is_connected

Returns true while a session is established or in progress (TCP handshake / SASL exchange). Commands issued in the connecting phase are queued and sent on completion.

quit([$cb])

Send a memcached QUIT and let the server close the connection.

STORAGE COMMANDS

Each command's callback receives ($result, $err). $result is 1 on success.

set($key, $value, [$expiry, [$flags,]] [$cb])

Store unconditionally. Without $cb this becomes fire-and-forget (SETQ): no response is received and any server-side failure is silently dropped.

add($key, $value, [$expiry, [$flags,]] [$cb])

Store only if the key does not exist. Errors with NOT_STORED if present.

replace($key, $value, [$expiry, [$flags,]] [$cb])

Store only if the key already exists. Errors with NOT_STORED if absent.

cas($key, $value, $cas, [$expiry, [$flags,]] [$cb])

Compare-and-swap. The $cas token comes from a prior gets / gats / mgets. Errors with EXISTS on token mismatch or NOT_FOUND if the key disappeared.

append($key, $data, [$cb])

Append bytes to an existing value. Errors with NOT_STORED if the key does not exist. Without $cb, errors are silently dropped.

prepend($key, $data, [$cb])

Prepend bytes to an existing value. Same error and fire-and-forget semantics as append.

delete($key, [$cb])

Delete a key. Errors with NOT_FOUND if absent.

RETRIEVAL COMMANDS

get($key, [$cb->($value, $err)])

Retrieve a value. On a cache miss, both $value and $err are undef -- a miss is not an error.

gets($key, [$cb->($info, $err)])

Like get but returns { value => ..., flags => ..., cas => ... }.

mget(\@keys, [$cb->(\%values, $err)])

Multi-get, internally pipelined as a sequence of GETKQ packets terminated by a NOOP fence. Returns a hash containing only the keys that were hits:

$mc->mget([qw(k1 k2 k3)], sub {
    my ($values, $err) = @_;
    # $values = { k1 => 'v1', k3 => 'v3' }   # k2 was a miss
});

mgets(\@keys, [$cb->(\%info, $err)])

Like mget but each value carries metadata:

$mc->mgets([qw(k1 k2)], sub {
    my ($info, $err) = @_;
    # $info = { k1 => { value => 'v', flags => 0, cas => 123 } }
});

ATOMIC COUNTERS

incr($key, [$delta, [$initial, [$expiry,]]] [$cb->($new_value, $err)])

Atomic increment. $delta defaults to 1. $expiry defaults to 0xFFFFFFFF, which means "do not auto-create" (the call then errors with NOT_FOUND). Pass any other expiry to auto-create with $initial:

$mc->incr('counter', 1, sub { ... });          # require existing
$mc->incr('counter', 1, 100, 300, sub { ... }); # auto-create at 100, 5min TTL

$new_value is the post-increment counter value.

decr($key, [$delta, [$initial, [$expiry,]]] [$cb->($new_value, $err)])

Atomic decrement. Memcached clamps the result at 0 (never negative). Same auto-create semantics as incr.

EXPIRATION

touch($key, $expiry, [$cb])

Update an existing key's expiration without fetching the value. Errors with NOT_FOUND if absent.

gat($key, $expiry, [$cb->($value, $err)])

Get-and-touch: retrieve and update expiration in one round-trip. Same miss semantics as get.

gats($key, $expiry, [$cb->($info, $err)])

Get-and-touch with metadata. Same shape as gets.

SERVER COMMANDS

flush([$expiry,] [$cb])

Invalidate every item. Optional delay in seconds before the flush takes effect. Without $cb, sent as fire-and-forget (FLUSHQ).

noop([$cb])

No-operation round-trip. Useful as a pipeline fence to wait until all previously-sent commands have been processed.

version([$cb->($version, $err)])

Server version string.

stats([$name,] [$cb->(\%stats, $err)])

Server statistics. Without $name, returns the default stats group. Common groups: settings, items, sizes, slabs, conns.

AUTHENTICATION

sasl_auth($username, $password, [$cb])

Authenticate via SASL PLAIN. Auto-invoked on connect when both username and password were passed to the constructor; call manually only when authenticating after a no-auth construction.

sasl_list_mechs([$cb->($mechs, $err)])

Query the server's supported mechanisms; returns a space-separated string such as "PLAIN".

LOCAL CONTROL

skip_pending

Drain the in-flight queue, firing every callback with (undef, "skipped"). The connection itself is left intact.

skip_waiting

Same, but for the local waiting queue (commands not yet sent).

pending_count

Number of commands sent and awaiting a response.

waiting_count

Number of commands held in the local waiting queue (because the connection is not ready, SASL is in progress, or max_pending is saturated).

ACCESSORS

Every option from new has a getter/setter of the same name. Calling without arguments reads the current value; with one argument it writes and (where meaningful, e.g. keepalive) takes effect immediately.

connect_timeout([$ms])
command_timeout([$ms])
max_pending([$num])
waiting_timeout([$ms])
resume_waiting_on_reconnect([$bool])
priority([$num])
keepalive([$seconds])
reconnect_enabled

Read-only; configure via reconnect.

reconnect($enable, [$delay_ms], [$max_attempts])

Reconfigure auto-reconnect at runtime.

on_error([$cb])
on_connect([$cb])
on_disconnect([$cb])

Get/set the corresponding handler. Pass undef to clear.

DESTRUCTION

If $mc goes out of scope while commands are in flight or queued, every pending and waiting callback fires once with (undef, "disconnected"). This holds whether you call disconnect first or simply drop the reference.

The clean shutdown idiom is:

$mc->disconnect;   # drains queues, fires on_disconnect
undef $mc;

If a callback closes over $mc (a common mistake -- every reference inside a callback closure keeps the object alive), break the cycle before dropping the outer reference:

$mc->on_error(undef);
$mc->on_connect(undef);
$mc->on_disconnect(undef);
undef $mc;

DESTROY is reentrant-safe: if a callback fired during teardown drops the last external reference to a separate EV::Memcached, that object's DESTROY is correctly deferred and run once unwound.

BINARY PROTOCOL NOTES

The wire format is the memcached binary protocol -- a 24-byte header plus body, with each request tagged by an opaque field used for in-flight matching and pipelining. Multi-get is sent as a run of GETKQ packets ending in a NOOP fence: the server emits a response only on hit, and the NOOP reply terminates the batch. Fire-and-forget set/flush use the quiet SETQ / FLUSHQ opcodes so the server sends no response at all.

Commands that can legitimately fail (add, replace, delete, incr, ...) always use the non-quiet opcode so error responses are consumed by the client even when the user passed no callback. Keys are validated against the 250-byte protocol limit before any bytes go on the wire.

BENCHMARKS

Numbers from bench/benchmark.pl on Linux, TCP loopback, 100-byte values, Perl 5.40, memcached 1.6.41:

                     50K cmds    200K cmds
Pipeline SET           213K        68K ops/sec
Pipeline GET           216K        67K ops/sec
Mixed workload         226K        69K ops/sec
Fire-and-forget SET    1.13M      1.29M ops/sec  (SETQ)
Multi-get (GETKQ)      1.30M      1.17M ops/sec  (per key)
Sequential round-trip   41K        38K ops/sec

Fire-and-forget is roughly 5x faster than callback mode because there is no per-command Perl SV allocation. Multi-get is the fastest read path since misses generate no traffic. Callback-mode throughput drops as batch size grows because SV allocation for closures dominates; realistic workloads (interleaved sends and receives) stay close to the 50K-command column.

max_pending overhead (200K commands):

unlimited        ~131K ops/sec
max_pending=500  ~126K ops/sec
max_pending=100  ~120K ops/sec
max_pending=50   ~117K ops/sec

Override BENCH_COMMANDS, BENCH_VALUE_SIZE, BENCH_HOST, and BENCH_PORT to retune.

SEE ALSO

EV, AnyEvent, Cache::Memcached::Fast, Memcached::Client, https://github.com/memcached/memcached/wiki/BinaryProtocolRevamped.

AUTHOR

vividsnow

LICENSE

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.