Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FS-37 Cache Algebra And Implementations #70

Merged
merged 13 commits into from
Feb 23, 2017
Merged

Conversation

diesalbla
Copy link
Contributor

This PR fulfills ticket #37. We introduce two new sbt modules, freestyle-cache and freestyle-cache-redis, that define an algebra of cache operations, and two interpreters for it. In this ticket, we understand by cache an efficient, high-load, and potentially parallel, key-value store.

Algebra

In the sbt project freestyle-cache, in the package freestyle.cache, we define an algebra named @free trait CacheM[F[_]], that describes a type-class of carrier types F[_] that support cache operations. This algebra gives the usual operations for Key-Value store: get, put, delete, hasKey, keys (for key-set), clear.
Because we want the CacheM trait to be generic on the type of indexing keys and on the type of stored values, we wrap the @free algebra inside an empty KeyValueProvider[K,V] class. Besides the algebra we provide an implicit method to build an interpreter for the algebra (with the same K and V types). This interpreter is built out of two pieces.

  • One is a raw interpreter to an intermediary carrier type F[_], as an object that inherits from trait KeyValueMap[F,K,V]. The reason for this is that in some implementations the raw format F is fixed.
  • A natural transformation from the raw format F to the desired one G.

HashMap

In the same project, in the package freestyle.cache.hashmap, we provide an implementation for an interpreter of the algebra, in which we use as a store a ConcurrentHashMap from the java.util library.

  • Hasher: the ConcurrentHashMap is based on the use of the methods hashcode and equals, which unfortunately is not always properly implemented for all types. We introduce a type-class Hashable, to indicate that the Key type must provide a hashing method.

Redis

In the project freestyle-cache-redis, in the package freestyle.cache.redis, we implement an interpreter for the algebra based on an external Redis data-store. For the implementation, we use the Redis client library rediscala, which is based in akka.

In this interpreter, the notion of parallelization is based on the use of Redis transactions, which for our purposes allows us to group a sequence of data-independent operations and get them sent and executed atomically by the redis server. This parallelization is implemented using the Applicative instance of Kleisli, where the Kleisli is used to defer the communications to the client.

@raulraja ¿Could you have it reviewed?

We add the simple algebra of Cache Operations, to represent
key-value stores. We provide a simple implementation, based
on a java Concurrent Hash Map.
We add an implementation of the cache algebra, where the cache is
backed in a Redis data-store. For the connection with the data
store, we need a client library for Scala. We use Scredis.
We replace the previous ad-hoc type ScredisOps with cats.data.Kleisli,
so as to reuse the functions already defined for this type.
We add a simple test example, to show the combination of applicative (
(parallel) fragments and the sequential ones.
We also rename the package for the scredis subfolder.x
We replace Scredis by Rediscala as our Scala library client for Redis.
Scredis does not seem to be actively maintained.
We refactor the `freestyle-cache` module. We remove from the
`freestyle.cache` package any implementation details based on
a Java Concurrent HashMap, and we move those to the `hashmap`
subpackage.

In the `freestyle.cache.implicits`, we leave a simple constructor for
interpreters, based on two steps: a raw implementation, and a natural
transformation.
After the previous refactoring, the Cache Module already separates
between generating a _raw_ format (in the Redis it's Future) and
the Natural Transformation to another carrier `F[_]`.
@diesalbla diesalbla self-assigned this Feb 21, 2017
@codecov-io
Copy link

codecov-io commented Feb 21, 2017

Codecov Report

Merging #70 into master will increase coverage by 4.51%.
The diff coverage is 70.68%.

@@            Coverage Diff             @@
##           master      #70      +/-   ##
==========================================
+ Coverage   57.23%   61.75%   +4.51%     
==========================================
  Files          20       27       +7     
  Lines         159      217      +58     
  Branches        0        1       +1     
==========================================
+ Hits           91      134      +43     
- Misses         68       83      +15

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 706a6af...5582973. Read the comment docs.

Copy link
Contributor

@raulraja raulraja left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@diesalbla Excellent work! This is very exciting! Added a few ideas but we can add a new ticket for those improvements 👍 🎉

def has(key: Key): FreeS.Par[F, Boolean]

// Returns the set of keys in the store
def keys: FreeS.Par[F, Seq[Key]]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the default Seq imported in Predef is mutable and pattern matching is easier over List can this return something else that is not Seq?

def hashCode(a: A): Int
}

object Hasher {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can include here automatic instances for any product type such as case classes, HList, TupleN etc with shapeless generic derivation given there is also an implicit Hasher for the contained elements.

import freestyle.implicits._
import org.scalatest._

class CacheTests extends WordSpec with Matchers with CacheTestContext {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice 👏

def get(key: Key): FreeS.Par[F, Option[Val]]

// Sets the value of a key to a newValue.
def put(key: Key, newVal: Val): FreeS.Par[F, Unit]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add the following methods if we can support them both on the InMemory and Redis impl.

def putAll(keyValues: Map[Key, Val]): FreeS.Par[F, Unit]
def putIfAbsent(key: Key, newVal: Val): FreeS.Par[F, Unit]
def replace(key: Key, newVal: Val): FreeS.Par[F, Unit]
def isEmpty: FreeS.Par[F, Boolean]
def containsKey(key: Key): FreeS.Par[F, Boolean]
def containsValue(v: Val): FreeS.Par[F, Boolean]

Copy link
Contributor Author

@diesalbla diesalbla Feb 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The methods putAll and putIfAbsent are straight-forward. AAMOF, the latter is directly supported in redis. The command isEmpty can be supported efficiently through the use of the scan redis command. The containsValue is the one that could be less efficient to implement.

In any case, this can be left to a low-hanging-fruit ticket.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, since you seem to have more insight, would you mind creating one and tagging it as such? thanks! @anamariamv Is getting started with Freestyle and she could take on that task as well.

@@ -5,6 +5,13 @@ import simulacrum.typeclass

import scala.util.Try

/*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

The implementation of the `keys` operation in the Hashmap based was
using a `2.12` API operation, unavailable in `2.11` for crossbuild.
override def keys: Ops[M, Seq[Key]] =
RediscalaCont.keys.transform(toM).map(_.map(parser).flatten)
override def keys: Ops[M, List[Key]] =
RediscalaCont.keys.transform(toM).map(_.map(parser).flatten.toList)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be simplified to flatMap?

@diesalbla diesalbla mentioned this pull request Feb 22, 2017
@raulraja raulraja merged commit 4a55ae3 into master Feb 23, 2017
@raulraja raulraja deleted the diesalbla-37-Cache_Algebras branch February 23, 2017 13:34
@diesalbla diesalbla removed their assignment Jun 18, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants