Add Support For Weight With Caffeine Caches
Add Support For Weight With Caffeine Caches
Adds support for describing cache weight for Caffeine based caches.
- Adds
ByteWeightpseudo-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.jolon 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
traitto allow for future implementations of unboxedByteWeightinstances 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
Weighertrait which is analogues to the Caffeine librariesWeigherclass.- 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
Weightwith amaximumWeightvalue, as it doesn't makes sense to define aWeightfor a cache, but no max value.
- The primary reasons for creating a new type are,
- Added
CaffeineCacheBuilderto be used to create new Caffeine based caches. The number of configuration values was getting large enough that passing each one as anOptionto thebuildfunction seemed intractable. - Deprecated the
CaffeineCache#buildmethod asCaffeineCacheBuilderprovides this functionality and having two interfaces to the same operation can be cumbersome or confusing to users.
@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.
@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?
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.