AFTER: [info] Benchmark (set1Size) Mode Cnt Score Error Units [info] ORSetMergeBenchmark.mergeAddFromBothNodes 1 thrpt 10 717.362 ± 15.770 ops/ms [info] ORSetMergeBenchmark.mergeAddFromBothNodes 10 thrpt 10 144.862 ± 8.313 ops/ms [info] ORSetMergeBenchmark.mergeAddFromBothNodes 20 thrpt 10 96.004 ± 0.972 ops/ms [info] ORSetMergeBenchmark.mergeAddFromBothNodes 100 thrpt 10 18.735 ± 0.368 ops/ms [info] ORSetMergeBenchmark.mergeAddFromOtherNode 1 thrpt 10 1261.825 ± 51.717 ops/ms [info] ORSetMergeBenchmark.mergeAddFromOtherNode 10 thrpt 10 162.367 ± 21.443 ops/ms [info] ORSetMergeBenchmark.mergeAddFromOtherNode 20 thrpt 10 103.423 ± 1.569 ops/ms [info] ORSetMergeBenchmark.mergeAddFromOtherNode 100 thrpt 10 18.690 ± 0.642 ops/ms [info] ORSetMergeBenchmark.mergeAddFromSameNode 1 thrpt 10 3666.086 ± 330.087 ops/ms [info] ORSetMergeBenchmark.mergeAddFromSameNode 10 thrpt 10 2404.863 ± 136.244 ops/ms [info] ORSetMergeBenchmark.mergeAddFromSameNode 20 thrpt 10 2423.596 ± 142.533 ops/ms [info] ORSetMergeBenchmark.mergeAddFromSameNode 100 thrpt 10 2094.802 ± 161.307 ops/ms [info] ORSetMergeBenchmark.mergeComplex 1 thrpt 10 326.784 ± 6.665 ops/ms [info] ORSetMergeBenchmark.mergeComplex 10 thrpt 10 133.394 ± 4.749 ops/ms [info] ORSetMergeBenchmark.mergeComplex 20 thrpt 10 88.241 ± 1.733 ops/ms [info] ORSetMergeBenchmark.mergeComplex 100 thrpt 10 18.117 ± 0.543 ops/ms BEFORE: [info] Benchmark (set1Size) Mode Cnt Score Error Units [info] ORSetMergeBenchmark.mergeAddFromBothNodes 1 thrpt 10 737.646 ± 10.289 ops/ms [info] ORSetMergeBenchmark.mergeAddFromBothNodes 10 thrpt 10 146.706 ± 6.331 ops/ms [info] ORSetMergeBenchmark.mergeAddFromBothNodes 20 thrpt 10 95.553 ± 1.801 ops/ms [info] ORSetMergeBenchmark.mergeAddFromBothNodes 100 thrpt 10 18.321 ± 0.586 ops/ms [info] ORSetMergeBenchmark.mergeAddFromOtherNode 1 thrpt 10 1274.526 ± 23.732 ops/ms [info] ORSetMergeBenchmark.mergeAddFromOtherNode 10 thrpt 10 162.426 ± 20.490 ops/ms [info] ORSetMergeBenchmark.mergeAddFromOtherNode 20 thrpt 10 102.436 ± 2.435 ops/ms [info] ORSetMergeBenchmark.mergeAddFromOtherNode 100 thrpt 10 18.911 ± 0.659 ops/ms [info] ORSetMergeBenchmark.mergeAddFromSameNode 1 thrpt 10 653.358 ± 71.232 ops/ms [info] ORSetMergeBenchmark.mergeAddFromSameNode 10 thrpt 10 147.385 ± 2.750 ops/ms [info] ORSetMergeBenchmark.mergeAddFromSameNode 20 thrpt 10 94.280 ± 0.894 ops/ms [info] ORSetMergeBenchmark.mergeAddFromSameNode 100 thrpt 10 17.922 ± 1.522 ops/ms [info] ORSetMergeBenchmark.mergeComplex 1 thrpt 10 335.060 ± 8.385 ops/ms [info] ORSetMergeBenchmark.mergeComplex 10 thrpt 10 134.438 ± 3.044 ops/ms [info] ORSetMergeBenchmark.mergeComplex 20 thrpt 10 86.015 ± 2.145 ops/ms [info] ORSetMergeBenchmark.mergeComplex 100 thrpt 10 17.611 ± 0.136 ops/ms
135 lines
4 KiB
Scala
135 lines
4 KiB
Scala
/**
|
|
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
|
|
*/
|
|
package akka.cluster.ddata
|
|
|
|
import akka.cluster.Cluster
|
|
import akka.cluster.UniqueAddress
|
|
import java.math.BigInteger
|
|
|
|
object GCounter {
|
|
val empty: GCounter = new GCounter
|
|
def apply(): GCounter = empty
|
|
/**
|
|
* Java API
|
|
*/
|
|
def create(): GCounter = empty
|
|
|
|
/**
|
|
* Extract the [[GCounter#value]].
|
|
*/
|
|
def unapply(c: GCounter): Option[BigInt] = Some(c.value)
|
|
|
|
private val Zero = BigInt(0)
|
|
}
|
|
|
|
/**
|
|
* Implements a 'Growing Counter' CRDT, also called a 'G-Counter'.
|
|
*
|
|
* It is described in the paper
|
|
* <a href="http://hal.upmc.fr/file/index/docid/555588/filename/techreport.pdf">A comprehensive study of Convergent and Commutative Replicated Data Types</a>.
|
|
*
|
|
* A G-Counter is a increment-only counter (inspired by vector clocks) in
|
|
* which only increment and merge are possible. Incrementing the counter
|
|
* adds 1 to the count for the current node. Divergent histories are
|
|
* resolved by taking the maximum count for each node (like a vector
|
|
* clock merge). The value of the counter is the sum of all node counts.
|
|
*
|
|
* This class is immutable, i.e. "modifying" methods return a new instance.
|
|
*/
|
|
@SerialVersionUID(1L)
|
|
final class GCounter private[akka] (
|
|
private[akka] val state: Map[UniqueAddress, BigInt] = Map.empty)
|
|
extends ReplicatedData with ReplicatedDataSerialization with RemovedNodePruning with FastMerge {
|
|
|
|
import GCounter.Zero
|
|
|
|
type T = GCounter
|
|
|
|
/**
|
|
* Scala API: Current total value of the counter.
|
|
*/
|
|
def value: BigInt = state.values.foldLeft(Zero) { (acc, v) ⇒ acc + v }
|
|
|
|
/**
|
|
* Java API: Current total value of the counter.
|
|
*/
|
|
def getValue: BigInteger = value.bigInteger
|
|
|
|
/**
|
|
* Increment the counter with the delta specified.
|
|
* The delta must be zero or positive.
|
|
*/
|
|
def +(delta: Long)(implicit node: Cluster): GCounter = increment(node, delta)
|
|
|
|
/**
|
|
* Increment the counter with the delta specified.
|
|
* The delta must be zero or positive.
|
|
*/
|
|
def increment(node: Cluster, delta: Long = 1): GCounter =
|
|
increment(node.selfUniqueAddress, delta)
|
|
|
|
/**
|
|
* INTERNAL API
|
|
*/
|
|
private[akka] def increment(key: UniqueAddress): GCounter = increment(key, 1)
|
|
|
|
/**
|
|
* INTERNAL API
|
|
*/
|
|
private[akka] def increment(key: UniqueAddress, delta: BigInt): GCounter = {
|
|
require(delta >= 0, "Can't decrement a GCounter")
|
|
if (delta == 0) this
|
|
else state.get(key) match {
|
|
case Some(v) ⇒
|
|
val tot = v + delta
|
|
assignAncestor(new GCounter(state + (key -> tot)))
|
|
case None ⇒ assignAncestor(new GCounter(state + (key -> delta)))
|
|
}
|
|
}
|
|
|
|
override def merge(that: GCounter): GCounter =
|
|
if ((this eq that) || that.isAncestorOf(this)) this.clearAncestor()
|
|
else if (this.isAncestorOf(that)) that.clearAncestor()
|
|
else {
|
|
var merged = that.state
|
|
for ((key, thisValue) ← state) {
|
|
val thatValue = merged.getOrElse(key, Zero)
|
|
if (thisValue > thatValue)
|
|
merged = merged.updated(key, thisValue)
|
|
}
|
|
clearAncestor()
|
|
new GCounter(merged)
|
|
}
|
|
|
|
override def needPruningFrom(removedNode: UniqueAddress): Boolean =
|
|
state.contains(removedNode)
|
|
|
|
override def prune(removedNode: UniqueAddress, collapseInto: UniqueAddress): GCounter =
|
|
state.get(removedNode) match {
|
|
case Some(value) ⇒ new GCounter(state - removedNode).increment(collapseInto, value)
|
|
case None ⇒ this
|
|
}
|
|
|
|
override def pruningCleanup(removedNode: UniqueAddress): GCounter =
|
|
new GCounter(state - removedNode)
|
|
|
|
// this class cannot be a `case class` because we need different `unapply`
|
|
|
|
override def toString: String = s"GCounter($value)"
|
|
|
|
override def equals(o: Any): Boolean = o match {
|
|
case other: GCounter ⇒ state == other.state
|
|
case _ ⇒ false
|
|
}
|
|
|
|
override def hashCode: Int = state.hashCode
|
|
|
|
}
|
|
|
|
object GCounterKey {
|
|
def create(id: String): Key[GCounter] = GCounterKey(id)
|
|
}
|
|
|
|
@SerialVersionUID(1L)
|
|
final case class GCounterKey(_id: String) extends Key[GCounter](_id) with ReplicatedDataSerialization
|