How we Scale using Play, Akka, and

wakecabbagepatchSoftware and s/w Development

Nov 18, 2013 (3 years and 9 months ago)

86 views

How
we Scale using Play
,
Akka
, and
Scala


Who are we?

Ryan Knight

Consultant

Brian Pugh

VP of Engineering

Lucidchart
: Online diagramming solution

Lucidchart’s

Story

By the end of
2011…

Throughout
2011, grew
the team and
enhanced the
product


Substantial growth
throughout the year

First real
release late
summer of
2010


Gained Initial traction

Started with
an idea in
2008


Nights and weekends
project


Lucidchart

was in a precarious situation


Threat #1: Scaling


Webservers
scaled horizontally but
required
a
large
number of
instances


DB
scaled
vertically
which
b
ecomes prohibitively expensive

Threat
#
2: Performance

Minimum and average
response times
too
high

Threat #3: Availability

Monolithic app: Any
functional area of the
application could take down
the entire site


Significant
change was
needed!

Identified 8 architectural goals


1. Service
-
Oriented Architecture

2. Little, if any, server side state

3. All data
sharded

4. Persistence
layer that
allowed

us
to control queries


5. Great library support


6. Good runtime monitoring

7. Efficient development

environment

8. Good support for parallel
processing

Is there an all
-
in
-
one solution?


Service
-
Oriented Architecture


Little, if any, server side state


All data sharded


Persistence layer that allowed us to control queries


Great library support


Good runtime monitoring


Efficient development environment


Good support for parallel processing

Evaluated
Various Languages
and
Frameworks

And the winner is…

What is
Scala
?



Agile, with lightweight
syntax

Safe and
performant
, with strong static typing

Object
-
oriented

Functional

Why
Scala
?

Fully interoperable with Java

Immutable data structures

Strong list processing
-

i.e. map, reduce and for
comprehensions

Pattern Matching
-

Java switch on steroids

What is Play?

High Velocity Web Framework

Developer friendly


Continuous refresh


Readable errors

Type Safety
-

fully compiled

Event driven / real time enabled

Play Features

Runs in its own container on the JVM

Websockets and Comet

RESTful

Friendly URLs for SEO and humans

Stateless for predictable horizontal scalability

What is
Akka
?

Simple concurrency

Error handling and self
-
healing

Elastic and decentralized

Adaptive load balancing

Akka

Actors

Alternative to shared mutable state for concurrency

Concurrent, lightweight process

Communicates with other actors through asynchronous
messages

An actor is protected from the rest of the system

Actors: Distributable
by
design

Actors are location
-
transparent & distributable

Scale UP and OUT for free

P
erfect

fabric for the cloud

Failure

Recovery

Supervisor

hierarchies with “let
-
it
-
crash”
semantics

Parent can resume, restart, or terminate child

Error
-
prone tasks are delegated to child Actors

Typesafe

Activator

How
does the
Typesafe

platform
measure up to
Lucidchart’s

goals?

Support for REST with XML and JSON

1. Service
-
Oriented
Architecture



Not particularly better than others, but at least as good



Major changes here would be the same, no matter which
language and framework was used


Play fully embraces this philosophy

2. Little, if
any, server
side state



Small amount of state held in a cookie



Heavy use of caching, but if a cache miss occurs, still
functions


Libraries exist for common DBs that
support
sharding

3. All data
sharded



Built our own
sharding

library



Anorm

is flexible enough to allow
sharding



Scala

is a flexible language which allowed us to create
simple APIs for data
sharding

Play supports pluggable persistence
layers

4. Persistence
layer allows
control of
queries



Anorm

gives us full control of SQL



Industry is constantly creating “domain specific languages”
for database access


Our take is that SQL is exactly that;
just use it!


val

roleIds

= SQL
("SELECT
role_id

FROM
users_roles

WHERE
user_id
={
user_id
}")


.on("
user_id
"
-
> id)


.as(get[Long]("
role_id
")
*)

Full Java ecosystem of libraries

5. Great
library
support



Many Java libraries have lightweight
Scala

wrappers

It’s the JVM: Full Java ecosystem of
monitoring tools

6. Good
runtime
monitoring



Heap dumps, thread dumps,
VisualVM
, JMX,
etc



New Relic


Play auto
-
reloading

7. Efficient
development
environment



Good, but could improve on compile times

Threads, Actors, Futures

8. Support
for parallel
processing



Love all the options!

Benefits: Concise code


Case classes


Functional

Two
examples

Immutable Person Class in Java

public class Person {


private final String name;


private final
int

iq
;



public Person(String name,
int

iq
) {


this.name = name;


this.iq =
iq
;


}



public
boolean

equals(Object o) {


if (this == o) return true;


if (o == null ||
getClass
() !=
o.getClass
()) return false;


Person
person

= (Person) o;


if (
iq

!= person.iq) return false;


if (name != null ? !
name.equals
(person.name) : person.name != null) return false;


return true;


}



public
int

hashCode
() {


int

result = name != null ?
name.hashCode
() : 0;


result = 31 * result +
iq
;


return result;


}



public String
getName
() {


return name;


}



public
int

getIq
() {


return
iq
;


}



public String
toString
() {


return "Person{" + "name='" + name + '
\
'' + ",
iq
=" +
iq

+'}';


}

}

Immutable Person Class in
Scala

case class Person(name: String,
iq
:
Int
)

Functions as first class citizens


Filter users based on subscription rules


71
lines of PHP code
vs.
10 lines
below


levelPrefix.foreach

{ prefix =>


val

validLevelIds

=
allLevels.filter
(



_.
name.startsWith
(prefix)



).
filter(
levelTermAnnual.isEmpty

|| _.annual ==
levelTermAnnual.get
).map(_.id)



allIds

=
allIds.filter

{ case
UserModel.UserIdData
(id,
account_id
, email) =>


if(prefix
== "free")


!
subscriptions.contains
(
account_id
)



else


subscriptions.get
(
account_id
).map(s =>
validLevelIds.contains
(
s.level_id
)).
getOrElse
(false)


}

}

Does a string have an upper case character

boolean

hasUpperCase

= false

for (
int

i

= 0;
i
<
name.length
();
i
++) {


if (
Character.isUpperCase
(
name.charAt
(
i
))) {



hasUpperCase

= true;



break;


}

}


Does a string have an upper case character

val

hasUpperCase

=
name.exists
(_.
isUpper
)

Benefits: Parallel Processing


Functional style encourages
immutability



Actors and Futures provide a simpler mental model for
concurrency


Using significant
concurrency; haven’t
had significant bugs
related to
it


Not the norm

Benefits: Parallel Processing

Example:
Processing
pages

//Send a message with each page

val

pageFutures

=
pages.map
{ page =>


ask(
pageRouter
, new
PageGeneratorMessage
(page)).
mapTo
[Page]

}

//get the
results

val

pdfPages

=
Future.sequence
(
pageFutures
)
.map
{ future =>
future.getPdf

}




//
create actor

class
PageGenerator

extends Actor {


def

receive = {


case m:
PageGeneratorMessage

=> sender !
generatePage
(
m.getPage
)


case _ =>
Logger.error
("Unknown message " +
m.toString
)


}




}

Benefits:
Type safe (with great inference)


A class of errors
is
guaranteed not to
exist



More confident
refactoring



Coming into a large codebase, types help
with readability of code
base

Benefits: Performance

Image Generation
service




Average
response time decreased by 53
%




Median
response time decreased by 37
%


Benefits: Scalability


Services can scale
independently



Separate
auto
-
scaling
groups for each
service

Benefits:
Availability


Services fail
independently



Image generation service


Overall failure rate decreased by 85%



Faster diagnosis of production issues


Single component
per
server

Benefits:
Continuous integration and
Deployment


Simple integration with Jenkins



Play “stage” to create a distribution



Package the “staged” output into a
Debian

package


Managed at runtime using
Opscode

Chef

Drawbacks:
Longer than expected


Attempting a rewrite, not just a port



Tend to underestimate time to port
from PHP to a
Scala
/Play service

Drawbacks: More moving parts


Many different services to
manage



Each service is simpler, but lots of them

Drawbacks: Training


PHP and Java developers are
productive from Day
1



With
Scala
, ramp
-
up time is around
1

month


Principles of Reactive
Programming


Functional Programming Principles in
Scala

Adoption


New Programming
Model


It’s OK
to use
Scala

as a better Java and
slowly transition


Leverage
existing
samples


Look
at templates in Activator


Understand how Java and
Scala

interact


Scala

tools to convert collections
to/from
Java.


Adoption


Organizations are
resistant
to
change


Start
by building internal tools and
test suites


Community p
articipation


User
groups, open source projects, etc.
flatten
the learning curve


Don’t
reinvent
the
wheel


Leverage
existing
libraries
on the JVM


Summary


Each language/framework has
strengths and
weaknesses



Lucidchart’s

team has been pleased with
Scala

and
Play

Questions?

http://www.lucidchart.com/jobs

http://
typesafe.com