Skip to content
This repository was archived by the owner on Jul 20, 2022. It is now read-only.

Commit 58fd6a4

Browse files
Merge pull request #4 from redbubble/slight-cache-cleanup
Moved common cache mechanics into its own class
2 parents 3184b86 + e932206 commit 58fd6a4

File tree

3 files changed

+106
-32
lines changed

3 files changed

+106
-32
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package com.redbubble.util.cache
2+
3+
import java.util.concurrent.{Executor, TimeUnit}
4+
5+
import com.redbubble.util.async.syntax._
6+
import com.twitter.util.{Duration, Future}
7+
8+
import scala.concurrent.ExecutionContext.fromExecutor
9+
import scala.concurrent.duration.{Duration => ScalaDuration}
10+
import scalacache.serialization.Codec
11+
import scalacache.{Flags, ScalaCache}
12+
13+
/**
14+
* Common implementations of the underlying caching mechanics, transforming of isomorphic types, etc.
15+
*/
16+
private[cache] object Caching {
17+
private val flags = Flags(readsEnabled = true, writesEnabled = true)
18+
19+
/**
20+
* Cache the result of executing `f` using the given `key`.
21+
*
22+
* @param cache The underlying cache.
23+
* @param ttl The TTL of the item to be cached.
24+
* @param key The key to use to cache the value.
25+
* @param codec The codec to use to serialise/encide the value into the cache.
26+
* @param f The function to run, whose result will be cached.
27+
* @param ex The executor to use to run any asynchronous operations.
28+
* @tparam V The type of the value to be cached.
29+
* @tparam Repr The type of the serialised/encoded form of the value, e.g. `InMemoryRepr` or `Array[Byte]`.
30+
* @return A Twitter `Future` with the result of `f`.
31+
*/
32+
def caching[V, Repr](cache: ScalaCache[Repr], ttl: Duration, key: CacheKey, codec: Codec[V, Repr])(f: => Future[V])
33+
(implicit ex: Executor): Future[V] = {
34+
val ec = toEc(ex)
35+
val result = scalacache.cachingWithTTL(key)(toScalaTtl(ttl))(f.asScala)(cache, flags, ec, codec)
36+
result.asTwitter(ec)
37+
}
38+
39+
/**
40+
* Manually put a value into the cache. This function is side-effecting. Prefer `caching` instead of this function.
41+
*
42+
* @param cache The underlying cache.
43+
* @param ttl The TTL of the item to be cached.
44+
* @param key The key to use to cache the value.
45+
* @param codec The codec to use to serialise/encide the value into the cache.
46+
* @param value The value to cache.
47+
* @param ex The executor to use to run any asynchronous operations.
48+
* @tparam V The type of the value to be cached.
49+
* @tparam Repr The type of the serialised/encoded form of the value, e.g. `InMemoryRepr` or `Array[Byte]`.
50+
* @return A Twitter `Future` containing no result (you can use this to know when the value has been cached).
51+
*/
52+
def put[V, Repr](cache: ScalaCache[Repr], ttl: Duration, key: CacheKey, codec: Codec[V, Repr], value: V)
53+
(implicit ex: Executor): Future[Unit] = {
54+
val ec = toEc(ex)
55+
val result = scalacache.put(key)(value, Some(toScalaTtl(ttl)))(cache, flags, codec)
56+
result.asTwitter(ec)
57+
}
58+
59+
/**
60+
* Manually get a value from the cache. Prefer `caching` instead of this function.
61+
*
62+
* @param cache The underlying cache.
63+
* @param key The key to use to cache the value.
64+
* @param codec The codec to use to serialise/encide the value into the cache.
65+
* @param ex The executor to use to run any asynchronous operations.
66+
* @tparam V The type of the value to be cached.
67+
* @tparam Repr The type of the serialised/encoded form of the value, e.g. `InMemoryRepr` or `Array[Byte]`.
68+
* @return A Twitter `Future` the value from the cache.
69+
*/
70+
def get[V, Repr](cache: ScalaCache[Repr], key: CacheKey, codec: Codec[V, Repr])
71+
(implicit ex: Executor): Future[Option[V]] = {
72+
val ec = toEc(ex)
73+
val result = scalacache.get(key)(cache, flags, codec)
74+
result.asTwitter(ec)
75+
}
76+
77+
/**
78+
* Flush (clear) the cache of all entries.
79+
*/
80+
def flush[Repr](cache: ScalaCache[Repr])(implicit ex: Executor): Future[Unit] =
81+
cache.cache.removeAll().asTwitter(toEc(ex))
82+
83+
private def toEc(ex: Executor) = fromExecutor(ex)
84+
85+
private def toScalaTtl(ttl: Duration): ScalaDuration = ScalaDuration(ttl.inNanoseconds, TimeUnit.NANOSECONDS)
86+
}

src/main/scala/com/redbubble/util/cache/memory/InMemorySimpleCache.scala

+12-18
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,12 @@ package com.redbubble.util.cache.memory
33
import java.util.concurrent.{Executor, TimeUnit}
44

55
import com.github.benmanes.caffeine.cache.Caffeine
6-
import com.redbubble.util.async.syntax._
7-
import com.redbubble.util.cache.{CacheKey, SimpleCache}
6+
import com.redbubble.util.cache.{CacheKey, Caching, SimpleCache}
87
import com.redbubble.util.metrics.StatsReceiver
98
import com.twitter.util.{Duration, Future}
109

11-
import scala.concurrent.ExecutionContext.fromExecutor
12-
import scala.concurrent.duration.{Duration => ScalaDuration}
1310
import scalacache.serialization.{Codec, InMemoryRepr}
14-
import scalacache.{CacheConfig, Flags, ScalaCache}
11+
import scalacache.{CacheConfig, ScalaCache}
1512

1613
/**
1714
* An in-memory cache, with metrics tracking.
@@ -24,33 +21,30 @@ import scalacache.{CacheConfig, Flags, ScalaCache}
2421
*/
2522
private[cache] final class InMemorySimpleCache(name: String, maxSize: Long, ttl: Duration)
2623
(implicit ex: Executor, statsReceiver: StatsReceiver) extends SimpleCache {
27-
private val flags = Flags(readsEnabled = true, writesEnabled = true)
28-
private val scalaTtl = ScalaDuration(ttl.inNanoseconds, TimeUnit.NANOSECONDS)
29-
private val cache = createCache(name, maxSize, scalaTtl, ex, statsReceiver)
30-
private val ec = fromExecutor(ex)
24+
private val cache = createCache(name, maxSize, ex, statsReceiver)
3125

3226
override def caching[V](key: CacheKey)(f: => Future[V]): Future[V] = {
33-
val noOpCodec = Codec.anyToNoSerialization[V]
34-
scalacache.cachingWithTTL[V, InMemoryRepr](key)(scalaTtl)(f.asScala)(cache, flags, ec, noOpCodec).asTwitter(ec)
27+
val codec = Codec.anyToNoSerialization[V]
28+
Caching.caching(cache, ttl, key, codec)(f)(ex)
3529
}
3630

3731
override def put[V, Repr](key: CacheKey, value: V): Future[Unit] = {
38-
val noOpCodec = Codec.anyToNoSerialization[V]
39-
scalacache.put[V, InMemoryRepr](key)(value, Some(scalaTtl))(cache, flags, noOpCodec).asTwitter(ec)
32+
val codec = Codec.anyToNoSerialization[V]
33+
Caching.put(cache, ttl, key, codec, value)(ex)
4034
}
4135

4236
override def get[V](key: CacheKey): Future[Option[V]] = {
43-
val noOpCodec = Codec.anyToNoSerialization[V]
44-
scalacache.get[V, InMemoryRepr](key)(cache, flags, noOpCodec).asTwitter(ec)
37+
val codec = Codec.anyToNoSerialization[V]
38+
Caching.get(cache, key, codec)(ex)
4539
}
4640

47-
override def flush(): Future[Unit] = cache.cache.removeAll().asTwitter(ec)
41+
override def flush(): Future[Unit] = Caching.flush(cache)(ex)
4842

4943
private def createCache(
50-
name: String, maxSize: Long, ttl: ScalaDuration, executor: Executor, statsReceiver: StatsReceiver): ScalaCache[InMemoryRepr] = {
44+
name: String, maxSize: Long, executor: Executor, statsReceiver: StatsReceiver): ScalaCache[InMemoryRepr] = {
5145
val underlying = Caffeine.newBuilder()
5246
.maximumSize(maxSize)
53-
.expireAfterWrite(ttl.length, ttl.unit)
47+
.expireAfterWrite(ttl.inNanoseconds, TimeUnit.NANOSECONDS)
5448
.executor(executor)
5549
.recordStats(() => new StatsCounter(name, statsReceiver))
5650
.build[String, Object]

src/main/scala/com/redbubble/util/cache/redis/RedisSimpleCache.scala

+8-14
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
package com.redbubble.util.cache.redis
22

3-
import java.util.concurrent.{Executor, TimeUnit}
3+
import java.util.concurrent.Executor
44

5-
import com.redbubble.util.async.syntax._
6-
import com.redbubble.util.cache.{CacheKey, SimpleCache}
5+
import com.redbubble.util.cache.{CacheKey, Caching, SimpleCache}
76
import com.redbubble.util.metrics.StatsReceiver
87
import com.twitter.util.{Duration, Future}
98

10-
import scala.concurrent.ExecutionContext.fromExecutor
11-
import scala.concurrent.duration.{Duration => ScalaDuration}
12-
import scalacache.{CacheConfig, Flags, ScalaCache}
9+
import scalacache.{CacheConfig, ScalaCache}
1310

1411
/**
1512
* An external cache, backed by a Redis instance.
@@ -23,27 +20,24 @@ import scalacache.{CacheConfig, Flags, ScalaCache}
2320
*/
2421
private[cache] final class RedisSimpleCache(name: String, host: String, port: Int, ttl: Duration)
2522
(implicit ex: Executor, statsReceiver: StatsReceiver) extends SimpleCache {
26-
private val flags = Flags(readsEnabled = true, writesEnabled = true)
27-
private val scalaTtl = ScalaDuration(ttl.inNanoseconds, TimeUnit.NANOSECONDS)
28-
private val cache = createCache(name, host, port, ex, statsReceiver)
29-
private val ec = fromExecutor(ex)
23+
private lazy val cache = createCache(name, host, port, ex, statsReceiver)
3024

3125
override def caching[V](key: CacheKey)(f: => Future[V]): Future[V] = {
3226
val codec = new ScalaCacheExternaliserCodec[V]
33-
scalacache.cachingWithTTL[V, Array[Byte]](key)(scalaTtl)(f.asScala)(cache, flags, ec, codec).asTwitter(ec)
27+
Caching.caching(cache, ttl, key, codec)(f)(ex)
3428
}
3529

3630
override def put[V, Repr](key: CacheKey, value: V): Future[Unit] = {
3731
val codec = new ScalaCacheExternaliserCodec[V]
38-
scalacache.put[V, Array[Byte]](key)(value, Some(scalaTtl))(cache, flags, codec).asTwitter(ec)
32+
Caching.put(cache, ttl, key, codec, value)(ex)
3933
}
4034

4135
override def get[V](key: CacheKey): Future[Option[V]] = {
4236
val codec = new ScalaCacheExternaliserCodec[V]
43-
scalacache.get[V, Array[Byte]](key)(cache, flags, codec).asTwitter(ec)
37+
Caching.get(cache, key, codec)(ex)
4438
}
4539

46-
override def flush(): Future[Unit] = cache.cache.removeAll().asTwitter(ec)
40+
override def flush(): Future[Unit] = Caching.flush(cache)
4741

4842
private def createCache(name: String, host: String, port: Int, executor: Executor, statsReceiver: StatsReceiver): ScalaCache[Array[Byte]] = {
4943
val underlying = MetricsEnableRedisCache(name, host, port)(executor, statsReceiver)

0 commit comments

Comments
 (0)