Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Iteratees for building a distributed cash-flow system

Iteratees for building a distributed cash-flow system

Talk given @ ScalaIO. How to combine, Scala Streams, Iteratees, Akka actors and Akka Cluster to build a distributed cash-flow computation system.

Xavier Bucchiotty

October 24, 2013
Tweet

More Decks by Xavier Bucchiotty

Other Decks in Programming

Transcript

  1. initial = 1000 € duration = 5 years fixed interets

    rate = 5% Date Amort Interests Outstanding 2013-01-01 200 € 50 € 800 € 2014-01-01 200 € 40 € 600 € 2015-01-01 200 € 30 € 400 € 2016-01-01 200 € 20 € 200 € 2017-01-01 200 € 10 € 0 € 1000 €
  2. initial = 1000 € duration = 5 years fixed interets

    rate = 5% Date Amort Interests Outstanding 2013-01-01 200 € 50 € 800 € 2014-01-01 200 € 40 € 600 € 2015-01-01 200 € 30 € 400 € 2016-01-01 200 € 20 € 200 € 2017-01-01 200 € 10 € 0 € 1000 € date = last date + (1 year)
  3. initial = 1000 € duration = 5 years fixed interets

    rate = 5% Date Amort Interests Outstanding 2013-01-01 200 € 50 € 800 € 2014-01-01 200 € 40 € 600 € 2015-01-01 200 € 30 € 400 € 2016-01-01 200 € 20 € 200 € 2017-01-01 200 € 10 € 0 € 1000 € amort = initial / duration
  4. initial = 1000 € duration = 5 years fixed interets

    rate = 5% Date Amort Interests Outstanding 2013-01-01 200 € 50 € 800 € 2014-01-01 200 € 40 € 600 € 2015-01-01 200 € 30 € 400 € 2016-01-01 200 € 20 € 200 € 2017-01-01 200 € 10 € 0 € 1000 € outstanding = last oustanding - amort
  5. initial = 1000 € duration = 5 years fixed interets

    rate = 5% Date Amort Interests Outstanding 2013-01-01 200 € 50 € 800 € 2014-01-01 200 € 40 € 600 € 2015-01-01 200 € 30 € 400 € 2016-01-01 200 € 20 € 200 € 2017-01-01 200 € 10 € 0 € 1000 € interests = last outstanding * rate
  6. val f = (last: Row) => new Row { def

    date = last.date + (1 year) def amortization = last amortization def outstanding = last.outstanding - amortization def interests = last.outstanding * fixedRate }
  7. Date Amort Interests Outstanding 2013-01-01 200 € 50 € 800

    € 2014-01-01 200 € 40 € 600 € 2015-01-01 200 € 30 € 400 € 2016-01-01 200 € 20 € 200 € 2017-01-01 200 € 10 € 0 €
  8. Date Amort Interests Outstanding 2013-01-01 200 € 50 € 800

    € 2014-01-01 200 € 40 € 600 € 2015-01-01 200 € 30 € 400 € 2016-01-01 200 € 20 € 200 € 2017-01-01 200 € 10 € 0 € first f(first) f(f(first))
  9. case class Loan( ... ) { def first: Row def

    f:(Row => Row) def rows = Stream.iterate(first)(f) .take(duration) }
  10. 3450 € Total Date Amort Interests Total paid 2013-01-01 200

    € 50 € 250 € 2014-01-01 200 € 40 € 240 € 2015-01-01 200 € 30 € 230 € 2016-01-01 200 € 20 € 220 € 2017-01-01 200 € 10 € 210 € 2013-01-01 200 € 50 € 250 € 2014-01-01 200 € 40 € 240 € 2015-01-01 200 € 30 € 230 € 2016-01-01 200 € 20 € 220 € 2017-01-01 200 € 10 € 210 € 2013-01-01 200 € 50 € 250 € 2014-01-01 200 € 40 € 240 € 2015-01-01 200 € 30 € 230 € 2016-01-01 200 € 20 € 220 € 2017-01-01 200 € 10 € 210 € Loan 1 Loan 2 Loan 3
  11. // Produce rows val totalPaid = portfolio.rows // Transform rows

    to amount .map(row => row.interests + row.amortization) //Consume amount .foldLeft(0 EUR)(_ + _)
  12. // Produce rows val totalPaid = portfolio.rows // Transform rows

    to amount .map(row => row.interests + row.amortization) //Consume amount .foldLeft(0 EUR)(_ + _) type RowProducer = Iterable[Row] type RowTransformer[T] = (Row=>T) type AmountConsumer[T] = (Iterable[Amount]=>T)
  13. //Loan Stream.iterate(first)(f) take duration //Porfolio loans => loans flatMap (loan

    => loan.rows) RowProducer (Iterable[Row]) + on demand computation - sequential computation
  14. object RowTransformer { val totalPaid = (row: Row) => row.interests

    + row.amortization } + function composition - type limited to «map» RowTransformer (Row => T)
  15. object AmountConsumer { def sum = (rows: Iterable[Amount]) => rows.foldLeft(Amount(0,

    EUR))(_ + _) } AmountConsumer (Iterable[Amount] => T) + function composition - synchronism
  16. case class Loan(initial: Amount, duration: Int, rowIt: RowIt) { def

    rows(implicit ctx: ExecutionContext) = Stream.iterate(first)(f).take(duration) } Data producer Enumerator.enumerate( )
  17. case class Portfolio(loans: Seq[Loansan]) { def rows(implicit ctx: ExecutionContext) =

    } producers can be combined Enumerator.interleave(loans.map(_.rows))
  18. 2013-01-01 200 € 50 € 250 € 2014-01-01 200 €

    40 € 240 € 2015-01-01 200 € 30 € 230 € 2016-01-01 200 € 20 € 220 € 2017-01-01 200 € 10 € 210 € 2013-01-01 200 € 50 € 250 € 2014-01-01 200 € 40 € 240 € 2015-01-01 200 € 30 € 230 € 2016-01-01 200 € 20 € 220 € 2017-01-01 200 € 10 € 210 € Date Amort Interests Total paid 2013-01-01 200 € 50 € 250 € 2014-01-01 200 € 40 € 240 € 2015-01-01 200 € 30 € 230 € 2016-01-01 200 € 20 € 220 € 2017-01-01 200 € 10 € 210 € 3450 € Total
  19. object Step { case class Done[+A, E] (a: A, remaining:

    Input[E]) case class Cont[E, +A] (k: Input[E] => Iteratee[E, A]) case class Error[E] (msg: String, input: Input[E]) }
  20. Enumerator Iteratee def step = ... val count = 0

    Input El(...) Status Continue Iteratee def step = ... val count = 1 computes
  21. Iteratee def step = ... val count = 1 Iteratee

    def step = ... val count = 1 Enumerator Input EOF Status Done computes
  22. Iteratee def step = ... val count = 1 Enumerator

    Input El(...) Status Error Iteratee def step = ... val error = "Runtime Error" computes
  23. val last: RowConsumer[Option[Row]] = { def step(last: Option[Row]): K[Row,Option[Row]]= {

    case Input.Empty => Cont(step(last)) case Input.EOF => Done(last, Input.EOF) case Input.El(e) => Cont(step(Some(e))) } Cont(step(Option.empty[Row])) }
  24. type RowProducer = Iterable[Row] type RowProducer = Enumerator[Row] type AmountConsumer[T]

    = (Iterable[Amount]=>T) type RowTransformer[T] = (Row=>T) type RowTransformer[T] = Enumeratee[Row, T] type AmountConsumer[T] = Iteratee[Amount, T]
  25. // Produce rows val totalPaidComputation: Future[Amount] = portfolio.rows &> totalPaid

    |>>> sum // Blocking the thread to wait for the result val totalPaid = Await.result( totalPaidComputation, atMost = defaultTimeout) totalPaid should equal(3480 EUR)
  26. //Loan Stream.iterate(first)(f) take duration //Porfolio loans => loans flatMap (loan

    => loan.rows) RowProducer //Loan Enumerator.enumerate( Stream.iterate(first)(f).take(duration) ) //Porfolio Enumerator.interleave(loans.map(_.rows))
  27. val totalPaid = (row: Row) => row.interests + row.amortization RowTransformer

    val totalPaid = Enumeratee.map[Row](row => row.interests + row.amortization )
  28. RowTransformer + Function composition + map, filter, ... val totalPaid

    = Enumeratee.map[Row](row => row.interests + row.amortization )
  29. AmountConsumer def sum = rows => rows.foldLeft(Amount(0, EUR))(_ + _)

    def sum = Iteratee.fold[Amount, Amount] (Amount(0, EUR))(_ + _)
  30. Stream API Step 1 5000 loans 50 rows ~ 560

    ms Iteratees Step 2 5000 loans 50 rows ~ 3500 ms ?
  31. Stream API Step 1 5000 loans 50 rows ~ 560

    ms with pause ~ 144900 ms Iteratees Step 2 5000 loans 50 rows ~ 3500 ms with pause ~ 157285 ms ?
  32. Cost of using this implementation of iteratees is greater than

    gain of interleaving for such small operations
  33. //Portfolio val split = loans.map(_.stream) .grouped(loans.size / 4) split .map(rows

    => Enumerator.flatten( ) ) .foldLeft(Enumerator.empty[Row]) (Enumerator.interleave(_, _)) Future(rows.flatten) .map(Enumerator.enumerate(_)) Seq[Stream[Row]] Seq[Loan] Iterator[Seq[Stream[Row]]] Seq[Stream[Row]] Future[Seq[Row]] Future[Enumerator[Row]] Enumerator[Row] Enumerator[Row] Seq[Enumerator[Row]]
  34. Stream API Step 1 5000 loans 50 rows ~ 560

    ms with pause ~ 144900 ms Iteratees Step 2 5000 loans 50 rows ~ 4571 ms with pause ~ 39042 ms
  35. class Backend extends Actor { def receive = { case

    Compute(loan) => sender.tell( msg = loan.stream.toList, sender = self) } } case class Compute(loan: Loan)
  36. case class Loan def rows(implicit calculator: ActorRef, ctx: ExecutionContext) =

    { val responseFuture = ask(calculator,Compute(this)) val rowsFuture = responseFuture .mapTo[List[Row]] rowsFuture.map(Enumerator.enumerate(_)) ) } }
  37. Supervision val simpleStrategy = OneForOneStrategy() { case _: AskTimeoutException =>

    Resume case _: RuntimeException => Escalate } system.actorOf(Props[Backend] ... .withSupervisorStrategy(simpleStrategy)), "calculator")
  38. 5000 loans 50 rows ~ 4571 ms with pause ~

    39042 ms Stream API Step 1 5000 loans 50 rows ~ 560 ms with pause ~ 144900 ms Iteratees Step 2 Akka actor Step 3 5000 loans 50 rows ~ 4271 ms with pause ~ 40882 ms
  39. Cluster Router ClusterRouterConfig Can create actors on different nodes of

    the cluster Role Local actors or not Control number of actors per node per system
  40. val calculator = system.actorOf(Props[Backend] .withRouter( RoundRobinRouter(nrOfInstances = 10) ) ,"calculator")

    } val calculator = system.actorOf(Props[Backend] .withRouter(ClusterRouterConfig( local = localRouter, settings = clusterSettings) ) , "calculator") }
  41. Router Routee 3 Routee 1 Actor System Routee 4 Routee

    3 Actor System Routee 6 Routee 5 Actor System Elasticity
  42. Router Routee 3 Routee 1 Actor System Routee 4 Routee

    3 Actor System Routee 6 Routee 5 Actor System Resilience
  43. Stream API Step 1 5000 loans 50 rows ~ 560

    ms with pause ~ 144900 ms Iteratees Step 2 5000 loans 50 rows ~ 4571 ms with pause ~ 39042 ms Akka actor Step 3 5000 loans 50 rows ~ 4271 ms with pause ~ 40882 ms Akka cluster Step 4 5000 loans 50 rows ~ 6213 ms with pause ~ 77957 ms 1 node / 2 actors
  44. Stream API Step 1 5000 loans 50 rows ~ 560

    ms with pause ~ 144900 ms Iteratees Step 2 5000 loans 50 rows ~ 4571 ms with pause ~ 39042 ms Akka actor Step 3 5000 loans 50 rows ~ 4271 ms with pause ~ 40882 ms Akka cluster Step 4 5000 loans 50 rows ~ 5547 ms with pause ~ 39695 ms 2 nodes / 4 actors
  45. Stream API Step 1 powerful library low memory performance when

    single threaded Iteratees Step 2 Akka actor Step 3 error management control on parallel execution via configuration Akka cluster Step 4 elasticity resilience monitoring elegant API enable asynchronism and parallelism
  46. Hot subject Recet blog post from «Mandubian» for Scalaz stream

    machines and iteratees [1] Recent presentation from «Heather Miller» for spores (distribuables closures) [2] Recent release of Scala 2.10.3 and performance optimization of Promise Release candidate of play-iteratee module with performance optimization Lots of stuff in the roadmap of Akka cluster 2.3.0