Fork me on GitHub

Atomic Counter

@since 1.3.0 (stand-alone) , 1.3.14 (full cluster support)

Overview

The atomic counter functionality aims to address the need of use cases like votes, likes, +1s and so on. It will make sure you'll eventually have a consistent and correct account of the sums/subtractions of all the increments.

When you set a specific node type (mix:atomicCounter) to any node, you'll have a protected property, oak:counter that will hold the count of your votes.

To perform an increment or decrement you'll have to set a specific property of type Long: oak:increment. See later on in the usage section.

Stand-alone, synchronous

When running on stand-alone configurations, like Segment, the actual increase of the oak:counter property will happen synchronously, in the same space of the commit. Therefore it will always reflect an up to date value.

Clustered, asynchronous

When running on clustered solutions, or potential one, like on DocumentMK the actual increase of the oak:counter property will happen asynchronously. Therefore the value displayed by oak:counter could not be up to date and lagging behind of some time. This is for dealing with conflicts that could happen when updating the same properties across the cluster and for scaling without having to deal with any global locking system.

The consolidation task will timeout by default at 32000ms. This aspect is configurable by providing the environment variable: oak.atomiccounter.task.timeout. In case a node time out it will be tracked in the logs with a warning.

For example to increase the timeout up to 64 seconds you can set from the command line -Doak.atomiccounter.task.timeout=64000.

Constraints

For the clustered solution, in order to have the asynchronous behaviour enabled you have to provide a org.apache.jackrabbit.oak.spi.state.Clusterable and a java.util.concurrent.ScheduledExecutorService. If any of these are null it will fall back to a synchronous behaviour.

It will fall back to synchronous as well if no org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard is provided or the provided commit hooks during repository constructions are not registered with the whiteboard itself. These are done automatically by the default Oak repository constructions, so other that you customise further no actions should be needed.

Enabling the feature (Repository Construction)

Plain Java

Stand-alone
NodeStore store = ... //store instantiation
Jcr jcr = new Jcr(store).withAtomicCounter();
Repository repo = jcr.createContentRepository();
Clustered
NodeStore store = ... //store instantiation

// DocumentNodeStore implements such aspect therefore it could be
// something like: `(Clusterable) store`. Casting the store into
// Clusterable.
Clusterable clusterable = ...

// you have to provide a ScheduledExecutorService which you'll
// have to take care of shutting it down properly during
// repository shutdown.
ScheduledExecutorService executor = ...

Jcr jcr = new Jcr(store)
    .with(clusterable)
    .with(executor)
    .withAtomicCounter();

Repository repo = jcr.createContentRepository();

OSGi

Stand-alone and Clustered
@Reference(target = "(type=atomicCounter)")
private EditorProvider atomicCounter;

...

NodeStore store = ...
Jcr jcr = new Jcr(store);
jcr.with(atomicCounter);

...

When running on clustered environment the EditorProvider expect to find a service of type org.apache.jackrabbit.oak.spi.state.Clusterable and org.apache.jackrabbit.oak.spi.state.NodeStore. DocumentNodeStore already register itself as Clusterable. If one of the two won't be available it will fall back to synchronous behaviour.

Usage

Session session = ...

// creating a counter node
Node counter = session.getRootNode().addNode("mycounter");
counter.addMixin("mix:atomicCounter"); // or use the NodeTypeConstants
session.save();

// Will output 0. the default value
System.out.println("counter now: " + counter.getProperty("oak:counter").getLong());

// incrementing by 5 the counter
counter.setProperty("oak:increment", 5);
session.save();

// Will output 5
System.out.println("counter now: " + counter.getProperty("oak:counter").getLong());

// decreasing by 1
counter.setProperty("oak:increment", -1);
session.save();

// Will output 4
System.out.println("counter now: " + counter.getProperty("oak:counter").getLong());

session.logout();

Debug

If you're experiencing any problems with the counter you can start analysing the situation by setting to DEBUG log appender org.apache.jackrabbit.oak.plugins.atomic.

If set to TRACE even more information will be provided.