pekko/akka-persistence/src/test/scala/akka/persistence/PerformanceSpec.scala

198 lines
6 KiB
Scala
Raw Normal View History

/*
* 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
2014-03-07 13:20:01 +01:00
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) {
2019-03-11 10:38:24 +01:00
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) {
2019-03-11 10:38:24 +01:00
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")
}
2019-03-11 10:38:24 +01:00
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
}
2019-03-11 10:38:24 +01:00
val receiveCommand: Receive = printProgress.andThen(controlBehavior.orElse {
case "a" => persist("a")(_ => context.become(processC))
case "b" => persist("b")(_ => ())
})
2019-03-11 10:38:24 +01:00
val processC: Receive = printProgress.andThen {
case "c" =>
persist("c")(_ => context.unbecome())
unstashAll()
case _ => stash()
}
}
}
2019-03-11 10:38:24 +01:00
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 = {
2019-03-11 10:38:24 +01:00
failAt.foreach { persistentActor ! FailAt(_) }
val m = new Measure(loadCycles)
m.startMeasure()
2019-03-11 10:38:24 +01:00
(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()
2019-03-11 10:38:24 +01:00
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 {
!per persistAsync Breaks binary compatibility because adding new methods to Eventsourced trait. Since akka-persistence is experimental this is ok, yet source-level compatibility has been perserved thankfuly :-) Deprecates: * Rename of EventsourcedProcessor -> PersistentActor * Processor -> suggest using PersistentActor * Migration guide for akka-persistence is separate, as wel'll deprecate in minor versions (its experimental) * Persistent as well as ConfirmablePersistent - since Processor, their main user will be removed soon. Other changes: * persistAsync works as expected when mixed with persist * A counter must be kept for pending stashing invocations * Uses only 1 shared list buffer for persit / persistAsync * Includes small benchmark * Docs also include info about not using Persistent() wrapper * uses java LinkedList, for best performance of append / head on persistInvocations; the get(0) is safe, because these msgs only come in response to persistInvocations * Renamed internal *MessagesSuccess/Failure messages because we kept small mistakes seeing the class "with s" and "without s" as the same * Updated everything that refered to EventsourcedProcessor to PersistentActor, including samples Refs #15227 Conflicts: akka-docs/rst/project/migration-guides.rst akka-persistence/src/main/scala/akka/persistence/JournalProtocol.scala akka-persistence/src/main/scala/akka/persistence/Persistent.scala akka-persistence/src/test/scala/akka/persistence/PersistentActorSpec.scala project/AkkaBuild.scala
2014-05-21 01:35:21 +02:00
stressStashingPersistentActor()
}
}
"A mixed command and event sourced persistent actor" should {
"have some reasonable throughput" in {
stressMixedPersistentActor(None)
}
}
}