Memcache

How not to use memcached

Presenter

Jonathan Steinert

Software Engineer

hachi@sixapart.com

Six Apart

Infrastructure Engineering

http://www.sixapart.com

Background

Memcached http://www.danga.com/memcached/ was first written in early 2003 by Brad Fitzpatrick for LiveJournal http://www.livejournal.com.

More information on memcache's history can be found at http://en.wikipedia.org/wiki/Memcache.

The latest release is 1.4.0 http://www.danga.com/memcached/download.bml.

Architectural Primer

Caveats

Usage Primer

Initialize Connection

my $mc = Cache::Memcached->new(
    servers => [ '192.0.2.100:11211''192.0.2.200:11211' ],
);

Set value

Simple Value

Sets $value as a string for later reference via $key

$value = "foo";
$success = $mc->set$key$value );

# or possibly

$success = $mc->set$key$value$expiration );

Delete value

Deletes the value referenced by $key

$rv = $mc->delete$key );

How not to use it

Off we go now.

(Don't) read data from memcache to write back to a database.

Uncached

sub get {
    my $id = shift;

    my ($value) = db_query("SELECT column FROM stuff WHERE id = $id");

    return $value;
}

(Don't) read data from memcache to write back to a database.

Cached

sub get {
    my $id = shift;

    my $memkey = "value_$id";
    my $value = $mc->get($memkey);
    return $value if defined $value;

    ($value) = db_query("SELECT column FROM stuff WHERE id = $id");

    $mc->set($memkey$value3600);
    return $value;
}

(Don't) read data from memcache to write back to a database.

Oops, we missed this

sub update {
    my $id = shift;
    my $value = get($id);
    $value = complex_transformation($value)
    db_query("UPDATE stuff SET value=$value WHERE id=$id");
}

(Don't) read data from memcache to write back to a database.

Correctly cached

sub update {
    my $id = shift;
    my ($value) = db_query("SELECT column FROM stuff WHERE id = $id");
    $value = complex_transformation($value)
    db_query("UPDATE stuff SET value=$value WHERE id=$id");
    $mc->delete("value_$id"); # could be ->set
}

(Don't) forget the negative caching case.

Uncached

sub get {
    my $id = shift;
    
    my ($value) = db_query("SELECT column FROM stuff WHERE id = $id");
    
    die "Invalid ID" unless defined $value;
    
    return $value;
}

(Don't) forget the negative caching case.

Cached

sub get {
    my $id = shift;
    
    my $memkey = "obj_$id";
    my $value = $mc->get($memkey);
    
    if (defined $value) {
        return $value;
    }
    
    ($value) = db_query("SELECT column FROM stuff WHERE id = $id");
    
    die "Invalid ID" unless defined $value;
    
    $mc->set($memkey$value3600);
    return $value;
}

(Don't) forget the negative caching case.

Correctly cached

sub get {
    my $id = shift;
    
    my $memkey = "obj_$id";
    my $value = $mc->get($memkey);
    
    if (defined $value) {
        die "Invalid ID" if $value == -1;
        return $value;
    }
    
    ($value) = db_query("SELECT column FROM stuff WHERE id = $id");
    $value = -1 unless defined $value;
    
    $mc->set($memkey$value3600);
    
    die "Invalid ID" unless defined $value;
    
    return $value;
}

(Don't) set the cache timeouts too low.

sub get {
    my $id = shift;

    my $memkey = "value_$id";
    my $value = $mc->get($memkey);
    return $value if defined $value;

    ($value) = db_query("SELECT column FROM stuff WHERE id = $id");

    $mc->set($memkey$value1);
    return $value;
}

(Don't) forget to lock.

Lock around your memcache misses on expensive lookups. This prevents a stampede on your database.

(Don't) forget to lock.

Lock around your memcache misses on expensive lookups. This prevents a stampede on your database.

Locking with your database could be a bad thing though.

(Don't) forget to lock.

Cached

sub get {
    my $id = shift;
    
    my $memkey = "obj_$id";
    my $value = $mc->get($memkey);
    return $value if defined $value;

    ($value) = db_query("SELECT column FROM stuff WHERE id = $id");
    $mc->set($memkey$value3600);
    
    return $value;
}

(Don't) forget to lock.

Cached and locked

sub get {
    my $id = shift;

    my $memkey = "obj_$id";
    my $value = $mc->get($memkey);
    return $value if defined $value;

    get_lock($memkey);

    ($value) = db_query("SELECT column FROM stuff WHERE id = $id");
    $mc->set($memkey$value3600);
    release_lock($memkey);

    return $value;
}

(Don't) forget to lock.

Cached and locked better

sub get {
    my $id = shift;

    my $memkey = "obj_$id";
    my $value = $mc->get($memkey);
    return $value if defined $value;

    get_lock($memkey);

    $value = $mc->get($memkey);
    if (defined $value) {
        release_lock($memkey);
        return $value;
    }

    ($value) = db_query("SELECT column FROM stuff WHERE id = $id");
    $mc->set($memkey$value3600);
    release_lock($memkey);

    return $value;
}

(Don't) cache data too early.

Uncached

sub get {
    my $id = shift;
    
    my @rows = db_query("SELECT column FROM stuff");
    
    my @sorted_rows = sort @rows;
    
    return @sorted_rows;
}

(Don't) cache data too early.

Cached

sub get {
    my $id = shift;
    
    my $memkey = "obj_$id";
    my $rows = $mc->get($memkey);
    
    unless (defined $rows) {
        $rows = [ db_query("SELECT column FROM stuff") ];
        $mc->set($memkey$rows3600);
    }
    
    my @sorted_rows = sort @rows;
    
    return @sorted_rows;
}

(Don't) cache data too early.

Cached better

sub get {
    my $id = shift;
    
    my $memkey = "obj_$id";
    my $rows = $mc->get($memkey);
    
    if (defined $rows) {
        return @$rows;
    }
    
    $rows = [ sort db_query("SELECT column FROM stuff") ];
    
    $mc->set($memkey$rows3600);
    
    return @$rows;
}

(Don't) store large things

Memcache has a limit on the size of objects it can store, set at compile time. Usually this is 1MB.

(Don't) store things you are directly serving

(Don't) rely on memcache as a persistence layer

Memcache doesn't write data out or otherwise store it across restarts.

(Don't) use it as a Job Queue/Message Queue

More Resources

This presentation is not intended to be exhaustive. The official memcached presence can be found at http://www.danga.com/memcached/.

IRC: #memcached on irc.freenode.net

Stop by the booth.

Slides at http://hachi.kuiki.net/talks/memcache/

http://www.letmegooglethatforyou.com/?q=memcached+primer

(Do!) ask questions

Extras, if we have time

(Don't) store low read/write ratio data.

If you are thinking of storing a piece of data that is read, changed and written back most of the time, you probably shouldn't use memcache.

Increment / Decrement

Returns the post-increment/decrement value

$value = $mc->incr$key );

$value = $mc->decr$key );

$value = $mc->incr$key$amount );

$value = $mc->decr$key$amount );

Add / Replace

Succeeds only if $key doesn't exist in the cache currently.

$rv = $mc->add$key$value );

Succeeds only if $key exists in the cache currently.

$rv = $mc->replace$key$value );