-
Notifications
You must be signed in to change notification settings - Fork 63
Using
note: see the page on including core.cache before you begin this section
To use the cache implementations or extend the core.cache protocols you first need to require the proper namespace:
(require '[clojure.core.cache :as cache])
Next you should create an instance of a specific cache type, optionally seeded:
(def C (cache/fifo-cache-factory {:a 1, :b 2}))
Note that caches are immutable. To actually use it, you need to put it somewhere you can give it a lifecyle (see below “Putting it together”).
To find a value in a map by its key you have a couple choices:
(cache/lookup C :a)
;=> 1
(:b C)
;=> 2
To ensure the proper cache policies are followed for each specific type, the following has?/hit/miss
pattern should be used:
(if (cache/has? C :c) ;; has? checks that the cache contains an item
(cache/hit C :c) ;; hit returns a cache with any relevant internal information updated
(cache/miss C :c 42)) ;; miss returns a new cache with the new item and without evicted entries
;=> {:a 1, :b 2, :c 42}
Using the has?/hit/miss
pattern ensures that the thresholding and eviction logic for each implementation works properly. The through
and through-cache
functions in clojure.core.cache
encapsulate this logic to make it easier to follow this pattern. Avoid this pattern at your own risk.
Finally, to explicitly evict an element in a cache, use the evict
function:
(cache/evict C :b)
;=> {:a 1}
For specific information about eviction policies and thresholds, view the specific documentation for each cache type listed in the next section.
A simple way to get a cache that actually changes is to put it into an atom:
(defonce cache-store (atom (cache/lru-cache-factory {})))
Now, assuming that you have a function retrieve-data
which actually gets your data, you do the has?-hit-miss triad in a transaction, then lookup on the returned cache:
(defn get-data [key]
(cache/lookup (swap! cache-store
#(if (cache/has? % key)
(cache/hit % key)
(cache/miss % key (retrieve-data key))))
key))
;; or
(defn get-data [key]
(cache/lookup (swap! cache-store cache/through-cache key retrieve-data)
key))
Be aware that swap!
can retry so it's possible this may call retrieve-data
multiple times. In the pathological case, this could cause a cache stampede. It's also possible for the above to return nil
rather than a newly-computed value because cache/lookup
can cause invalidation in some cache implementations (such as TTL) -- so the inner cache/has?
call succeeds, and the swap!
returns the "hit" cache, but then the cache/lookup
call fails because the item has expired since the cache/has?
call!
The clojure.core.cache.wrapped
namespace addresses all of this.
This namespace has an identical set of functions to clojure.core.cache
but they all traffic in terms of caches wrapped in atoms.
(require '[clojure.core.cache.wrapped :as cw])
(defonce cache-store (cw/lru-cache-factory {})) ; factory returns new cache wrapped in atom
(defn get-data [key]
(cw/lookup-or-miss cache-store key retrieve-data))
Behind the scenes, cw/lookup-or-miss
is similar to the code above except that it guarantees retrieve-data
is only called at most once, and it automatically retries the whole operation if the outer lookup causes cache invalidation.
All the factory functions in the wrapped namespace return new caches wrapped in atoms. All the other API functions in the wrapped namespace accept an atom containing a cache and perform swap!
on it if the operation is mutating (e.g., evict
, hit
, miss
, seed
, through
, through-cache
-- thus they return the updated cache value itself). lookup
accepts an atom containing a cache and returns the associated value from the cache. has?
accepts an atom containing a cache and return a Boolean indicating the presence of the item in the cache.
core.cache comes with a number of builtin immutable cache implementations, including (click through for specific information):
- FIFO cache
- LRU cache
- LU cache
- TTL cache
- LIRS cache
- Function-backed cache (work in progress)
- Soft-reference cache (work in progress)
The core.cache implementations are backed by any map-like object. Additionally, each cache implements the Clojure map behaviors and can therefore serve as special maps or even as backing stores for other cache implementations. For caches taking a limit argument, the eviction policies tend not to apply until the limit has been exceeded.
See the section on creating custom caches for more information.
See the section on composing caches for more information.