Skip to content

Instantly share code, notes, and snippets.

@ragnard
Last active February 20, 2021 16:27
Show Gist options
  • Save ragnard/4468291 to your computer and use it in GitHub Desktop.
Save ragnard/4468291 to your computer and use it in GitHub Desktop.
Using Redis for persistent memoization of Clojure functions
(ns util.redis
(:refer-clojure :exclude [memoize])
(:require [taoensso.carmine :as car]))
;; boilerplate stuff that is not in Carmine
(def ^:dynamic ^:private *pool*)
(def ^:dynamic ^:private *spec*)
(defmacro with-redis
"Establish the Redis connection pool and server spec to be used in
body, and execute body."
[pool spec & body]
`(binding [*pool* ~pool
*spec* ~spec]
~@body))
(defmacro exec-commands
"Execute Redis commands in body using connection pool and server
spec established using with-redis."
[& body]
`(car/with-conn *pool* *spec* ~@body))
;; memoization function
(defn memoize
"Similar to clojure.core/memoize, but uses Redis to store the
mapping from arguments to results. The key used will be :key,
concatenated with \":\" and the EDN representation of the arguments,
which is not a universally sound idea, but has the advantage of
being readable. An optional TTL in seconds can be specified
using :expire."
[f & {:keys [key expire]}]
{:pre [(string? key)]}
(fn [& args]
(let [memo-key (str key ":" (pr-str args))]
(if-let [val (exec-commands (car/get memo-key))]
val
(let [ret (apply f args)]
(exec-commands (car/set memo-key ret)
(when expire (car/expire memo-key expire)))
ret)))))
(ns example
(:require [util.redis :as redis]
[taoensso.carmine :as car]))
(def pool (car/make-conn-pool))
(def spec (car/make-conn-spec))
(defn add [x y] (+ x y))
(def memoized-add (redis/memoize add :key "add"))
(defn fetch-url [url] (slurp url))
(def memoized-fetch-url (redis/memoize fetch-url :key "urls" :expire 60))
(redis/with-redis pool spec
(println (memoized-add 1 1))
(println (memoized-add 41 1))
(time (memoized-fetch-url "http://www.guardian.co.uk"))
(time (memoized-fetch-url "http://www.guardian.co.uk")))
;; => 2
;; 42
;; "Elapsed time: 2743.294 msecs"
;; "Elapsed time: 1.134 msecs"

Redis state after evaluating usage.clj:

redis 127.0.0.1:6379> keys *
1) "add:(41 1)"
2) "add:(1 1)"
3) "urls:(\"http://www.guardian.co.uk\")"
redis 127.0.0.1:6379> get "add:(41 1)"
"42"
redis 127.0.0.1:6379> get "add:(1 1)"
"2"
redis 127.0.0.1:6379> ttl "urls:(\"http://www.guardian.co.uk\")"
(integer) 47
redis 127.0.0.1:6379> 
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment