pekko/akka-persistence/src/test/scala/akka/persistence/PerformanceSpec.scala
Martin Krasser 4e5ce5529c !per #3761 Reliable channels
- Built-in redelivery mechanism for Channel and PersistentChannel
- redelivery counter on ConfirmablePersistent
- redeliveries out of initial message delivery order
- relative order of redelivered messages is preserved
- configurable redelivery policy (ChannelSettings)
- Major refactorings of channels (and channel tests)
- Throughput load test for PersistentChannel

Todo:

- Paged/throtlled replay (another pull request)
- Resequencer (another pull request)
2013-12-12 12:22:46 +01:00

204 lines
6.8 KiB
Scala

package akka.persistence
import scala.concurrent.duration._
import scala.language.postfixOps
import com.typesafe.config.ConfigFactory
import akka.actor._
import akka.testkit._
object PerformanceSpec {
// multiply cycles with 200 for more
// accurate throughput measurements
val config =
"""
akka.persistence.performance.cycles.warmup = 300
akka.persistence.performance.cycles.load = 1000
"""
case object StartMeasure
case object StopMeasure
case class FailAt(sequenceNr: Long)
trait Measure extends { this: Actor
val NanoToSecond = 1000.0 * 1000 * 1000
var startTime: Long = 0L
var stopTime: Long = 0L
var startSequenceNr = 0L;
var stopSequenceNr = 0L;
def startMeasure(): Unit = {
startSequenceNr = lastSequenceNr
startTime = System.nanoTime
}
def stopMeasure(): Unit = {
stopSequenceNr = lastSequenceNr
stopTime = System.nanoTime
sender ! (NanoToSecond * (stopSequenceNr - startSequenceNr) / (stopTime - startTime))
}
def lastSequenceNr: Long
}
class PerformanceTestDestination extends Actor with Measure {
var lastSequenceNr = 0L
val confirm: PartialFunction[Any, Any] = {
case cp @ ConfirmablePersistent(payload, sequenceNr, _)
lastSequenceNr = sequenceNr
cp.confirm()
payload
}
def receive = confirm andThen {
case StartMeasure startMeasure()
case StopMeasure stopMeasure()
case m if (lastSequenceNr % 1000 == 0) print(".")
}
}
abstract class PerformanceTestProcessor(name: String) extends NamedProcessor(name) with Measure {
var failAt: Long = -1
val controlBehavior: Receive = {
case StartMeasure startMeasure()
case StopMeasure stopMeasure()
case FailAt(sequenceNr) failAt = sequenceNr
}
override def postRestart(reason: Throwable) {
super.postRestart(reason)
receive(StartMeasure)
}
}
class CommandsourcedTestProcessor(name: String) extends PerformanceTestProcessor(name) {
def receive = controlBehavior orElse {
case p: Persistent
if (lastSequenceNr % 1000 == 0) if (recoveryRunning) print("r") else print(".")
if (lastSequenceNr == failAt) throw new TestException("boom")
}
}
class EventsourcedTestProcessor(name: String) extends PerformanceTestProcessor(name) with EventsourcedProcessor {
val receiveReplay: Receive = {
case _ if (lastSequenceNr % 1000 == 0) print("r")
}
val receiveCommand: Receive = controlBehavior orElse {
case cmd persist(cmd) { _
if (lastSequenceNr % 1000 == 0) print(".")
if (lastSequenceNr == failAt) throw new TestException("boom")
}
}
}
class StashingEventsourcedTestProcessor(name: String) extends PerformanceTestProcessor(name) with EventsourcedProcessor {
val receiveReplay: Receive = {
case _ if (lastSequenceNr % 1000 == 0) print("r")
}
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 other stash()
}
}
}
class PerformanceSpec extends AkkaSpec(PersistenceSpec.config("leveldb", "performance", serialization = "off").withFallback(ConfigFactory.parseString(PerformanceSpec.config))) with PersistenceSpec with ImplicitSender {
import PerformanceSpec._
val warmupCycles = system.settings.config.getInt("akka.persistence.performance.cycles.warmup")
val loadCycles = system.settings.config.getInt("akka.persistence.performance.cycles.load")
def stressCommandsourcedProcessor(failAt: Option[Long]): Unit = {
val processor = namedProcessor[CommandsourcedTestProcessor]
failAt foreach { processor ! FailAt(_) }
1 to warmupCycles foreach { i processor ! Persistent(s"msg${i}") }
processor ! StartMeasure
1 to loadCycles foreach { i processor ! Persistent(s"msg${i}") }
processor ! StopMeasure
expectMsgPF(100 seconds) {
case throughput: Double println(f"\nthroughput = $throughput%.2f persistent commands per second")
}
}
def stressEventsourcedProcessor(failAt: Option[Long]): Unit = {
val processor = namedProcessor[EventsourcedTestProcessor]
failAt foreach { processor ! FailAt(_) }
1 to warmupCycles foreach { i processor ! s"msg${i}" }
processor ! StartMeasure
1 to loadCycles foreach { i processor ! s"msg${i}" }
processor ! StopMeasure
expectMsgPF(100 seconds) {
case throughput: Double println(f"\nthroughput = $throughput%.2f persistent events per second")
}
}
def stressStashingEventsourcedProcessor(): Unit = {
val processor = namedProcessor[StashingEventsourcedTestProcessor]
1 to warmupCycles foreach { i processor ! "b" }
processor ! StartMeasure
val cmds = 1 to (loadCycles / 3) flatMap (_ List("a", "b", "c"))
processor ! StartMeasure
cmds foreach (processor ! _)
processor ! StopMeasure
expectMsgPF(100 seconds) {
case throughput: Double println(f"\nthroughput = $throughput%.2f persistent events per second")
}
}
def stressPersistentChannel(): Unit = {
val channel = system.actorOf(PersistentChannel.props())
val destination = system.actorOf(Props[PerformanceTestDestination])
1 to warmupCycles foreach { i channel ! Deliver(Persistent(s"msg${i}"), destination) }
channel ! Deliver(Persistent(StartMeasure), destination)
1 to loadCycles foreach { i channel ! Deliver(Persistent(s"msg${i}"), destination) }
channel ! Deliver(Persistent(StopMeasure), destination)
expectMsgPF(100 seconds) {
case throughput: Double println(f"\nthroughput = $throughput%.2f persistent commands per second")
}
}
"A command sourced processor" should {
"have some reasonable throughput" in {
stressCommandsourcedProcessor(None)
}
"have some reasonable throughput under failure conditions" in {
stressCommandsourcedProcessor(Some(warmupCycles + loadCycles / 10))
}
}
"An event sourced processor" should {
"have some reasonable throughput" in {
stressEventsourcedProcessor(None)
}
"have some reasonable throughput under failure conditions" in {
stressEventsourcedProcessor(Some(warmupCycles + loadCycles / 10))
}
"have some reasonable throughput with stashing and unstashing every 3rd command" in {
stressStashingEventsourcedProcessor()
}
}
"A persistent channel" should {
"have some reasonable throughput" in {
stressPersistentChannel()
}
}
}