mules icon indicating copy to clipboard operation
mules copied to clipboard

Add Support For Weight With Caffeine Caches

Open isomarcte opened this issue 5 years ago • 3 comments

Add Support For Weight With Caffeine Caches

Adds support for describing cache weight for Caffeine based caches.

  • Adds ByteWeight pseudo-typeclass.
    • It is a pseudo typeclass because for any given type there does exist a single valid implementation, but because measuring exact memory usage of a value on the JVM changes based on JVM, OS, and library versions it is likely that this type class will need to violate coherence.
    • Added default instances for boxed primitives and a handful of collections. Measurements of memory usage were made using org.openjdk.jol on a x64 Linux system, running JDK 14.0.2, with Scala 2.13.3.
    • Added fallback instance for anything which implements Foldable. It will fail for infinitely large types, e.g. streaming constructs, but so would caching such values.
    • Added instance for boxed Arrays, but put it into a lower priority trait to allow for future implementations of unboxed ByteWeight instances for unboxed primitives. I'm not entirely sure such instances are possible to write on the current JVM/Scala versions without runtime instrumentation (which we don't want), but left room in case they are possible now or in the future.
  • Added Weigher trait which is analogues to the Caffeine libraries Weigher class.
    • The primary reasons for creating a new type are,
      • Support for contramapping keys and values to compose new weight instances.
      • Marrying the definition of a Weight with a maximumWeight value, as it doesn't makes sense to define a Weight for a cache, but no max value.
  • Added CaffeineCacheBuilder to be used to create new Caffeine based caches. The number of configuration values was getting large enough that passing each one as an Option to the build function seemed intractable.
  • Deprecated the CaffeineCache#build method as CaffeineCacheBuilder provides this functionality and having two interfaces to the same operation can be cumbersome or confusing to users.

isomarcte avatar Aug 13 '20 14:08 isomarcte

@ChristopherDavenport I have tests to ensure the Weigher is doing what we want, but they won't work unless we expose some of the Caffeine Cache methods, in particular cleanUp. That is, a Caffeine Cache won't evict values right away, but will wait until a cleanup phase is run, either implicitly based on internal timers, or explicitly based on a call to cleanUp.

What are you thoughts on exposing some of those via a functional interface? I can have them up quickly if you are not opposed to it. There are other nice things in there, like asking for stats and approximate size, which have real use cases in non-test contexts.

isomarcte avatar Aug 13 '20 14:08 isomarcte

@ChristopherDavenport I've updated this to use a Builder as per your suggestion for binary compatibility. I've also updated the commit and the commit message/PR message.

Thoughts?

isomarcte avatar Oct 10 '20 18:10 isomarcte

That is, a Caffeine Cache won't evict values right away, but will wait until a cleanup phase is run, either implicitly based on internal timers, or explicitly based on a call to cleanUp.

fyi, if you set Caffeine.executor(Runnable::run) then it will use a same-thread (direct) executor. That will cause evictions to occur immediately on the same thread, which is very fast work. However, any listeners or futures produced by the cache will also use that executor and, as user callback functions, the cache does not know if that is cheap or not. Therefore the better default is to be asynchronous (thanks to the FJP.commonPool) but it is not for the cache's own benefit.

ben-manes avatar Mar 19 '21 21:03 ben-manes