Skip to content

1 Getting started

Peter Taoussanis edited this page May 28, 2024 · 3 revisions

Setup

Dependency

Add the relevant dependency to your project:

Leiningen: [com.taoensso/carmine               "x-y-z"] ; or
deps.edn:   com.taoensso/carmine {:mvn/version "x-y-z"}

And setup your namespace imports:

(ns my-app (:require [taoensso.carmine :as car :refer [wcar]]))

Configure connections

You'll usually want to define a single connection pool, and one connection spec for each of your Redis servers, example:

(defonce my-conn-pool (car/connection-pool {})) ; Create a new stateful pool
(def     my-conn-spec {:uri "redis://redistogo:[email protected]:9475/"})
(def     my-wcar-opts {:pool my-conn-pool, :spec my-conn-spec})

This my-wcar-opts can then be provided to Carmine's wcar ("with Carmine") API:

(wcar my-wcar-opts (car/ping)) ; => "PONG"

wcar is the main entry-point to Carmine's API. See its docstring for lots more info on connection options!

You can create a wcar partial for convenience:

(defmacro wcar* [& body] `(car/wcar my-wcar-opts ~@body))

Usage

Command pipelines

Calling multiple Redis commands in a single wcar body uses efficient Redis pipelining under the hood, and returns a pipeline reply (vector) for easy destructuring:

(wcar*
  (car/ping)
  (car/set "foo" "bar")
  (car/get "foo")) ; => ["PONG" "OK" "bar"] (3 commands -> 3 replies)

If the number of commands you'll be calling might vary, it's possible to request that Carmine always return a destructurable pipeline-style reply:

(wcar* :as-pipeline (car/ping)) ; => ["PONG"] ; Note the pipeline-style reply

If the server replies with an error, an exception is thrown:

(wcar* (car/spop "foo"))
=> Exception ERR Operation against a key holding the wrong kind of value

But what if we're pipelining?

(wcar*
  (car/set  "foo" "bar")
  (car/spop "foo")
  (car/get  "foo"))
=> ["OK" #<Exception ERR Operation against ...> "bar"]

Serialization

The only scalar type native to Redis is the byte string. But Carmine uses Nippy under the hood to seamlessly support all of Clojure's rich data types:

(wcar*
  (car/set "clj-key"
    {:bigint (bigint 31415926535897932384626433832795)
     :vec    (vec (range 5))
     :set    #{true false :a :b :c :d}
     :bytes  (byte-array 5)
     ;; ...
     })

  (car/get "clj-key"))

=> ["OK" {:bigint 31415926535897932384626433832795N
          :vec    [0 1 2 3 4]
          :set    #{true false :a :c :b :d}
          :bytes  #<byte [] [B@4d66ea88>}]

Types are handled as follows:

Clojure type Redis type
Strings Redis strings
Keywords Redis strings
Simple numbers Redis strings
Everything else Auto de/serialized with Nippy

You can force automatic de/serialization for an argument of any type by wrapping it with car/freeze.

Command coverage

Carmine uses the official Redis command spec to auto-generate a Clojure function for every Redis command. These are accessible from the main Carmine namespace:

;; Example: car/sort is a Clojure function for the Redis SORT command
(clojure.repl/doc car/sort)

=> "SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination]

Sort the elements in a list, set or sorted set.

Available since: 1.0.0.

Time complexity: O(N+M*log(M)) where N is the number of elements in the list or set to sort, and M the number of returned elements. When the elements are not sorted, complexity is currently O(N) as there is a copy step that will be avoided in next releases."

Each Carmine release will always use the latest Redis command spec.

But if a new Redis command hasn't yet made it to Carmine, or if you want to use a Redis command not in the official spec (a Redis module command for example) - you can always use Carmine's redis-call function to issue arbitrary Redis commands.

So you're not constrained by the commands provided by Carmine.

Commands are functions

Carmine's auto-generated command functions are real functions, which means that you can use them like any other Clojure function:

(wcar* (doall (repeatedly 5 car/ping)))
=> ["PONG" "PONG" "PONG" "PONG" "PONG"]

(let [first-names ["Salvatore"  "Rich"]
      surnames    ["Sanfilippo" "Hickey"]]
  (wcar* (mapv #(car/set %1 %2) first-names surnames)
         (mapv car/get first-names)))
=> ["OK" "OK" "Sanfilippo" "Hickey"]

(wcar* (mapv #(car/set (str "key-" %) (rand-int 10)) (range 3))
       (mapv #(car/get (str "key-" %)) (range 3)))
=> ["OK" "OK" "OK" "OK" "0" "6" "6" "2"]

And since real functions can compose, so can Carmine's.

By nesting wcar calls, you can fully control how composition and pipelining interact:

(let [hash-key "awesome-people"]
  (wcar*
    (car/hmset hash-key "Rich" "Hickey" "Salvatore" "Sanfilippo")
    (mapv (partial car/hget hash-key)
      ;; Execute with own connection & pipeline then return result
      ;; for composition:
      (wcar* (car/hkeys hash-key)))))
=> ["OK" "Sanfilippo" "Hickey"]

Performance

Redis is probably most famous for being fast. Carmine hold up its end and usually performs within ~10% of the official C redis-benchmark utility despite offering features like command composition, reply parsing, etc.

Benchmarks are included in the Carmine GitHub repo and can be easily run from your own environment.

Clone this wiki locally