pekko/akka-distributed-data/src/main/scala/akka/cluster/ddata/GCounter.scala
Patrik Nordwall e10593ec31 =cdd #18328 use ancestor field, for fast forward merge
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
2015-11-25 09:14:24 +01:00

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