The Actor Model

I'm sorry that I long ago coined the term "objects" for this topic because it gets many people to focus on the lesser idea.
The big idea is "messaging"
[..]
The key in making great and growable systems is much more to design how its modules communicate rather than what their internal properties and behaviors should be.
[..]
-- Alan Kay

http://wiki.c2.com/?AlanKayOnMessaging

https://computinged.wordpress.com/2010/09/11/moti-asks-objects-never-well-hardly-ever/

Operating Systems

  • Processes are the fundamental unit of computation
  • Processes have independent state
  • One process crashing should not impact another process
  • Processes can be scheduled independently
  • Processes communicate over pipes, queues, shared memory

Microservice Architecture

  • Services are the fundamental unit of computation
  • Services have independent state
  • One service crashing should not impact another
  • Services can be scheduled (and scaled) independently
  • Services talk over a network, sometimes through TCP or Queues, no shared state or memory

Object Oriented Programming

  • Objects are the fundamental unit of computation
  • Objects can share state with other objects, expose their state (im)mutably to other objects
  • One objects failure can corrupt or impact another object
  • Objects do not lend themselves to independent scheduling of execution
  • Objects communicate by sharing memory
foo = Foo()
bar = Bar()

try:
    bar.do_thing(foo.get_inner_state())
except:
    # What state is 'foo' in?

Actor Oriented Programming

  • Actors are the fundamental unit of computation
  • Actors can not share state with each other
  • Actors isolate failure
  • Actors can be scheduled individually
  • Actors communicate via queues

An Actor Can

  • send (0 or more) messages to themself or other actors
  • create new actors
  • change its own internal state
actor Foo
  var some_state: U64 = 0

  be update(value: U64) =>
    some_state = some_state + value

    let new_actor = Foo.create()
    new_actor.update(some_state)

hey sup

nm u

nm

Time

Concurrency

  • Within an Actor you write code sequentially
  • Actor communication is asynchronous
  • No locks, no shared memory, concurrency is just how you build your program
  • Impossible to have data races because actors can not share memory

hey sup

nm u

nm

Time

hey :)

hi :)

Reliability

  • Actors isolate failures
  • Actors can easily model redundancy - create a pool of actors, route data to them (just like an ELB)
  • Actors mimic real systems - sending a message to a computer that crashed shouldn't crash the sender, same idea with actors
  • Used at Ericsson to achieve between 5 and 9 9's of availability with Erlang

hey sup

....

k nvm

Time

hey sup

Send Message

Await Response

Give up

Handle it

hey sup

hey sup

ERROR

hey sup

ERROR

...

Circuit Breakers

Time

Route message through CB

Time out

Return error

Return error immediately

Scalability

  • Actors are cheap. +240 bytes of overhead in Pony. Very fast to spin up/ down, unlike OS threads.
  • Need to scale? Add more actors. Need to scale more? Add more computers! You can treat computers as actors. Talking to an actor on your local system is similar (or identical) to talking to an actor on another system.
  • Code written for 10 users is virtually identical to code written for 10_000_000 users
  • Used by Whatsapp to scale to 1 billion users on the Erlang VM

Time

Do this 1 thing

Do this 1 thing

k done

Scaling To One User

Receive task

For every task, create new actor, assign it the task

Send completed tasks back

Do these 1000 thing

k done

Scaling To 1000 Users

Do this 1 thing

Time

Receive task

For every task, create new actor, assign it the task

Send completed tasks back

Pony

  • Compiled, static type safety similar to Rust
  • Capability safety (sandboxing)
  • Exception safety (no unchecked exceptions), any function that can fail is modeled as a partial function
  • Actors can not fail/ die

Erlang

  • Dynamic type safety
  • Focus on fail fast, supervisor tree structures
  • Despite lack of optimizing power still capable of scaling well
  • Actors can fail/ die
actor Player
  let name: String
  let env: Env
  
  new create(name': String, env': Env) =>
    name = name'
    env = env'

  be ping(opponent: Player) =>
    env.out.print("Sending ping from " + this.name)
    opponent.ping(this)

actor Main
  new create(env: Env) =>
    let player1 = Player.create("player1", env)
    let player2 = Player.create("player2", env)
    let player3 = Player.create("player3", env)
    
    player1.ping(player2)
    player1.ping(player3)
Sending ping from player1
Sending ping from player3
Sending ping from player2
Sending ping from player1
Sending ping from player1
Sending ping from player3
Sending ping from player2
Sending ping from player1
Sending ping from player3
Sending ping from player1
Sending ping from player1
Sending ping from player2
Sending ping from player1

Supervisor

  • An actor whose only job is to watch other actors (or supervisors)
  • Contains a policy for how to restart failed actors

Worker

  • An actor who performs some arbitrary task
  • No significant error handling exists in workers
  • Problem? Let the supervisor figure it out.

Supervisor

 

 

Worker

r.i.p

i got this

Asset Authentication App

  • Provide REST API to query for Auth information
  • Turn Attributed Auth Docs into Behaviors
  • Store Auth information into Cassandra
  • Generate Log Content

Input Queue

Output Queue

C*

REST API

Persistors

Router

Behavior Generator

Log Converter

C*

Output

Queue

What do we want?

  • Modular, extendable features with clear boundaries
  • Primary features should always work, never impact each other
  • Easily scale if one feature starts to get heavier load

Why Not Microservices?

  • Increased latency
  • Ownership of code would not change
  • Still mostly single purpose, just with multiple 'features'
  • Just doesn't seem worth it for something that's already quite small and already fits the microservice paradigm decently

C*

REST API

Input Queue

Output Queue

Persistors

Router

Behavior Generator

Log Converter

C*

  • REST API
  • Behavior Generation
  • Persist Auth Info
  • Log Content

Router

C*

Persistors

Persistors

Router

Behavior Generator

C*

C*

actor Router
  var generators = BehaviorGeneratorPool

  be route_message(msg: Datatype) =>
    for thing_to_persist in msg.persist_stuff() do
        let persistor = Persistor.create()
        persistor.persist(thing_to_persist)
    end

    generators.gen(msg)
  

Persistors

Router

Behavior Generator

C*

actor Persistor
  let connection: CassandraConnection

  new create() =>
    connection = CassandraConnection.create("some cassandra url idk")

  be persist(data: SomeDatatype) =>
    # validation, transformation, whatever
    connection.store(data)

Persistors

Router

Behavior Generator

C*

Persistors

Router

Behavior Generator

C*

C*

Akka

public class SimpleActor extends UntypedActor {

    LoggingAdapter log = Logging.getLogger(getContext().system(), this);


    public SimpleActor() {
        log.info("SimpleActor constructor");
    }

    @Override
    public void onReceive(Object msg) throws Exception {

        log.info("Received Command: " + msg);

        if (msg instanceof Command) {
            final String data = ((Command) msg).getData();
            final Event event = new Event(data, UUID.randomUUID().toString());

            // emmit an event somewhere...

        } else if (msg.equals("echo")) {
            log.info("ECHO!");
        }
    }
}

Companies Using Akka In Production

http://doc.akka.io/docs/akka/2.0.4/additional/companies-using-akka.html

We saw a perfect business reason for using Akka. It lets you concentrate on the business logic instead of the low level things. It’s easy to teach others and the business intent is clear just by reading the code. We didn’t chose Akka just for fun. It’s a business critical application that’s used in broadcasting.

Our Akka journey started as a prototyping project, but Akka has now become a crucial part of our system.

The Actor model has simplified a lot of concerns in the type of systems that we build and is now part of our reference architecture. With Akka we deliver systems that meet the most strict performance requirements of our clients in a near-realtime environment. We have found the Akka framework and it's support team invaluable.

Questions?

Persistors

Router

Behavior Generator

C*

C*

REST API

Input Queue

Output Queue

Persistors

Router

Behavior Generator

Log Converter

C*

  • REST API
  • Behavior Generation
  • Persist Auth Info
  • Log Content

C*

REST API

C*

Input Queue

Router

Log Converter

Persistors

  • Log Content

C*

REST API

Input Queue

Output Queue

Persistors

Router

Behavior Generator

Log Converter

C*

  • REST API
  • Behavior Generation
  • Persist Auth Info
  • Log Content