Resilient Applications with Akka Persistence - Scaladays 2014

73
Resilient Applications with Akka Persistence Patrik Nordwall @patriknw Konrad Malawski @ktosopl Björn Antonsson @bantonsson

description

In this presentation you will learn how to leverage the features introduced in Akka Persistence: opt-in at-least-once delivery semantics between actors and the ability to recover application state after a crash. Both are implemented by storing immutable facts in a persisted append-only log. We will show you how to create persistent actors using command and event sourcing, replicate events with reliable communication, scale out and improve resilience with clustering.

Transcript of Resilient Applications with Akka Persistence - Scaladays 2014

Page 1: Resilient Applications with Akka Persistence - Scaladays 2014

Resilient Applications with Akka Persistence

Patrik Nordwall@patriknw

Konrad Malawski@ktosopl

Björn Antonsson@bantonsson

Page 2: Resilient Applications with Akka Persistence - Scaladays 2014

Reactive Applications

Akka  Persistence  ScalaDays  2014

Page 3: Resilient Applications with Akka Persistence - Scaladays 2014

Resilient

• Embrace Failure • Failure is a normal part of the application lifecycle

• Self Heal • Failure is detected, isolated, and managed

Akka  Persistence  ScalaDays  2014

Page 4: Resilient Applications with Akka Persistence - Scaladays 2014

The Naïve Way

• Write State to Database

• Transactions Everywhere

• Problem Solved?

• Not Scalable, Responsive, Event-Driven!

Akka  Persistence  ScalaDays  2014

Page 5: Resilient Applications with Akka Persistence - Scaladays 2014

Command and Event Sourcing

Page 6: Resilient Applications with Akka Persistence - Scaladays 2014

Command and Event Sourcing

• State is the sum of Events

• Events are persisted to Store

• Append only

• Scales well

Akka  Persistence  ScalaDays  2014

Page 7: Resilient Applications with Akka Persistence - Scaladays 2014

Command v.s. Event

• Command

• What someone wants me to do

• Can be rejected

• Event

• Something that has already happened

• An immutable fact

Akka  Persistence  ScalaDays  2014

Page 8: Resilient Applications with Akka Persistence - Scaladays 2014

Commands can Generate Events

• If I accept a Command and change State

• Persist Event to Store

• If I crash

• Replay Events to recover State

Akka  Persistence  ScalaDays  2014

Page 9: Resilient Applications with Akka Persistence - Scaladays 2014

Persist All Commands?

• If I crash on a Command

• I will likely crash during recovery

• Like the Army

• Don't question orders

• Repeat until success

Akka  Persistence  ScalaDays  2014

Page 10: Resilient Applications with Akka Persistence - Scaladays 2014

Only Persist Events

• Only accepted Commands generate Events

• No surprises during recovery

• Like a dieting method

• You are what you eat

Akka  Persistence  ScalaDays  2014

Page 11: Resilient Applications with Akka Persistence - Scaladays 2014

Achievement Unlocked?

• Resilient

• State is recoverable

• Scalable

• Append only writes

• Something Missing?

• Queries

Akka  Persistence  ScalaDays  2014

Page 12: Resilient Applications with Akka Persistence - Scaladays 2014

CQRSCommand Query Responsibility Segregation

Page 13: Resilient Applications with Akka Persistence - Scaladays 2014

CQRS

• Separate Models

• Command Model

• Optimized for command processing

• Query Model

• Optimized data presentation

Akka  Persistence  ScalaDays  2014

Page 14: Resilient Applications with Akka Persistence - Scaladays 2014

Query Model from Events

• Source the Events

• Pick what fits

• In Memory

• SQL Database

• Graph Database

• Key Value Store

Akka  Persistence  ScalaDays  2014

Page 15: Resilient Applications with Akka Persistence - Scaladays 2014

Akka  Persistence  ScalaDays  2014

Client

Service

Query  Model

Command Store

Query Store

Command  Model

Page 16: Resilient Applications with Akka Persistence - Scaladays 2014

PersistentActor

Akka  Persistence  ScalaDays  2014

Page 17: Resilient Applications with Akka Persistence - Scaladays 2014

PersistentActor

Processor & Eventsourced ProcessorReplaces:

in Akka 2.3.4+

Page 18: Resilient Applications with Akka Persistence - Scaladays 2014

super quick domain modelling!

sealed trait Command!case class GiveMe(coins: Int) extends Command!case class TakeMy(coins: Int) extends Command

Commands - what others “tell” us; not persisted

case class Wallet(coins: Int) {! def updated(diff: Int) = State(coins + diff)!}

State - reflection of a series of events

sealed trait Event!case class BalanceChangedBy(coins: Int) extends Event!

Events - reflect effects, past tense; persisted

Page 19: Resilient Applications with Akka Persistence - Scaladays 2014

var state = S0 !

def processorId = “a” !

PersistentActor

Command

!

!

Journal

Page 20: Resilient Applications with Akka Persistence - Scaladays 2014

PersistentActor

var state = S0 !

def processorId = “a” !

!

!

Journal

Generate Events

Page 21: Resilient Applications with Akka Persistence - Scaladays 2014

PersistentActor

var state = S0 !

def processorId = “a” !

!

!

Journal

Generate Events

E1

Page 22: Resilient Applications with Akka Persistence - Scaladays 2014

PersistentActor

ACK “persisted”

!

!

Journal

E1

var state = S0 !

def processorId = “a” !

Page 23: Resilient Applications with Akka Persistence - Scaladays 2014

PersistentActor

“Apply” event

!

!

Journal

E1

var state = S1 !

def processorId = “a” !

E1

Page 24: Resilient Applications with Akka Persistence - Scaladays 2014

PersistentActor

!

!

Journal

E1

var state = S1 !

def processorId = “a” !

E1

Okey!

Page 25: Resilient Applications with Akka Persistence - Scaladays 2014

PersistentActor

!

!

Journal

E1

var state = S1 !

def processorId = “a” !

E1

Okey!

Page 26: Resilient Applications with Akka Persistence - Scaladays 2014

PersistentActor

!

!

Journal

E1

var state = S1 !

def processorId = “a” !

E1

Ok, he got my $.

Page 27: Resilient Applications with Akka Persistence - Scaladays 2014

PersistentActor

class BitCoinWallet extends PersistentActor {!! var state = Wallet(coins = 0)!! def updateState(e: Event): State = {! case BalanceChangedBy(coins) => state.updatedWith(coins)! }! ! // API:!! def receiveCommand = ??? // TODO!! def receiveRecover = ??? // TODO!!}!

Page 28: Resilient Applications with Akka Persistence - Scaladays 2014

persist(e) { e => }

Page 29: Resilient Applications with Akka Persistence - Scaladays 2014

PersistentActor

def receiveCommand = {!! case TakeMy(coins) =>! persist(BalanceChangedBy(coins)) { changed =>! state = updateState(changed) ! }!!!!!!!}

async callback

Page 30: Resilient Applications with Akka Persistence - Scaladays 2014

PersistentActor: persist(){}

def receiveCommand = {!!!!!!! case GiveMe(coins) if coins <= state.coins =>! persist(BalanceChangedBy(-coins)) { changed =>! state = updateState(changed) ! sender() ! TakeMy(coins)! }!}

async callbackSafe to mutate

the Actor’s state

Page 31: Resilient Applications with Akka Persistence - Scaladays 2014

PersistentActor

def receiveCommand = {!!!!!!! case GiveMe(coins) if coins <= state.coins =>! persist(BalanceChangedBy(-coins)) { changed =>! state = updateState(changed) ! sender() ! TakeMy(coins)! }!}

Safe to access sender here

Page 32: Resilient Applications with Akka Persistence - Scaladays 2014

persist(){} - Ordering guarantees

!

!

Journal

E1

var state = S0 !

def processorId = “a” !

C1C2

C3

Page 33: Resilient Applications with Akka Persistence - Scaladays 2014

!

!

Journal

E1

var state = S0 !

def processorId = “a” !

C1C2

C3

Commands get “stashed” until processing C1’s events are acted upon.

persist(){} - Ordering guarantees

Page 34: Resilient Applications with Akka Persistence - Scaladays 2014

!

!

Journal

var state = S0 !

def processorId = “a” !

C1C2

C3 E1

E2

E2E1

events get applied in-order

persist(){} - Ordering guarantees

Page 35: Resilient Applications with Akka Persistence - Scaladays 2014

C2

!

!

Journal

var state = S0 !

def processorId = “a” !

C3 E1 E2

E2E1

and the cycle repeats

persist(){} - Ordering guarantees

Page 36: Resilient Applications with Akka Persistence - Scaladays 2014

persistAsync(e) { e => }

Page 37: Resilient Applications with Akka Persistence - Scaladays 2014

persistAsync(e) { e => } + defer(e) { e => }

Page 38: Resilient Applications with Akka Persistence - Scaladays 2014

def receiveCommand = {!!!! case Mark(id) =>! sender() ! InitMarking! persistAsync(Marker) { m =>! // update state...! }!!!!!}

persistAsync

PersistentActor: persistAsync(){}

will NOT force stashing of commands

Page 39: Resilient Applications with Akka Persistence - Scaladays 2014

PersistentActor: persistAsync(){}

def receiveCommand = {!!!! case Mark(id) =>! sender() ! InitMarking! persistAsync(Marker) { m =>! // update state...! }!! defer(Marked(id)) { marked =>! sender() ! marked! }!}

execute once all persistAsync handlers done

NOT persisted

Page 40: Resilient Applications with Akka Persistence - Scaladays 2014

persistAsync(){} - Ordering guarantees

!

!

Journal

var state = S0 !

def processorId = “a” !

C1C2

C3

Page 41: Resilient Applications with Akka Persistence - Scaladays 2014

persistAsync(){} - Ordering guarantees

!

!

Journal

var state = S0 !

def processorId = “a” !C2

C3

Page 42: Resilient Applications with Akka Persistence - Scaladays 2014

persistAsync(){} - Ordering guarantees

!

!

Journal

var state = S0 !

def processorId = “a” !

C3

Page 43: Resilient Applications with Akka Persistence - Scaladays 2014

persistAsync(){} - Ordering guarantees

!

!

Journal

var state = S0 !

def processorId = “a” !

C3

E1

E2

Page 44: Resilient Applications with Akka Persistence - Scaladays 2014

persistAsync(){} - Ordering guarantees

var state = S0 !

def processorId = “a” !

C3

E1

Akka  Persistence  ScalaDays

!

!

Journal

E1

E2

Page 45: Resilient Applications with Akka Persistence - Scaladays 2014

persistAsync(){} - Ordering guarantees

E1

var state = S1 !

def processorId = “a” !

E2

E1

E2

!

!

JournalAkka  Persistence  ScalaDays

E2

E3E1

Page 46: Resilient Applications with Akka Persistence - Scaladays 2014

persistAsync(){} - Ordering guarantees

E1

var state = S2 !

def processorId = “a” !

E2

E1 E2

deferred handlers triggered

M1M2

!

!

JournalAkka  Persistence  ScalaDays

E2

E3E1

Page 47: Resilient Applications with Akka Persistence - Scaladays 2014

Recovery

Akka  Persistence  ScalaDays

Page 48: Resilient Applications with Akka Persistence - Scaladays 2014

Eventsourced, recovery

/** MUST NOT SIDE-EFFECT! */!def receiveRecover = {! case replayedEvent: Event => ! state = updateState(replayedEvent)!}

re-using updateState, as seen in receiveCommand

Akka  Persistence  ScalaDays

Page 49: Resilient Applications with Akka Persistence - Scaladays 2014

Views

Akka  Persistence  ScalaDays

Page 50: Resilient Applications with Akka Persistence - Scaladays 2014

Journal (DB)

!

!

!

Views

!Processor

!def processorId = “a”

!

polling

Akka  Persistence  ScalaDays

!View

!def processorId = “a”

!!!

Page 51: Resilient Applications with Akka Persistence - Scaladays 2014

Journal (DB)

!

!

!

Views

!Processor

!def processorId = “a”

!

polling

!View

!def processorId = “a”

!!!

polling

different ActorPath, same processorId

Akka  Persistence  ScalaDays

!View

!def processorId = “a”

!!!

Page 52: Resilient Applications with Akka Persistence - Scaladays 2014

View

class DoublingCounterProcessor extends View {! var state = 0! override val processorId = "counter"!! def receive = {! case Persistent(payload, seqNr) =>! // “state += 2 * payload” !! }!}

subject to change!

Akka  Persistence  ScalaDays

Page 53: Resilient Applications with Akka Persistence - Scaladays 2014

Views, as Reactive Streams

Akka  Persistence  ScalaDays

Page 54: Resilient Applications with Akka Persistence - Scaladays 2014

View, as ReactiveStream

// Imports ...!!import org.reactivestreams.api.Producer!!import akka.stream._!import akka.stream.scaladsl.Flow!!import akka.persistence._!import akka.persistence.stream._!

val materializer = FlowMaterializer(MaterializerSettings())!

pull request by krasserm

early preview

Akka  Persistence  ScalaDays

Page 55: Resilient Applications with Akka Persistence - Scaladays 2014

View, as ReactiveStream

// 1 producer and 2 consumers:!val p1: Producer[Persistent] = PersistentFlow.! fromProcessor(“processor-1").! toProducer(materializer)!!Flow(p1).! foreach(p => println(s"consumer-1: ${p.payload}”)).! consume(materializer)!!Flow(p1).! foreach(p => println(s"consumer-2: ${p.payload}”)).! consume(materializer)

pull request by krasserm

early preview

Akka  Persistence  ScalaDays

Page 56: Resilient Applications with Akka Persistence - Scaladays 2014

View, as ReactiveStream

// 2 producers (merged) and 1 consumer:!val p2: Producer[Persistent] = PersistentFlow.! fromProcessor(“processor-2").! toProducer(materializer)!val p3: Producer[Persistent] = PersistentFlow.! fromProcessor(“processor-3").! toProducer(materializer)!!Flow(p2).merge(p3). // triggers on “either”! foreach { p => println(s"consumer-3: ${p.payload}") }.! consume(materializer)!

pull request by krasserm

early preview

Akka  Persistence  ScalaDays

Page 57: Resilient Applications with Akka Persistence - Scaladays 2014

Akka  Persistence  ScalaDays  2014

Usage in a Cluster

• distributed journal (http://akka.io/community/)

• Cassandra

• DynamoDB

• HBase

• MongoDB

• shared LevelDB journal for testing

• single writer

• cluster singleton

• cluster sharding

Page 58: Resilient Applications with Akka Persistence - Scaladays 2014

Akka  Persistence  ScalaDays  2014

Cluster Singleton

AB

C D

Page 59: Resilient Applications with Akka Persistence - Scaladays 2014

Akka  Persistence  ScalaDays  2014

Cluster Singleton

AB

C

D

role: backend-1 role: backend-1

role: backend-2 role: backend-2

Page 60: Resilient Applications with Akka Persistence - Scaladays 2014

Akka  Persistence  ScalaDays  2014

Cluster Sharding

A B

C D

Page 61: Resilient Applications with Akka Persistence - Scaladays 2014

Akka  Persistence  ScalaDays  2014

Cluster Sharding

sender

id:17

region node-­‐1

coordinator

region node-­‐2

region node-­‐3

GetShardHome:17

id:17 ShardHome:17  -­‐>  node2

17  -­‐>  node2

Page 62: Resilient Applications with Akka Persistence - Scaladays 2014

Akka  Persistence  ScalaDays  2014

Cluster Sharding

sender region node-­‐1

coordinator

region node-­‐2

region node-­‐3

id:17

id:17GetShardHome:17

ShardHome:17  -­‐>  node2

id:17

17  -­‐>  node2

17  -­‐>  node2

Page 63: Resilient Applications with Akka Persistence - Scaladays 2014

Akka  Persistence  ScalaDays  2014

Cluster Sharding

17

sender region node-­‐1

coordinator

region node-­‐2

region node-­‐3

id:17

id:17

17  -­‐>  node2

17  -­‐>  node2

17  -­‐>  node2

Page 64: Resilient Applications with Akka Persistence - Scaladays 2014

Akka  Persistence  ScalaDays  2014

Cluster Sharding

17

sender region node-­‐1

coordinator

region node-­‐2

region node-­‐3

17  -­‐>  node2

17  -­‐>  node2

17  -­‐>  node2

id:17

Page 65: Resilient Applications with Akka Persistence - Scaladays 2014

Akka  Persistence  ScalaDays  2014

Cluster Sharding

17

sender region node-­‐1

coordinator

region node-­‐2

region node-­‐3

17  -­‐>  node2

17  -­‐>  node2

17  -­‐>  node2

id:17

Page 66: Resilient Applications with Akka Persistence - Scaladays 2014

Cluster Sharding

val idExtractor: ShardRegion.IdExtractor = { case cmd: Command => (cmd.postId, cmd) } ! val shardResolver: ShardRegion.ShardResolver = msg => msg match { case cmd: Command => (math.abs(cmd.postId.hashCode) % 100).toString }

ClusterSharding(system).start( typeName = BlogPost.shardName, entryProps = Some(BlogPost.props()), idExtractor = BlogPost.idExtractor, shardResolver = BlogPost.shardResolver)

val blogPostRegion: ActorRef = ClusterSharding(context.system).shardRegion(BlogPost.shardName) !val postId = UUID.randomUUID().toString blogPostRegion ! BlogPost.AddPost(postId, author, title)

Page 67: Resilient Applications with Akka Persistence - Scaladays 2014

Akka  Persistence  ScalaDays  2014

Lost messages

sender destination

$

Page 68: Resilient Applications with Akka Persistence - Scaladays 2014

Akka  Persistence  ScalaDays  2014

At-least-once delivery - duplicates

sender destination

$

ok

$

$$

ok

Re-­‐send

Page 69: Resilient Applications with Akka Persistence - Scaladays 2014

Akka  Persistence  ScalaDays  2014

M2

At-least-once delivery - unordered

sender destination

M1

ok  1 ok  2

M2

ok  3

M3

M1M3M2

Re-­‐send

Page 70: Resilient Applications with Akka Persistence - Scaladays 2014

Akka  Persistence  ScalaDays  2014

M2

At-least-once delivery - crash

sender destination

M1

ok  1 ok  2

M2

ok  3

M3

1. Sent  M1  2. Sent  M2  3. Sent  M3  

M3

5.  M2  Confirmed  6.  M3  Confirmed

4.  M1  Confirmed

senderM1M2

M3

Page 71: Resilient Applications with Akka Persistence - Scaladays 2014

PersistentActor with AtLeastOnceDelivery

case class Msg(deliveryId: Long, s: String) case class Confirm(deliveryId: Long) sealed trait Evt case class MsgSent(s: String) extends Evt case class MsgConfirmed(deliveryId: Long) extends Evt

class Sender(destination: ActorPath) extends PersistentActor with AtLeastOnceDelivery { ! def receiveCommand: Receive = { case s: String => persist(MsgSent(s))(updateState) case Confirm(deliveryId) => persist(MsgConfirmed(deliveryId))(updateState) } ! def receiveRecover: Receive = { case evt: Evt => updateState(evt) } ! def updateState(evt: Evt): Unit = evt match { case MsgSent(s) => deliver(destination, deliveryId => Msg(deliveryId, s)) ! case MsgConfirmed(deliveryId) => confirmDelivery(deliveryId) } }

Page 72: Resilient Applications with Akka Persistence - Scaladays 2014

Akka  Persistence  ScalaDays  2014

Next step

• Documentation • http://doc.akka.io/docs/akka/2.3.3/scala/persistence.html

• http://doc.akka.io/docs/akka/2.3.3/java/persistence.html

• http://doc.akka.io/docs/akka/2.3.3/contrib/cluster-sharding.html

• Typesafe Activator • https://typesafe.com/activator/template/akka-sample-persistence-scala

• https://typesafe.com/activator/template/akka-sample-persistence-java

• http://typesafe.com/activator/template/akka-cluster-sharding-scala

• Mailing list • http://groups.google.com/group/akka-user

• Migration guide from Eventsourced • http://doc.akka.io/docs/akka/2.3.3/project/migration-guide-eventsourced-2.3.x.html

Page 73: Resilient Applications with Akka Persistence - Scaladays 2014

©Typesafe 2014 – All Rights Reserved