On When Concurrency Matters

Sean T. Allen

www.seantallen.com

  • Distinguished Engineer @ Antithesis
  • Member of the Pony core team
  • High Plains Drifter
  • Reviewer #2

On When Concurrency Matters:
Behaviour-Oriented Concurrency

A talk about
concurrency

Some History

An ACID DB
in Pony

The Actor Model

Actors
pass messages

Actors
protect resources

Actors
own their memory

Actors
combine behaviour
and memory

No
globals

No
locks

No
shared state

Actors allow
sequential reasoning

Actors can
scale horizontally

Actors are
deadlock-free

Actors provide
data race freedom

Coordination Pain

ACID with Actors

Each table is an actor

Users
Orders
Stock

Each table is an actor

Users
Orders
Stock

Each table is an actor

Users
Orders
Stock

Each table is an actor

Users
Orders
Stock

Each table is an actor

Users
Orders
Stock

A simple query

Client
Users

A simple query

Client
Users

A simple query

Client
Users

A simple query

Client
SELECT * FROM users WHERE …
[ … ]
Users

A naive JOIN

Client Users Orders

A naive JOIN

Client Users Orders SELECT * FROM users WHERE id=42

A naive JOIN

Client Users Orders SELECT * FROM orders WHERE user_id=42

A naive JOIN

Client Users Orders [ … orders … ]

A naive JOIN

Client Users Orders { id: 42, name: … }

A naive JOIN

Client Users Orders JOIN locally

User 42 is Alice

Alice has 2 orders

No other writers

But the JOIN is not atomic

Our Client U read Users O read Orders

But the JOIN is not atomic

Our Client U read Users O read Orders

JOIN result:
Alice + 2 orders

A writer
adds an order

But the JOIN is not atomic

Our Client U read Users O read Orders A writer + INSERT order

But the JOIN is not atomic

Our Client U read Users O read Orders A writer + INSERT order

But the JOIN is not atomic

Our Client U read Users O read Orders A writer + INSERT order

JOIN result:
Alice + 3 orders

A writer
deletes an order

But the JOIN is not atomic

Our Client U read Users O read Orders A writer DELETE order

But the JOIN is not atomic

Our Client U read Users O read Orders A writer DELETE order

But the JOIN is not atomic

Our Client U read Users O read Orders A writer DELETE order

JOIN result:
Alice + 1 order

Same JOIN.
Three different answers.

It's a race

Locks

Two-phase
locking

Two-phase locking

Client Users Orders

Two-phase locking — growing

Client Users Orders acquire LOCK(txn=42)

Two-phase locking — growing

Client Users Orders acquire LOCK(txn=42) LOCK(txn=42)

Two-phase locking — growing

Client Users Orders acquire LOCK(txn=42) LOCK(txn=42) GRANTED

Two-phase locking — growing

Client Users Orders acquire LOCK(txn=42) LOCK(txn=42) GRANTED GRANTED

Two-phase locking — growing

Client Users Orders acquire LOCK(txn=42) LOCK(txn=42) GRANTED GRANTED read READ(SELECT…)

Two-phase locking — growing

Client Users Orders acquire LOCK(txn=42) LOCK(txn=42) GRANTED GRANTED read READ(SELECT…) READ(SELECT…)

Two-phase locking — growing

Client Users Orders acquire LOCK(txn=42) LOCK(txn=42) GRANTED GRANTED read READ(SELECT…) READ(SELECT…) [ … ]

Two-phase locking — growing

Client Users Orders acquire LOCK(txn=42) LOCK(txn=42) GRANTED GRANTED read READ(SELECT…) READ(SELECT…) [ … ] [ … ]

Two-phase locking — shrinking

Client Users Orders release RELEASE(txn=42)

Two-phase locking — shrinking

Client Users Orders release RELEASE(txn=42) RELEASE(txn=42)

Two-phase locking — shrinking

Client Users Orders release RELEASE(txn=42) RELEASE(txn=42) RELEASED

Two-phase locking — shrinking

Client Users Orders release RELEASE(txn=42) RELEASE(txn=42) RELEASED RELEASED

12 messages
for one JOIN.

Behaviour-Oriented Concurrency

What if a JOIN could look like this?

when (users, orders) {
  let u = users.find(42)
  let o = orders.where(u.id)
  return JOIN(u, o)
}

Break an actor
into its parts

Regions
isolate memory

Cowns
rent regions

Behaviours
acquire cowns

The behaviour

when (users, orders) {
  let u = users.find(42)
  let o = orders.where(u.id)
  return JOIN(u, o)
}

The cowns

when (users, orders) {
  let u = users.find(42)
  let o = orders.where(u.id)
  return JOIN(u, o)
}

Atomic across cowns

when (users, orders) {
  let u = users.find(42)
  let o = orders.where(u.id)
  return JOIN(u, o)
}

Cowns
receive messages

Cowns
protect resources

Cowns
rent their memory

No
globals

No
locks

No
shared state

Behaviours allow
sequential reasoning

Behaviours can
scale horizontally

Behaviours are
deadlock-free

Behaviours provide
data race freedom

Behaviour-Oriented Concurrency:Actors with benefits