197 lines
6 KiB
Scala
197 lines
6 KiB
Scala
/*
|
|
* Copyright (C) 2009-2019 Lightbend Inc. <https://www.lightbend.com>
|
|
*/
|
|
|
|
package akka.persistence
|
|
|
|
import scala.concurrent.duration._
|
|
|
|
import com.typesafe.config.ConfigFactory
|
|
|
|
import akka.actor._
|
|
import akka.testkit._
|
|
|
|
object PerformanceSpec {
|
|
val config =
|
|
"""
|
|
akka.persistence.performance.cycles.load = 100
|
|
# more accurate throughput measurements
|
|
#akka.persistence.performance.cycles.load = 200000
|
|
"""
|
|
|
|
case object StopMeasure
|
|
final case class FailAt(sequenceNr: Long)
|
|
|
|
class Measure(numberOfMessages: Int) {
|
|
private val NanoToSecond = 1000.0 * 1000 * 1000
|
|
|
|
private var startTime: Long = 0L
|
|
private var stopTime: Long = 0L
|
|
|
|
def startMeasure(): Unit = {
|
|
startTime = System.nanoTime
|
|
}
|
|
|
|
def stopMeasure(): Double = {
|
|
stopTime = System.nanoTime
|
|
(NanoToSecond * numberOfMessages / (stopTime - startTime))
|
|
}
|
|
}
|
|
|
|
abstract class PerformanceTestPersistentActor(name: String) extends NamedPersistentActor(name) {
|
|
var failAt: Long = -1
|
|
|
|
override val receiveRecover: Receive = {
|
|
case _ => if (lastSequenceNr % 1000 == 0) print("r")
|
|
}
|
|
|
|
val controlBehavior: Receive = {
|
|
case StopMeasure => deferAsync(StopMeasure)(_ => sender() ! StopMeasure)
|
|
case FailAt(sequenceNr) => failAt = sequenceNr
|
|
}
|
|
|
|
}
|
|
|
|
class CommandsourcedTestPersistentActor(name: String) extends PerformanceTestPersistentActor(name) {
|
|
|
|
override val receiveCommand: Receive = controlBehavior.orElse {
|
|
case cmd =>
|
|
persistAsync(cmd) { _ =>
|
|
if (lastSequenceNr % 1000 == 0) print(".")
|
|
if (lastSequenceNr == failAt) throw new TestException("boom")
|
|
}
|
|
}
|
|
}
|
|
|
|
class EventsourcedTestPersistentActor(name: String) extends PerformanceTestPersistentActor(name) {
|
|
|
|
override val receiveCommand: Receive = controlBehavior.orElse {
|
|
case cmd =>
|
|
persist(cmd) { _ =>
|
|
if (lastSequenceNr % 1000 == 0) print(".")
|
|
if (lastSequenceNr == failAt) throw new TestException("boom")
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* `persist` every 10th message, otherwise `persistAsync`
|
|
*/
|
|
class MixedTestPersistentActor(name: String) extends PerformanceTestPersistentActor(name) {
|
|
var counter = 0
|
|
|
|
val handler: Any => Unit = { _ =>
|
|
if (lastSequenceNr % 1000 == 0) print(".")
|
|
if (lastSequenceNr == failAt) throw new TestException("boom")
|
|
}
|
|
|
|
val receiveCommand: Receive = controlBehavior.orElse {
|
|
case cmd =>
|
|
counter += 1
|
|
if (counter % 10 == 0) persist(cmd)(handler)
|
|
else persistAsync(cmd)(handler)
|
|
}
|
|
}
|
|
|
|
class StashingEventsourcedTestPersistentActor(name: String) extends PerformanceTestPersistentActor(name) {
|
|
|
|
val printProgress: PartialFunction[Any, Any] = {
|
|
case m => if (lastSequenceNr % 1000 == 0) print("."); m
|
|
}
|
|
|
|
val receiveCommand: Receive = printProgress.andThen(controlBehavior.orElse {
|
|
case "a" => persist("a")(_ => context.become(processC))
|
|
case "b" => persist("b")(_ => ())
|
|
})
|
|
|
|
val processC: Receive = printProgress.andThen {
|
|
case "c" =>
|
|
persist("c")(_ => context.unbecome())
|
|
unstashAll()
|
|
case _ => stash()
|
|
}
|
|
}
|
|
}
|
|
|
|
class PerformanceSpec
|
|
extends PersistenceSpec(
|
|
PersistenceSpec
|
|
.config("leveldb", "PerformanceSpec", serialization = "off")
|
|
.withFallback(ConfigFactory.parseString(PerformanceSpec.config)))
|
|
with ImplicitSender {
|
|
import PerformanceSpec._
|
|
|
|
val loadCycles = system.settings.config.getInt("akka.persistence.performance.cycles.load")
|
|
|
|
def stressPersistentActor(persistentActor: ActorRef, failAt: Option[Long], description: String): Unit = {
|
|
failAt.foreach { persistentActor ! FailAt(_) }
|
|
val m = new Measure(loadCycles)
|
|
m.startMeasure()
|
|
(1 to loadCycles).foreach { i =>
|
|
persistentActor ! s"msg${i}"
|
|
}
|
|
persistentActor ! StopMeasure
|
|
expectMsg(100.seconds, StopMeasure)
|
|
println(f"\nthroughput = ${m.stopMeasure()}%.2f $description per second")
|
|
}
|
|
|
|
def stressCommandsourcedPersistentActor(failAt: Option[Long]): Unit = {
|
|
val persistentActor = namedPersistentActor[CommandsourcedTestPersistentActor]
|
|
stressPersistentActor(persistentActor, failAt, "persistent commands")
|
|
}
|
|
|
|
def stressEventSourcedPersistentActor(failAt: Option[Long]): Unit = {
|
|
val persistentActor = namedPersistentActor[EventsourcedTestPersistentActor]
|
|
stressPersistentActor(persistentActor, failAt, "persistent events")
|
|
}
|
|
|
|
def stressMixedPersistentActor(failAt: Option[Long]): Unit = {
|
|
val persistentActor = namedPersistentActor[MixedTestPersistentActor]
|
|
stressPersistentActor(persistentActor, failAt, "persistent events & commands")
|
|
}
|
|
|
|
def stressStashingPersistentActor(): Unit = {
|
|
val persistentActor = namedPersistentActor[StashingEventsourcedTestPersistentActor]
|
|
val m = new Measure(loadCycles)
|
|
m.startMeasure()
|
|
val cmds = (1 to (loadCycles / 3)).flatMap(_ => List("a", "b", "c"))
|
|
cmds.foreach(persistentActor ! _)
|
|
persistentActor ! StopMeasure
|
|
expectMsg(100.seconds, StopMeasure)
|
|
println(f"\nthroughput = ${m.stopMeasure()}%.2f persistent events per second")
|
|
}
|
|
|
|
"Warmup persistent actor" should {
|
|
"exercise" in {
|
|
stressCommandsourcedPersistentActor(None)
|
|
}
|
|
"exercise some more" in {
|
|
stressCommandsourcedPersistentActor(None)
|
|
}
|
|
}
|
|
|
|
"A command sourced persistent actor" should {
|
|
"have some reasonable throughput" in {
|
|
stressCommandsourcedPersistentActor(None)
|
|
}
|
|
}
|
|
|
|
"An event sourced persistent actor" should {
|
|
"have some reasonable throughput" in {
|
|
stressEventSourcedPersistentActor(None)
|
|
}
|
|
"have some reasonable throughput under failure conditions" in {
|
|
stressEventSourcedPersistentActor(Some(loadCycles / 10))
|
|
}
|
|
"have some reasonable throughput with stashing and unstashing every 3rd command" in {
|
|
stressStashingPersistentActor()
|
|
}
|
|
}
|
|
|
|
"A mixed command and event sourced persistent actor" should {
|
|
"have some reasonable throughput" in {
|
|
stressMixedPersistentActor(None)
|
|
}
|
|
}
|
|
|
|
}
|