Improve performance of DData delta updates, #25310
* Use deterministic order of the target nodes for the writes when type RequiresCausalDeliveryOfDeltas, otherwise the random pick of targets caused that delta sequence numbers were missing for susequent updates * Resend immediately when receiving DeltaNack instead of waiting for timeout. DeltaNack can happen when there are multiple concurrent updates from same node because each starts a WriteAggregator and a later Update might bypass an earlier
This commit is contained in:
parent
db48514b88
commit
180ef934bc
3 changed files with 59 additions and 8 deletions
|
|
@ -1,3 +1,7 @@
|
||||||
|
# #25310 Improve performance of DData delta updates
|
||||||
|
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.ddata.ReadWriteAggregator.secondaryNodes")
|
||||||
|
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.ddata.ReadWriteAggregator.primaryNodes")
|
||||||
|
|
||||||
# #23703 Optimized serializer for ORSet[ActorRef]
|
# #23703 Optimized serializer for ORSet[ActorRef]
|
||||||
ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.cluster.ddata.protobuf.msg.ReplicatedDataMessages#GSetOrBuilder.getActorRefElementsCount")
|
ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.cluster.ddata.protobuf.msg.ReplicatedDataMessages#GSetOrBuilder.getActorRefElementsCount")
|
||||||
ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.cluster.ddata.protobuf.msg.ReplicatedDataMessages#GSetOrBuilder.getActorRefElementsList")
|
ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.cluster.ddata.protobuf.msg.ReplicatedDataMessages#GSetOrBuilder.getActorRefElementsList")
|
||||||
|
|
|
||||||
|
|
@ -1948,13 +1948,17 @@ final class Replicator(settings: ReplicatorSettings) extends Actor with ActorLog
|
||||||
|
|
||||||
def doneWhenRemainingSize: Int
|
def doneWhenRemainingSize: Int
|
||||||
|
|
||||||
lazy val (primaryNodes, secondaryNodes) = {
|
def primaryAndSecondaryNodes(requiresCausalDeliveryOfDeltas: Boolean): (Vector[Address], Vector[Address]) = {
|
||||||
val primarySize = nodes.size - doneWhenRemainingSize
|
val primarySize = nodes.size - doneWhenRemainingSize
|
||||||
if (primarySize >= nodes.size)
|
if (primarySize >= nodes.size)
|
||||||
(nodes, Set.empty[Address])
|
(nodes.toVector, Vector.empty[Address])
|
||||||
else {
|
else {
|
||||||
// Prefer to use reachable nodes over the unreachable nodes first
|
// Prefer to use reachable nodes over the unreachable nodes first.
|
||||||
val orderedNodes = scala.util.Random.shuffle(reachableNodes.toVector) ++ scala.util.Random.shuffle(unreachable.toVector)
|
// When RequiresCausalDeliveryOfDeltas use deterministic order to so that sequence numbers of subsequent
|
||||||
|
// updates are in sync on the destination nodes.
|
||||||
|
val orderedNodes =
|
||||||
|
if (requiresCausalDeliveryOfDeltas) reachableNodes.toVector.sorted ++ unreachable.toVector.sorted
|
||||||
|
else scala.util.Random.shuffle(reachableNodes.toVector) ++ scala.util.Random.shuffle(unreachable.toVector)
|
||||||
val (p, s) = orderedNodes.splitAt(primarySize)
|
val (p, s) = orderedNodes.splitAt(primarySize)
|
||||||
(p, s.take(MaxSecondaryNodes))
|
(p, s.take(MaxSecondaryNodes))
|
||||||
}
|
}
|
||||||
|
|
@ -2030,6 +2034,14 @@ final class Replicator(settings: ReplicatorSettings) extends Actor with ActorLog
|
||||||
var gotLocalStoreReply = !durable
|
var gotLocalStoreReply = !durable
|
||||||
var gotWriteNackFrom = Set.empty[Address]
|
var gotWriteNackFrom = Set.empty[Address]
|
||||||
|
|
||||||
|
private val (primaryNodes, secondaryNodes) = {
|
||||||
|
val requiresCausalDeliveryOfDeltas = delta match {
|
||||||
|
case None ⇒ false
|
||||||
|
case Some(d) ⇒ d.dataEnvelope.data.isInstanceOf[RequiresCausalDeliveryOfDeltas]
|
||||||
|
}
|
||||||
|
primaryAndSecondaryNodes(requiresCausalDeliveryOfDeltas)
|
||||||
|
}
|
||||||
|
|
||||||
override def preStart(): Unit = {
|
override def preStart(): Unit = {
|
||||||
val msg = deltaMsg match {
|
val msg = deltaMsg match {
|
||||||
case Some(d) ⇒ d
|
case Some(d) ⇒ d
|
||||||
|
|
@ -2048,7 +2060,10 @@ final class Replicator(settings: ReplicatorSettings) extends Actor with ActorLog
|
||||||
gotWriteNackFrom += senderAddress()
|
gotWriteNackFrom += senderAddress()
|
||||||
if (isDone) reply(isTimeout = false)
|
if (isDone) reply(isTimeout = false)
|
||||||
case DeltaNack ⇒
|
case DeltaNack ⇒
|
||||||
// ok, will be retried with full state
|
// Deltas must be applied in order and we can't keep track of ordering of
|
||||||
|
// simultaneous updates so there is a chance that the delta could not be applied.
|
||||||
|
// Try again with the full state
|
||||||
|
sender() ! writeMsg
|
||||||
|
|
||||||
case _: Replicator.UpdateSuccess[_] ⇒
|
case _: Replicator.UpdateSuccess[_] ⇒
|
||||||
gotLocalStoreReply = true
|
gotLocalStoreReply = true
|
||||||
|
|
@ -2148,6 +2163,10 @@ final class Replicator(settings: ReplicatorSettings) extends Actor with ActorLog
|
||||||
|
|
||||||
val readMsg = Read(key.id)
|
val readMsg = Read(key.id)
|
||||||
|
|
||||||
|
private val (primaryNodes, secondaryNodes) = {
|
||||||
|
primaryAndSecondaryNodes(requiresCausalDeliveryOfDeltas = false)
|
||||||
|
}
|
||||||
|
|
||||||
override def preStart(): Unit = {
|
override def preStart(): Unit = {
|
||||||
primaryNodes.foreach { replica(_) ! readMsg }
|
primaryNodes.foreach { replica(_) ! readMsg }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ object PerformanceSpec extends MultiNodeConfig {
|
||||||
commonConfig(ConfigFactory.parseString(s"""
|
commonConfig(ConfigFactory.parseString(s"""
|
||||||
akka.loglevel = ERROR
|
akka.loglevel = ERROR
|
||||||
akka.stdout-loglevel = ERROR
|
akka.stdout-loglevel = ERROR
|
||||||
|
akka.loggers = ["akka.event.Logging$$DefaultLogger"]
|
||||||
akka.actor.provider = "cluster"
|
akka.actor.provider = "cluster"
|
||||||
akka.log-dead-letters = off
|
akka.log-dead-letters = off
|
||||||
akka.log-dead-letters-during-shutdown = off
|
akka.log-dead-letters-during-shutdown = off
|
||||||
|
|
@ -81,21 +82,28 @@ class PerformanceSpec extends MultiNodeSpec(PerformanceSpec) with STMultiNodeSpe
|
||||||
enterBarrier(from.name + "-joined")
|
enterBarrier(from.name + "-joined")
|
||||||
}
|
}
|
||||||
|
|
||||||
def repeat(description: String, keys: Iterable[ORSetKey[Int]], n: Int, expectedAfterReplication: Option[Set[Int]] = None)(
|
def repeat(description: String, keys: Iterable[ORSetKey[Int]], n: Int,
|
||||||
|
expectedAfterReplication: Option[Set[Int]] = None, oneByOne: Boolean = false)(
|
||||||
block: (ORSetKey[Int], Int, ActorRef) ⇒ Unit, afterEachKey: ORSetKey[Int] ⇒ Unit = _ ⇒ ()): Unit = {
|
block: (ORSetKey[Int], Int, ActorRef) ⇒ Unit, afterEachKey: ORSetKey[Int] ⇒ Unit = _ ⇒ ()): Unit = {
|
||||||
|
|
||||||
keys.foreach { key ⇒
|
keys.foreach { key ⇒
|
||||||
val startTime = System.nanoTime()
|
val startTime = System.nanoTime()
|
||||||
runOn(n1) {
|
runOn(n1) {
|
||||||
val latch = TestLatch(n)
|
val latch = TestLatch(n)
|
||||||
val replyTo = system.actorOf(countDownProps(latch))
|
val oneByOneProbe = TestProbe()
|
||||||
|
val replyTo =
|
||||||
|
if (oneByOne) oneByOneProbe.ref
|
||||||
|
else system.actorOf(countDownProps(latch))
|
||||||
|
|
||||||
var i = 0
|
var i = 0
|
||||||
while (i < n) {
|
while (i < n) {
|
||||||
block(key, i, replyTo)
|
block(key, i, replyTo)
|
||||||
i += 1
|
i += 1
|
||||||
|
if (oneByOne)
|
||||||
|
oneByOneProbe.receiveOne(timeout)
|
||||||
}
|
}
|
||||||
Await.ready(latch, 10.seconds + (2.second * factor))
|
if (!oneByOne)
|
||||||
|
Await.ready(latch, 10.seconds + (2.second * factor))
|
||||||
}
|
}
|
||||||
expectedAfterReplication.foreach { expected ⇒
|
expectedAfterReplication.foreach { expected ⇒
|
||||||
enterBarrier("repeat-" + key + "-before-awaitReplicated")
|
enterBarrier("repeat-" + key + "-before-awaitReplicated")
|
||||||
|
|
@ -234,6 +242,26 @@ class PerformanceSpec extends MultiNodeSpec(PerformanceSpec) with STMultiNodeSpe
|
||||||
enterBarrier("after-6")
|
enterBarrier("after-6")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"be good for ORSet one-by-one deltas" taggedAs PerformanceTest in {
|
||||||
|
val keys = (1 to repeatCount).map(n ⇒ ORSetKey[Int]("E" + n))
|
||||||
|
val n = 300 * factor
|
||||||
|
val writeMajority = WriteMajority(timeout)
|
||||||
|
repeat("ORSet Update one-by-one deltas", keys, n, oneByOne = true) { (key, i, replyTo) ⇒
|
||||||
|
replicator.tell(Update(key, ORSet(), writeMajority)(_ + i), replyTo)
|
||||||
|
}
|
||||||
|
enterBarrier("after-7")
|
||||||
|
}
|
||||||
|
|
||||||
|
"be good for ORSet deltas" taggedAs PerformanceTest in {
|
||||||
|
val keys = (1 to repeatCount).map(n ⇒ ORSetKey[Int]("F" + n))
|
||||||
|
val n = 200 * factor
|
||||||
|
val writeMajority = WriteMajority(timeout)
|
||||||
|
repeat("ORSet Update deltas", keys, n, oneByOne = false) { (key, i, replyTo) ⇒
|
||||||
|
replicator.tell(Update(key, ORSet(), writeMajority)(_ + i), replyTo)
|
||||||
|
}
|
||||||
|
enterBarrier("after-8")
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue