Outdated egg!

This is an egg for CHICKEN 3, the unsupported old release. You're almost certainly looking for the CHICKEN 4 version of this egg, if it exists.

If it does not exist, there may be equivalent functionality provided by another egg; have a look at the egg index. Otherwise, please consider porting this egg to the current version of CHICKEN.

memcached

Introduction

Memcached is a high-performance daemon written in C for storing an in-memory cache. Clients connect to memcached servers using TCP sockets. Each server basically maintains a mapping from (string) keys to (u8vector) values, along with an optional flags integer that the application may use as it sees fit. Entries in the mapping may be expired to make room for others if they are not frequently accessed, or they may have an explicit expiry time, or they may be explicitly flushed from the cache.

Normally, a group of clients share a cluster of memcached servers; the clients agree on a mapping from keys to servers based upon a hash of the key, so that if one client stores something under a given key, other clients will be able to deduce which server that something is stored on, so they can update, get, or delete it.

This egg provides a low-level interface to access a single server, and a high-level interface to access a cluster of servers with tolerance for individual servers coming and going.

Both interfaces protect the TCP connections to the servers with mutexes, for thread-safety. The high-level interface takes pains to only lock connections in a global order, so it should not deadlock, while only locking one connection at a time, to maximise throughput in multithreaded clients.

Examples

Basic operations

  (require-extension memcached)

  ;; Connect to the server
  (define mcc (memcache-connect '(("localhost" 11211 1) ("localhost" 11212 1))))

  ;; Set two keys.
  ;; "a" (with flags 0 and expiry time 0) is set to #u8(65 65 65)
  ;; "b" (with the same flags and expiry time) is set to #u8(66 66 66)
  (memcache-set*! mcc '(("a" 0 0 #u8(65 65 65)) ("b" 0 0 #u8(66 66 66))))

  ;; Ask for three keys
  (memcache-get* mcc '("a" "b" "c"))
  ;; => (("a" 0 #u8(65 65 65)) ("b" 0 #u8(66 66 66)) #f)
  ;; We find "a" and "b" with the correct flags and value,
  ;; Nothing was found for "c" so we get #f

  ;; Delete "a".
  ;; The second argument is a deletion interval;
  ;; if this is non-zero, then attempts to store a new value for this
  ;; key will be rejected for the specified number of seconds, to prevent
  ;; accidental re-creation of the key.
  (memcache-delete! mcc "a" 0)

  ;; Ask again
  (memcache-get* mcc '("a" "b" "c"))
  ;; => (#f ("b" 0 #u8(66 66 66)) #f)
  ;; "a" has dutifully disappeared.

Using manual hashing

Sometimes, you will have a handy integer hash value already available for every key. In which case, you can save the memcache client the work of computing its own hash for the key.

To specify a manual hash value, use a list as the key. The first element should be the integer hash, the second value the actual key.

Note that your hash MUST be consistent. If you store something with a key of (1 "a"), then later attempts to access the same entry as "a" or (2 "a") will only exceed through sheer luck, if the hash computed or supplied happens to map to the same server.

  (require-extension memcached)

  ;; Connect to the server
  (define mcc (memcache-connect '(("localhost" 11211 1) ("localhost" 11212 1))))

  ;; Set two keys.
  ;; "a" (with flags 0 and expiry time 0) is set to #u8(65 65 65)
  ;; "b" (with the same flags and expiry time) is set to #u8(66 66 66)
  (memcache-set*! mcc '(((1 "a") 0 0 #u8(65 65 65)) ((2 "b") 0 0 #u8(66 66 66))))

  ;; Ask for three keys
  (memcache-get* mcc '((1 "a") (2 "b") (3 "c")))
  ;; => (("a" 0 #u8(65 65 65)) ("b" 0 #u8(66 66 66)) #f)
  ;; We find "a" and "b" with the correct flags and value,
  ;; Nothing was found for "c" so we get #f

Single-value shortcuts

The memcache egg provides functions to get, set, and delete arbitrary lists of entries at once, as demonstrated above. This allows internal optimisations; communications with each server may, in future, happen in parallel. Use these functions wherever you can, but when you only need to operate upon single values, there are convenience functions available (without the * in the name) to make this easier.

  (require-extension memcached)

  ;; Connect to the server
  (define mcc (memcache-connect '(("localhost" 11211 1) ("localhost" 11212 1))))

  ;; Set two keys.
  ;; "a" (with flags 0 and expiry time 0) is set to #u8(65 65 65)
  ;; "b" (with the same flags and expiry time) is set to #u8(66 66 66)
  (memcache-set! mcc "a" 0 0 #u8(65 65 65))
  (memcache-set! mcc "b" 0 0 #u8(66 66 66))

  ;; Ask for three keys
  (memcache-get mcc "a")
  ;; => (("a" 0 #u8(65 65 65))
  (memcache-get mcc "b")
  ;; => (("b" 0 #u8(66 66 66))
  (memcache-get mcc "c")
  ;; => #f

Practical caching

Say we have a function that takes a particularly long time to execute, but whose output is decided entirely by its inputs. We can make execution of this function more efficient if it is called more than once with the same input arguments, by caching its results. We use the arguments to the function as the key, after converting them to a string, and encoding the result of the function as a u8vector.

  (require-extension memcached)
  (require-extension srfi-1)

  (define mcc (memcache-connect '(("localhost" 11211 1) ("localhost" 11212 1))))

  (define (memoized-function arg)
    (define key (number->string arg))
    (define cached (memcache-get mcc key))
    (if (eq? cached #f)
      (let* ((computed (my-slow-function arg)) ; Cache miss
             (computed-as-string (number->string computed))
             (computed-as-u8vector
              (with-input-from-string
                computed-as-string
                (lambda ()
                  (read-u8vector (string-length computed-as-string))))))
        (memcache-add! mcc key 0 0 computed-as-u8vector)
        computed)
      (let* ((cached-as-u8vector (third cached)) ; Cache hit
             (cached-as-string
              (with-output-to-string
                (lambda () (write-u8vector cached-as-u8vector)))))
        (string->number cached-as-string))))

Manual implementation of memoization is rather ungainly; watch future versions of this egg for a neater interface.

Authors

Alaric Blagrave Snell-Pym of Kitten Technologies

License

Copyright (c) 2003-2007, Warhead.org.uk Ltd

All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

Neither the names of Warhead.org.uk Ltd, Snell Systems, nor Kitten Technologies, nor the names of their contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE

Requirements

None

Accessing a single memcached server

(single-memcache-connect HOSTNAME PORT)
Returns a memcache server object
(single-memcache-disconnect MCS)
Explicitly shuts down a memcache server object
(single-memcache-set! MCS KEY FLAGS EXPTIME VALUE)
Stores an entry

on the memcache server MCS with the given string KEY, FLAGS, EXPTIME, and VALUE. EXPTIME can be 0 to disable automatic expiry, otherwise it can either be a number of seconds into the future (if less than 60*60*24*30, or thirty days), or a UNIX timestamp of an explicit point in the future.

(single-memcache-add! MCS KEY FLAGS EXPTIME VALUE)
Stores an entry

on the memcache server MCS as before, but only if there is not already an entry with the same KEY.

(single-memcache-replace! MCS KEY FLAGS EXPTIME VALUE)
Stores an entry

on the memcache server MCS as before, but only if there IS already an entry with the same KEY.

(single-memcache-get MCS KEY)
Attempts to fetch the entry

with the given string KEY from server MCS. If the key is not found, returns #f. Otherwise, it returns a list with three elements: the key, the flags, and the value stored in that entry.

(single-memcache-get* MCS KEYS)
Attempts to find multiple entries

from the server MCS. KEYS must be a list of string keys. The result is a list where each element in the result is the result for the corresponding key in KEYS; one might imagine that this function works by mapping single-memcache-get over KEYS, although it does something a little more efficient under the hood.

(single-memcache-delete! MCS KEY TIME)
Deletes the given KEY

from server MCS. TIME, if non-zero, is used as a grace period in seconds during which attempts to store a value for that KEY are rejected, to help prevent accidental re-insertion of the KEY.

(single-memcache-incr! MCS KEY DELTA)
If KEY exists and its

value is an ASCII representation of a decimal integer, replaces the value with the ASCII decimal representation of the value plus DELTA. Otherwise, sets the value to be DELTA. Returns the new value, as an integer.

(single-memcache-decr! MCS KEY DELTA)
If KEY exists and its

value is an ASCII representation of a decimal integer, replaces the value with the ASCII decimal representation of the value minus DELTA, unless doing so would reduce the value below zero, in which case it is merely set to "0". Otherwise, sets the value to be DELTA. Returns the new value, as an integer.

(single-memcache-flush! MCS DELAY)
After DELAY seconds, deletes

all entries on this server.

Accessing a memcached cluster

In all of the following functions, KEY is either a string or a list of the form (integer string) specifying a string key and an explicit hash value, and VALUE is a u8vector.

(memcache-connect SERVERSPEC)
Creates a connection to a cluster of memcache

servers. The SERVERSPEC is a list of servers, each represented as a list containing a hostname, a port, and a weight, in turn. The weight is used to shape the fraction of entries that will be stored on this server; a server with a weight of 0 will not be used, while one with a weight of 2 will receive (on average) twice as many entries as one with a weight of 1.

It is vital that all clients using the same cluster as an identical SERVERSPEC; if not, they will assign entries to servers differently.

The return value is a memcache cluster object (MCC).

(memcache-disconnect MCC)
Explicitly shuts down the connections to the servers in MCC.
(memcache-set! MCC KEY FLAGS EXPTIME VALUE)
As with single-memcache-set!, stores an entry

in the cluster

(memcache-add! MCC KEY FLAGS EXPTIME VALUE)
As with single-memcache-add!, stores an entry

in the cluster, as long as one does not already exist

(memcache-replace! MCC KEY FLAGS EXPTIME VALUE)
As with single-memcache-replace!, provides new

flags, expiry time, and value for an existing entry in the cluster.

(memcache-set*! MCC ENTRIES)
Performs multiple memcache-set!s, with potential parallelism.

ENTRIES should be a list; each element should be a list of the form (KEY FLAGS EXPTIME VALUE).

(memcache-add*! MCC ENTRIES)
Performs multiple memcache-add!s, with potential parallelism.

ENTRIES should be a list; each element should be a list of the form (KEY FLAGS EXPTIME VALUE).

(memcache-replace*! MCC ENTRIES)
Performs multiple memcache-replace!s, with potential parallelism.

ENTRIES should be a list; each element should be a list of the form (KEY FLAGS EXPTIME VALUE).

(memcache-get MCC KEY)
Finds the entry with the specified KEY in the cluster MCC. If there is no such

entry, returns #f; otherwise, returns a list of the form (KEY FLAGS VALUE).

(memcache-get* MCC KEYS)
Given a list of KEYS, searches the cluster for entries with those keys, and

returns a list of results. Conceptually similar to mapping memcache-get over KEYS, each element of the resulting list corresponds to an element in KEYS, and will either be #f for failure or a list of the form (KEY FLAGS VALUE) for success.

: (memcache-incr! MCC KEY DELTA) : As per single-memcache-incr!, attempts to add DELTA to the ASCII-encoded decimal value corresponding to KEY, and returns the result as an integer.

: (memcache-decr! MCC KEY DELTA) : As per single-memcache-decr!, attempts to subtract DELTA to the ASCII-encoded decimal value corresponding to KEY (with a floor of zero), and returns the result as an integer.

: (memcache-delete! MCC KEY TIME) : Attempts to delete the entry with the specified KEY from the cluster, with a grace period of TIME seconds during which attempts to add a new entry with the same KEY will fail.

: (memcache-delete*! MCC KEYS TIME) : Attempt to delete every entry with a key in the list of KEYS, with the specified grace period TIME.

: (memcache-flush! MCC DELAY) : Ask every server in the cluster to delete all keys it holds, with a delay of DELAY seconds between each server, as flushing every server in the cluster at once could put a significant burden on the resource the cache is protecting.

Dependencies and Unit Tests

This egg is written entirely in native Scheme, and doesn't need any C libraries or anything. It opens its own TCP sockets and works from there.

It doesn't need any other eggs, but the unit tests in the test directory use the test egg, and will only work if memcached is installed on the local machine. The unit tests spawn their own memcached instances to talk to, then kill one off and test that things still work properly - so please make sure that ports 11211 and 11212 are free. If the tests go badly wrong, you may need to run "killall memcached" to clear up the daemons (or explicitly kill the two PIDs printed out at the start of the test run).

Version History