A fast and robust Common Lisp client for Redis
CL-REDIS — A fast and robust Common Lisp client for Redis
(tested with Redis version 3.0.0 (2.9.104 to be precise))
- Make sure a Redis server is running.
- Connect to the server to the given host and port with
(redis:connect :host <host> :port <port>)(
- Interact with the server using Redis commands from the
CL-USER> (red:ping) "PONG"
- Disconnect from the server with
- Alternatively, wrap the whole interaction session in
with-connectionmacro, which accepts the same arguments as
connectdoes, opens a socket connection, executes the body of the macro with the current connection (
*connection*) bound to this new connection, and ensures that the connection is closed afterwards.
The system provides 2 packages:
RED. All the
functionality is available from the
REDIS package. Not to cause
symbol clashes, Redis commands are defined in this package with a
prefix (which defaults to
red- and is set at compilation time).
RED is a syntactic sugar — it just provides the Redis
commands without a prefix. So it is not intended to be imported to
avoid symbol conflicts with package
COMMON-LISP — just use the
package-qualified symbol names: i.e. the same Redis command (for
GET) can be called as
RED-GET (if you import the
Available through quicklisp.
Debugging and error recovery
T, all client-server communications will be
echoed to the stream
*echo-stream*, which defaults to
Error handling is mimicked after
In particular, whenever an error occurs that breaks the communication stream,
a condition of type
redis-connection-error is signalled offering
:reconnect restart. If it is selected the whole Redis command will be
resent, if the reconnection attempt succeeds.
connect checks if a connection to Redis is already established,
and offers two restarts (
:replace) if this is the case.
When the server respondes with an error reply
(i.e., a reply that starts with
a condition of type
redis-error-reply is signalled.
There's also a high-level
that tries to do the right thing™
(i.e. automatically reopen the connection once, if it is broken).
Since there's no special command to receive messages from Redis via PubSub here's how you do it:
(bt:make-thread (lambda () (with-connection () (red:subscribe "foo") (loop :for msg := (expect :anything) :do (print msg)))) "pubsub-listener")
To publish, obviously:
(with-connection () (red:publish "foo" "test"))
For better performance Redis allows to pipeline commands
and delay receiving results until the end,
and process them all in oine batch afterwards.
To support that there's
Compare execution times in the following examples
(with pipelining and without: 6.567 secs vs. 2023.924 secs!):
(let ((names (let (acc) (dotimes (i 1000 (nreverse acc)) (push (format nil "n~a" i) acc)))) (vals (let (big-acc) (dotimes (i 1000 (nreverse big-acc)) (let (acc) (dotimes (i (random 100)) (push (list (random 10) (format nil "n~a" i)) acc)) (push (nreverse acc) big-acc)))))) (time (redis:with-connection () (redis:with-pipelining (loop :for k :in names :for val :in vals :do (dolist (v val) (apply #'red:zadd k v))) (red:zunionstore "result" (length names) names) (red:zrange "result" 0 -1)))) ;; Evaluation took: ;; 6.567 seconds of real time ;; 3.900243 seconds of total run time (3.200200 user, 0.700043 system) (time (redis:with-connection () (loop :for k :in names :for val :in vals :do (dolist (v val) (apply #'red:zadd k v))) (red:zunionstore "result" (length names) names) (red:zrange "result" 0 -1)))) ;; Evaluation took: ;; 2023.924 seconds of real time ;; 3.560222 seconds of total run time (2.976186 user, 0.584036 system)
with-pipelining calls theoretically may nest,
but the results will only be available to the highest-level pipeline,
all the nested pipelines will return :PIPELINED.
So a warining is signalled in this situation.
expect implement the Redis protocol
according to the spec.
tell specifies how a request to Redis is formatted,
expect — how the response is handled.
The best way to implement another method on
expect is usually with
def-expect-method, which arranges reading data from the socket
and provides a variable
reply, which holds the decoded reply data
from the server with the initial character removed. For example:
(def-expect-method :ok (assert (string= reply "OK")) reply)
Redis operations are defined as ordinary functions by
for which only arguments and return type should be provided.
def-cmd prefixes all the defined functions' names with
which defaults to
(Note, that setting of
*cmd-prefix* will have its effects at compile time).
It also exports them from
RED package without the prefix.
An example of command definition is given below:
(def-cmd KEYS (pattern) :multi "Return all the keys matching the given pattern.")
commands.lisp for all defined commands.
- The following commands are not implemented,
because they are not intended for use in client:
DEBUG OBJECT, and
- Support for Unix domain sockets — planned
- Consistent hashing isn't built-in. Actually, such thing is orthogonal to the functionality of this library and, probably, should be implemented in a separate library.
- Connection pooling is also not implemented, because in the presence of
with-persistent-connectionit is actually not needed so much. Persistent connections are more simple, efficient and less error-prone for dedicated threads. But there are other use-cases for pooling, so it will probably be implemented in future releases.
The library is developed and maintained by Vsevolod Dyomkin firstname.lastname@example.org.
At the initial stages Alexandr Manzyuk email@example.com developed the connection handling code following the implementation in Postmodern. It was since partially rewritten to accommodate more advanced connection handling strategies, like persistent connection.
MIT (See LICENSE file for details).