How to cache results in scala?
Asked Answered
A

6

22

This page has a description of Map's getOrElseUpdate usage method:

object WithCache{
  val cacheFun1 = collection.mutable.Map[Int, Int]()
  def fun1(i:Int) = i*i
  def catchedFun1(i:Int) = cacheFun1.getOrElseUpdate(i, fun1(i))
}

So you can use catchedFun1 which will check if cacheFun1 contains key and return value associated with it. Otherwise, it will invoke fun1, then cache fun1's result in cacheFun1, then return fun1's result.

I can see one potential danger - cacheFun1 can became to large. So cacheFun1 must be cleaned somehow by garbage collector?

P.S. What about scala.collection.mutable.WeakHashMap and java.lang.ref.* ?

Alert answered 6/9, 2010 at 12:9 Comment(1)
Probably not: codeinstructions.com/2008/09/…Zicarelli
P
15

See the Memo pattern and the Scalaz implementation of said paper.

Also check out a STM implementation such as Akka.

Not that this is only local caching so you might want to lookinto a distributed cache or STM such as CCSTM, Terracotta or Hazelcast

Piled answered 6/9, 2010 at 13:13 Comment(2)
Only that the Memo Pattern uses WeahHashMap and is therefore not a very good cache.Zicarelli
@Zicarelli Depends upon "cache" requirements. In this case a "weak cache" works to elevate the posters concerns that "cacheFun1 can [become too] large" ..Manzanares
F
7

Take a look at spray caching (super simple to use)

http://spray.io/documentation/1.1-SNAPSHOT/spray-caching/

makes the job easy and has some nice features

for example :

      import spray.caching.{LruCache, Cache}

      //this is using Play for a controller example getting something from a user and caching it
      object CacheExampleWithPlay extends Controller{

        //this will actually create a ExpiringLruCache and hold data for 48 hours
        val myCache: Cache[String] = LruCache(timeToLive = new FiniteDuration(48, HOURS))

        def putSomeThingInTheCache(@PathParam("getSomeThing") someThing: String) = Action {
          //put received data from the user in the cache
          myCache(someThing, () => future(someThing))
          Ok(someThing)
        }

        def checkIfSomeThingInTheCache(@PathParam("checkSomeThing") someThing: String) = Action {
          if (myCache.get(someThing).isDefined)
            Ok(s"just $someThing found this in the cache")
          else
            NotFound(s"$someThing NOT found this in the cache")
        }
      }
Fluency answered 5/3, 2014 at 7:52 Comment(0)
Z
6

On the scala mailing list they sometimes point to the MapMaker in the Google collections library. You might want to have a look at that.

Zicarelli answered 6/9, 2010 at 12:10 Comment(1)
Google Collections has been renamed to Guava, and the new CacheBuilder is probably a closer match: docs.guava-libraries.googlecode.com/git/javadoc/index.html?com/…Johan
G
4

For simple caching needs, I'm still using Guava cache solution in Scala as well. Lightweight and battle tested.

If it fit's your requirements and constraints generally outlined below, it could be a great option:

  • Willing to spend some memory to improve speed.
  • Expecting that keys will sometimes get queried more than once.
  • Your cache will not need to store more data than what would fit in RAM. (Guava caches are local to a single run of your application. They do not store data in files, or on outside servers.)

Example for using it will be something like this:

  lazy val cachedData = CacheBuilder.newBuilder()
    .expireAfterWrite(60, TimeUnit.MINUTES)
    .maximumSize(10)
    .build(
      new CacheLoader[Key, Data] {
        def load(key: Key): Data = {
          veryExpansiveDataCreation(key)
        }
      }
    )

To read from it, you can use something like:

  def cachedData(ketToData: Key): Data = {
    try {
      return cachedData.get(ketToData)
    } catch {
      case ee: Exception => throw new YourSpecialException(ee.getMessage);
    }
  }
Guthrun answered 3/4, 2017 at 9:12 Comment(0)
O
3

We are using Scaffeine (Scala + Caffeine), and you can read abouts its pros/cons compared to other frameworks over here.

You add your sbt,

"com.github.blemale" %% "scaffeine" % "4.0.1"

Build your cache

import com.github.blemale.scaffeine.{Cache, Scaffeine}
import scala.concurrent.duration._

val cachedItems: Cache[String, Int] = 
  Scaffeine()
    .recordStats()
    .expireAtferWrite(60.seconds)
    .maximumSize(500)
    .build[String, Int]()

cachedItems.put("key", 1) // Add items

cache.getIfPresent("key") // Returns an option
Omora answered 5/2, 2021 at 22:16 Comment(1)
Typo from .build to .buidBozo
V
1

Since it hasn't been mentioned before let me put on the table the light Spray-Caching that can be used independently from Spray and provides expected size, time-to-live, time-to-idle eviction strategies.

Vania answered 3/1, 2014 at 14:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.