New Functional Map API in Infinispan 8 - Introduction

In Infinispan 8.0.0.Beta3, we have a introduced a new experimental API for interacting with your data which takes advantage of the functional programming additions and improved asynchronous programming capabilities available in Java 8.

Over the next few weeks we’ll be introducing different aspects of the API. In this first blog post, we’ll focus on why we felt there’s a need for a new approach, answering a few key questions.

ConcurrentMap and JCache

Map­-like key/value pair APIs have often been used for distributed caching and in-­memory data grids. Initially, ConcurrentMap became popular but this was designed to be run within a single JVM, and hence some of the operations suffered in distributed environments or when persistence stores were attached. For example, methods such as '[V put(K, V)]', '[V putIfAbsent(K, V)]', '[V replace(K, V)]' would force implementations to return the previous value, but often this value is not needed yet this could be expensive to transfer.

JSR­-107 set out to improve on this and came up with the JCache specification which solved this particular problem separating operations such ConcurrentMap’s '[V put(K, V)]' into two operations: '[void put(K, V)]' and 'V getAndPut(K, V)', and it applied the same logic to other operations such as 'replace' by providing an alternative 'getAndReplace(K, V)'…​ etc.

However, even though JCache was designed with distributed caching in mind, it still failed to provide an API to execute operations asynchronously and hence avoid resource under­utilization by having threads waiting for remote operations to complete. 'l​oadAll' ​is probably the only exception, and it would have been the perfect candidate to return a F​uture​ or similar construct, but having to pass in a completion listener feels a bit clunky and cannot be chained easily.

In my opinion, the best parts of JCache are 'i​nvoke'​ and '[i​nvokeAll]' methods. When you look at them, you see a lot of potential to reimplement get, put, getAndPut, getAndReplace, putAll,​ getAll, ​and many others using these methods. In other words, as an implementer, all you should need to implement is those two functions, and the rest would be syntactic sugar for the user. Unfortunately, the way 'i​nvoke' and 'i​nvokeAll' handle arguments is a bit clunky, and really,  it’s just screaming for lambdas to be passed in and C​ompletableFuture instances to be returned (Java 8!).

So, when Infinispan moved to Java 8, we decided to revisit these concepts and see if we could come up with a better, distilled map­-like interface to be used for as either a caching or data grid API.

New Functional Map API

Infinispan’s Functional Map API is a distilled map­like asynchronous API which uses lambdas to interact with data.

Asynchronous and Lazy

Being an asynchronous API, all methods that return a single result, return a CompletableFuture which wraps the result, so you can use the resources of your system more efficiently by having the possibility to receive callbacks when the CompletableFuture has completed, or you can chain or compose them with other CompletableFuture. If you do want to block the thread and wait for the result, just as it happens with a ConcurrentMap or JCache method call, you can simply call CompletableFuture.get() (for such situations, we are working on finding ways to avoid unnecessary thread creation when the caller will block on the CompletableFuture).

For those operations that return multiple results, the API returns instances of a ​[Traversable] interface which offers a lazy pull­-style API for working with multiple results. Although push­-style interfaces for handling multiple results, such as RxJava, are fully asynchronous, they’re harder to use from a user’s perspective. T​raversable,​ being a lazy pull­-style API, can still be asynchronous underneath since the user can decide to work on the traversable at a later stage, and the Traversable implementation itself can decide when to compute those results.

Lambda transparency

Since the content of the lambdas is transparent to Infinispan, the API has been split into 3 interfaces for read­-only (R​eadOnlyMap)​, read­-write (R​eadWriteMap)​ and write­-only (W​riteOnlyMap)​ operations respectively, in order to provide hints to the Infinispan internals on the type of work needed to support lambdas.

For example, Infinispan has been designed in such way that our 'C​oncurrentMap.​g​et(​)' and 'JCache.​g​etAll(​)' implementations do not require locks to be acquired. These get()/getAll() operations are read-only operations, and hence if you call our functional map R​eadOnlyMap’s 'eval(​)' or 'e​valMany(​)' operations, you get the same benefit. A key advantage of R​eadOnlyMap’s 'eval​()' and 'e​valMany(​)' operations is that they take lambdas as parameters which means the returned types are more flexible, so we can return a value associated with the key, or we can return a boolean if a value has the expected contents, or we can return some metadata parameters from it, e.g. last accessed time, last modified time, creation time, lifespan, version information…​etc.

Another important hint that is required to make efficient use of the system is to know when a write-only operation is being executed. Write­-only operations require locks to be acquired and as demonstrated by JCache’s '[void removeAll()]' and `void put(K, V)' or ConcurrentMap’s '[putAll()]', they do not require the previous value to be queried or read, which as explained above is a very important optimization since reading the previous value might require the persistence layer or a remote node to be queried. WriteOnlyMap’s '[eval()]', '[evalMany()]', and '[evalAll()]' follow this same pattern with the added flexibility for the lambda to decide what kind of write operation to execute.

The final type of operations we have are read­-write operations, and within this category we find CAS-like (Compare­-And­-Swap) operations. This type of operations require previous value associated with the key to be read and for locks to be acquired before executing the lambda. Most of the operations in ConcurrentMap and JCache operations fall within this domain including: 'V put(K, V)', '[boolean putIfAbsent(K, V)]', 'V replace(K, V)', 'boolean replace(K, V, V)'…​etc. ReadWriteMap’s 'eval()', 'evalMany()' and 'evalAll()' provide a way to implement the vast majority of these operations thanks to the flexibility of the lambdas passed in. So you can make CAS­-like comparisons not only based on value equality but based on metadata parameter equality such as version information, and you can send back previous value or boolean instances to signal whether the CAS­-like comparison succeeded.

$DEITY, I need to learn a new API!!!

This new functional Map­-like API is meant to complement existing Key/Value Infinispan API offerings, so you’ll still be able to use ConcurrentMap or JCache standard APIs if that’s what suits your use case best.

The target audience for this new API is either:

  1. Distributed or persistent caching/in­-memory­ data­-grid users that want to benefit from CompletableFuture and/or Traversable for async/lazy data grid or caching data manipulation. The clear advantage here is that threads do not need to be idle waiting for remote operations to complete, but instead these can be notified when remote operations complete and then chain them with other subsequent operations.

  2. Users wanting to go beyond the standard operations exposed by ConcurrentMap and JCache, for example, if you want to do a replace operation using metadata parameter equality instead of value equality, or if you want to retrieve metadata information from values…​etc.

Internally, we feel that this new functional Map­-like API distills the Map­-like APIs that we currently offer (including ConcurrentMap and JCache) and gets rid of a lot of duplication in our AdvancedCache API (e.g. '[getCacheEntry()]', '[getAsync()]', '[putAsync()]', 'put(K, V, Metadata)'…​etc), and hence down the line, we’d want all these APIs to be implemented using the new functional Map­like API. By doing that, we hope to reduce the number of commands that our internal architecture implements, hence reducing our code base.

This new API also offers a new approach for passing per-invocation parameters, and much more flexible Metadata handling compared to our current approach. As we dig into this new API in next blog posts, we’ll explain the differences and advantages provided by these.

Functional Map API usage examples

To give you a little taste of what the API looks like, here is a write-­only operation to associate a key with a value, whose CompletableFuture has been chained so that when it completes, a read­-only operation can be executed to read the stored value, and when that completes, print it to the system output:

You can find more examples of this new API in FunctionalConcurrentMap and FunctionalJCache classes, which are implementations of ConcurrentMap and JCache respectively using the new Functional Map API.

Tell me more!!

Over the next few weeks I’ll be posting examples looking at the finer details of these new Functional Map APIs, but if you’re eager to get started, check the classes in org.infinispan.functional package, FunctionalConcurrentMap and FunctionalJCache which are ConcurrentMap and JCache implementations based on these Functional Map APIs, and FunctionalMapTest which demonstrates operations that go beyond what ConcurrentMap and JCache offer.

Happy (functional) hacking :)




JUGs alpha as7 asymmetric clusters asynchronous beta c++ cdi chat clustering community conference configuration console data grids data-as-a-service database devoxx distributed executors docker event functional grouping and aggregation hotrod infinispan java 8 jboss cache jcache jclouds jcp jdg jpa judcon kubernetes listeners meetup minor release off-heap openshift performance presentations product protostream radargun radegast recruit release release 8.2 9.0 final release candidate remote query replication queue rest query security spring streams transactions vert.x workshop 8.1.0 API DSL Hibernate-Search Ickle Infinispan Query JP-QL JSON JUGs JavaOne LGPL License NoSQL Open Source Protobuf SCM administration affinity algorithms alpha amazon anchored keys annotations announcement archetype archetypes as5 as7 asl2 asynchronous atomic maps atomic objects availability aws beer benchmark benchmarks berkeleydb beta beta release blogger book breizh camp buddy replication bugfix c# c++ c3p0 cache benchmark framework cache store cache stores cachestore cassandra cdi cep certification cli cloud storage clustered cache configuration clustered counters clustered locks codemotion codename colocation command line interface community comparison compose concurrency conference conferences configuration console counter cpp-client cpu creative cross site replication csharp custom commands daas data container data entry data grids data structures data-as-a-service deadlock detection demo deployment dev-preview development devnation devoxx distributed executors distributed queries distribution docker documentation domain mode dotnet-client dzone refcard ec2 ehcache embedded embedded query equivalence event eviction example externalizers failover faq final fine grained flags flink full-text functional future garbage collection geecon getAll gigaspaces git github gke google graalvm greach conf gsoc hackergarten hadoop hbase health hibernate hibernate ogm hibernate search hot rod hotrod hql http/2 ide index indexing india infinispan infinispan 8 infoq internationalization interoperability interview introduction iteration javascript jboss as 5 jboss asylum jboss cache jbossworld jbug jcache jclouds jcp jdbc jdg jgroups jopr jpa js-client jsr 107 jsr 347 jta judcon kafka kubernetes lambda language learning leveldb license listeners loader local mode lock striping locking logging lucene mac management map reduce marshalling maven memcached memory migration minikube minishift minor release modules mongodb monitoring multi-tenancy nashorn native near caching netty node.js nodejs non-blocking nosqlunit off-heap openshift operator oracle osgi overhead paas paid support partition handling partitioning performance persistence podcast presentation presentations protostream public speaking push api putAll python quarkus query quick start radargun radegast react reactive red hat redis rehashing releaase release release candidate remote remote events remote query replication rest rest query roadmap rocksdb ruby s3 scattered cache scripting second level cache provider security segmented server shell site snowcamp spark split brain spring spring boot spring-session stable standards state transfer statistics storage store store by reference store by value streams substratevm synchronization syntax highlighting tdc testing tomcat transactions tutorial uneven load user groups user guide vagrant versioning vert.x video videos virtual nodes vote voxxed voxxed days milano wallpaper websocket websockets wildfly workshop xsd xsite yarn zulip
Posted by Galder Zamarreño on 2015-08-21
Tags: functional introduction API lambda
back to top