Copyright | (c) Adam Conner-Sax 2019 |
---|---|
License | BSD-3-Clause |
Maintainer | adam_conner_sax@yahoo.com |
Stability | experimental |
Safe Haskell | None |
Language | Haskell2010 |
This module defines a key/value store (Polysemy) effect. Rather than return the usual Maybe v
from a lookup,
the cache returns a Maybe (WithCacheTime v)
, where WithCacheTime a
wraps a value of type a along with a
time-stamp (of type UTCTime
). This module provides a thread-safe in-memory implementation as well as
a disk-based persistent implementation and a combination of the two, where the disk-based layer sits behind the
in-memory layer. In our use case, the stored values will be arrays of bytes, the result of serializing
whatever data we wish to cache.
WithCacheTime
is intended to simplify tracking dependencies among cached computations. For example, imagine
you have two long running computations which you wish to cache so you need only run those computations once:
computeA :: m a computeA = ... computeB :: m b computeB = ... cachedA :: WithCacheTime m a cachedA :: retrieveOrMake serialize "a.bin" (pure ()) (const computeA) cachedB :: WithCacheTime m b cachedB = retrieveOrMake serialize "b.bin" (pure ()) (const computeB)
and you have a computation which depends on a
and b
and should also be cached, but we
want to make sure it gets recomputed if either a
or b
do. We use the applicative instance of
WithCacheTime
to combine cached results into and inject them into later computations while
taking into account the newest time-stamp among the dependencies:
computeC :: a -> b -> m c computeC a b = ... cDeps :: WithCachedTime m (a, b) cDeps = (,) $ cachedA <*> cachedB cachedC :: WithCacheTime m c cachedC = retrieveOrMake serialize "c.bin" cDeps $ \(a, b) -> computeC a b
As with cachedA
and cachedB
, cachedC
will run the computation if the key, "c.bin" in this case,
is absent from the cache.
In addition, cachedC
will be recomputed even if it is in the cache, if the time-stamp of the cached value
is older than either the time stamp of cachedA
or cachedB
.
WithCacheTime m a
holds the time-stamp and a monadic computation which will produce an a
. This allows
deferral of the deserialization of cached data until we know that we need to use it. In the example above,
suppose a
is retrieved from cache, and b
is computed fresh. cachedA
holds a timestamp
(the modification time of the file in cache or the time a was cached in memory) and a monadic
computation which will deserialize the cached byte array retrieved for a. cachedB
holds a time-stamp
(the time the computation of b completes) and the trivial monadic action return b
. Since b
was
just computed, the cached c
is outdated and will be recomputed. At that point a
is deserialized, b
is unwrapped and thse are given to the function to compute c
, which is then
stored in cache as well as returned in the WithCacheTime m c
, holding a new time-stamp.
If multiple threads attempt to lookup or retrieveOrMake
at the same key
at close to the same time, the first request will proceed,
loading from cache if possible, and the other threads will block until
the in-memory cache is populated or the first thread fails to fill in data.
This is intended to save CPU in the relatively common case that, e.g., several threads are launched to analyze the same data. The functions which load that data from on-disk-cache or produce it from other analyses need only be run once. Using the cache to facilitate this sharing still requires each thread to deserialize the data. If that cost is significant, you may want to compute the data before launching the threads.
NB: Should the action given to create the data, the (b -> m a)
argument of retrieveOrMake
somehow
fail, this may lead to a situation where it runs on the first thread, fails, then runs on all the other threads
simultaneously, presumably failing all those times as well.
Examples are available, and might be useful for seeing how all this works.
Synopsis
- data Cache k v m a
- data WithCacheTime m a
- type ActionWithCacheTime r a = WithCacheTime (Sem r) a
- withCacheTime :: Maybe UTCTime -> m a -> WithCacheTime m a
- onlyCacheTime :: Applicative m => Maybe UTCTime -> WithCacheTime m ()
- ignoreCacheTime :: WithCacheTime m a -> m a
- ignoreCacheTimeM :: Monad m => m (WithCacheTime m a) -> m a
- cacheTime :: WithCacheTime m a -> Maybe UTCTime
- wctMapAction :: (m a -> n b) -> WithCacheTime m a -> WithCacheTime n b
- encodeAndStore :: (Show k, Member (Cache k ct) r, LogWithPrefixesLE r) => Serialize CacheError r a ct -> k -> a -> Sem r ()
- retrieveAndDecode :: forall ct k r a. (Member (Cache k ct) r, Member (Embed IO) r, MemberWithError (Error CacheError) r, LogWithPrefixesLE r, Show k) => Serialize CacheError r a ct -> k -> Maybe UTCTime -> Sem r (ActionWithCacheTime r a)
- lookupAndDecode :: forall ct k r a. (Member (Cache k ct) r, LogWithPrefixesLE r, Member (Embed IO) r, MemberWithError (Error CacheError) r, Show k) => Serialize CacheError r a ct -> k -> Maybe UTCTime -> Sem r (Maybe (ActionWithCacheTime r a))
- retrieveOrMake :: forall ct k r a b. (Member (Cache k ct) r, LogWithPrefixesLE r, Member (Embed IO) r, MemberWithError (Error CacheError) r, Show k) => Serialize CacheError r a ct -> k -> ActionWithCacheTime r b -> (b -> Sem r a) -> Sem r (ActionWithCacheTime r a)
- clear :: Member (Cache k ct) r => k -> Sem r ()
- clearIfPresent :: (Member (Cache k ct) r, MemberWithError (Error CacheError) r) => k -> Sem r ()
- persistStreamlyByteArray :: (Show k, Member (Embed IO) r, MemberWithError (Error CacheError) r, LogWithPrefixesLE r) => (k -> FilePath) -> InterpreterFor (Cache k (Array Word8)) r
- persistLazyByteString :: (Members '[Embed IO] r, MemberWithError (Error CacheError) r, LogWithPrefixesLE r, Show k) => (k -> FilePath) -> InterpreterFor (Cache k ByteString) r
- persistStrictByteString :: (Members '[Embed IO] r, MemberWithError (Error CacheError) r, LogWithPrefixesLE r, Show k) => (k -> FilePath) -> InterpreterFor (Cache k ByteString) r
- type AtomicMemCache k v = TVar (Map k (TMVar (Maybe (WithCacheTime Identity v))))
- runAtomicInMemoryCache :: (Ord k, Show k, Member (Embed IO) r, LogWithPrefixesLE r) => AtomicMemCache k ct -> InterpreterFor (Cache k ct) r
- runBackedAtomicInMemoryCache :: (Ord k, Show k, LogWithPrefixesLE r, Members '[Embed IO, Cache k ct] r) => AtomicMemCache k ct -> InterpreterFor (Cache k ct) r
- runPersistenceBackedAtomicInMemoryCache :: (Ord k, Show k, Member (Embed IO) r, MemberWithError (Error CacheError) r, LogWithPrefixesLE r) => InterpreterFor (Cache k ct) r -> AtomicMemCache k ct -> InterpreterFor (Cache k ct) r
- runPersistenceBackedAtomicInMemoryCache' :: (Ord k, Show k, Member (Embed IO) r, MemberWithError (Error CacheError) r, LogWithPrefixesLE r) => InterpreterFor (Cache k ct) r -> InterpreterFor (Cache k ct) r
- data CacheError
Effect
Key/Value store effect requiring its implementation to return values with time-stamps.
Instances
type DefiningModule (Cache :: Type -> Type -> k -> Type -> Type) Source # | |
Defined in Knit.Effect.AtomicCache |
Time Stamps
Types
data WithCacheTime m a Source #
Wrapper to hold (deserializable, if necessary) content and a timestamp. The stamp must be at or after the time the data was constructed
Instances
Functor m => Functor (WithCacheTime m) Source # | |
Defined in Knit.Effect.AtomicCache fmap :: (a -> b) -> WithCacheTime m a -> WithCacheTime m b # (<$) :: a -> WithCacheTime m b -> WithCacheTime m a # | |
Applicative m => Applicative (WithCacheTime m) Source # | |
Defined in Knit.Effect.AtomicCache pure :: a -> WithCacheTime m a # (<*>) :: WithCacheTime m (a -> b) -> WithCacheTime m a -> WithCacheTime m b # liftA2 :: (a -> b -> c) -> WithCacheTime m a -> WithCacheTime m b -> WithCacheTime m c # (*>) :: WithCacheTime m a -> WithCacheTime m b -> WithCacheTime m b # (<*) :: WithCacheTime m a -> WithCacheTime m b -> WithCacheTime m a # | |
Show (m a) => Show (WithCacheTime m a) Source # | |
Defined in Knit.Effect.AtomicCache showsPrec :: Int -> WithCacheTime m a -> ShowS # show :: WithCacheTime m a -> String # showList :: [WithCacheTime m a] -> ShowS # |
type ActionWithCacheTime r a = WithCacheTime (Sem r) a Source #
Specialize WithCacheTime
for use with a Polysemy effects stack.
Constructors
withCacheTime :: Maybe UTCTime -> m a -> WithCacheTime m a Source #
Construct a WithCacheTime from a Maybe Time.UTCTime
and an action.
onlyCacheTime :: Applicative m => Maybe UTCTime -> WithCacheTime m () Source #
Construct a WithCacheTime with a time and no action.
Combinators
ignoreCacheTime :: WithCacheTime m a -> m a Source #
Access the computation part of a WithCacheTime a
. This or
ignoreCacheTimeM
is required to use the cached value as anything but input
to another cached computation.
ignoreCacheTimeM :: Monad m => m (WithCacheTime m a) -> m a Source #
Access the computation part of an m (WithCacheTime a)
. This or
ignoreCacheTime
is required to use the cached value as anything but input
to another cached computation.
cacheTime :: WithCacheTime m a -> Maybe UTCTime Source #
Access the Maybe Time.UTCTime
part of a WithCacheTime
Utilities
wctMapAction :: (m a -> n b) -> WithCacheTime m a -> WithCacheTime n b Source #
Map one type of action to another. NB: 'WithCacheTime m' is a functor
(as long as m
is), so if m
is not changing, you should prefer fmap
to this function.
Cache Actions
:: (Show k, Member (Cache k ct) r, LogWithPrefixesLE r) | |
=> Serialize CacheError r a ct | Record-Of-Functions for serialization/deserialization |
-> k | Key |
-> a | Data to encode and cache |
-> Sem r () |
Combine the action of serializing and caching
:: forall ct k r a. (Member (Cache k ct) r, Member (Embed IO) r, MemberWithError (Error CacheError) r, LogWithPrefixesLE r, Show k) | |
=> Serialize CacheError r a ct | Record-Of-Functions for serialization/deserialization |
-> k | Key |
-> Maybe UTCTime |
|
-> Sem r (ActionWithCacheTime r a) | Result of lookup or running computation, wrapped as |
Combine the action of retrieving from cache and deserializing. | Throws if item not found or any other error during retrieval
:: forall ct k r a. (Member (Cache k ct) r, LogWithPrefixesLE r, Member (Embed IO) r, MemberWithError (Error CacheError) r, Show k) | |
=> Serialize CacheError r a ct | Record-Of-Functions for serialization/deserialization |
-> k | Key |
-> Maybe UTCTime |
|
-> Sem r (Maybe (ActionWithCacheTime r a)) | Result of lookup or running computation, wrapped as |
Combine the action of retrieving from cache and deserializing.
| Returns Nothing
if item not found, and throws on any other error.
:: forall ct k r a b. (Member (Cache k ct) r, LogWithPrefixesLE r, Member (Embed IO) r, MemberWithError (Error CacheError) r, Show k) | |
=> Serialize CacheError r a ct | Record-Of-Functions for serialization/deserialization |
-> k | Key |
-> ActionWithCacheTime r b | Cached Dependencies |
-> (b -> Sem r a) | Computation to produce |
-> Sem r (ActionWithCacheTime r a) | Result of lookup or running computation, wrapped as |
Lookup key and, if that fails, run an action to update the cache.
Further, if the item is in cache, but older than time-stamp of the
supplied 'ActionWithCacheTime r b', this function calls the given
b -> P.Sem r (Maybe a)
with the cached value from the supplied
'ActionWithCacheTime m b'.
Throws if item not found *and* making fails.
clear :: Member (Cache k ct) r => k -> Sem r () Source #
Clear the cache at a given key. Throws an exception if item is not present.
clearIfPresent :: (Member (Cache k ct) r, MemberWithError (Error CacheError) r) => k -> Sem r () Source #
Clear the cache at a given key. Doesn't throw if item is missing.
Effect Interpretations
Persist To Disk
persistStreamlyByteArray :: (Show k, Member (Embed IO) r, MemberWithError (Error CacheError) r, LogWithPrefixesLE r) => (k -> FilePath) -> InterpreterFor (Cache k (Array Word8)) r Source #
Interpreter for Cache via persistence to disk as a Streamly Memory.Array (Contiguous storage of Storables) of Bytes (Word8)
persistLazyByteString :: (Members '[Embed IO] r, MemberWithError (Error CacheError) r, LogWithPrefixesLE r, Show k) => (k -> FilePath) -> InterpreterFor (Cache k ByteString) r Source #
Interpreter for Cache via persistence to disk as a lazy ByteString
persistStrictByteString :: (Members '[Embed IO] r, MemberWithError (Error CacheError) r, LogWithPrefixesLE r, Show k) => (k -> FilePath) -> InterpreterFor (Cache k ByteString) r Source #
Interpreter for Cache via persistence to disk as a strict ByteString
Thread-safe Map
type AtomicMemCache k v = TVar (Map k (TMVar (Maybe (WithCacheTime Identity v)))) Source #
Specific type of in-memory cache.
runAtomicInMemoryCache :: (Ord k, Show k, Member (Embed IO) r, LogWithPrefixesLE r) => AtomicMemCache k ct -> InterpreterFor (Cache k ct) r Source #
Interpreter for in-memory only AtomicMemCache
Combined Map/Disk
runBackedAtomicInMemoryCache :: (Ord k, Show k, LogWithPrefixesLE r, Members '[Embed IO, Cache k ct] r) => AtomicMemCache k ct -> InterpreterFor (Cache k ct) r Source #
interpret Cache via a different-Cache-backed AtomicMemCache
runPersistenceBackedAtomicInMemoryCache :: (Ord k, Show k, Member (Embed IO) r, MemberWithError (Error CacheError) r, LogWithPrefixesLE r) => InterpreterFor (Cache k ct) r -> AtomicMemCache k ct -> InterpreterFor (Cache k ct) r Source #
Interpret Cache via AtomicMemCache and an interpreter for a backing cache, usually a persistence layer.
runPersistenceBackedAtomicInMemoryCache' :: (Ord k, Show k, Member (Embed IO) r, MemberWithError (Error CacheError) r, LogWithPrefixesLE r) => InterpreterFor (Cache k ct) r -> InterpreterFor (Cache k ct) r Source #
Interpret Cache via AtomicMemCache and an interpreter for a backing cache, usually a persistence layer. Create a new, empty, AtomicMemCache to begin.
Exceptions
data CacheError Source #
Error Type for Cache errors. Simplifies catching and reporting them.
ItemNotFoundError Text | |
ItemTooOldError Text | |
DeSerializationError Text | |
PersistError Text | |
OtherCacheError Text |
Instances
Eq CacheError Source # | |
Defined in Knit.Effect.AtomicCache (==) :: CacheError -> CacheError -> Bool # (/=) :: CacheError -> CacheError -> Bool # | |
Show CacheError Source # | |
Defined in Knit.Effect.AtomicCache showsPrec :: Int -> CacheError -> ShowS # show :: CacheError -> String # showList :: [CacheError] -> ShowS # |