Scaling For (Almost) Free Elixir: It’s a Process ❦ Aaron Seigo aseigo@mykolab.com
“Big whirls have little whirls, That feed on their velocity; And little whirls have lesser whirls, And so on to viscosity.” ― Lewis Fry Richardson
To take full advantage of Elixir programs need to be created with concurrency in mind.
Processes are the key mechanic Processes are shared-nothing execution contexts which are preemptively scheduled across a set of OS threads They are intrinsically capable of being run concurrently
Combine them into larger ones Distribute them across compute resources Write small programs Combine them into larger ones Distribute them across compute resources Run the small programs concurrently Erlang was way ahead of the micro-service bandwagon!
“We do not have ONE web-server handling 2 millions sessions. We have 2 million webservers handling one session each.” – Joe Armstrong “Managing Two Million Webservers”
Concurrent ≆ Parallel
Anatomy of a process Message Box Working Memory Code Path
Message passing ties the small programs into bigger ones The Message Box Processes may receive arbitrary messages Incoming messages are stored in the process’ inbox Messages are filtered using pattern matching Message passing ties the small programs into bigger ones
Enforces separation, simplifies management Workset Memory Each process has its own block of RAM Garbage collection hides most of the details When a process is done, its memory is reaped 0(1) Enforces separation, simplifies management
Defines the mechanism and lifespan of the process Execution Stack Modules are the unit of code organization (topic) Processes are the unit of code execution (purpose) When the code halts or errors, the process exits Defines the mechanism and lifespan of the process
Processes Work Together Message Passing API Separation of Concerns Modularity Lifespan Tracking Reliability
Message Passing send receiver, message
Message Passing send receiver, message Posts the message to the receiver’s inbox and returns immediately. The message is also send’s return value.
Message Passing send receiver, message May be a pid, an atom that maps to a named process, a tuple {atom, node} to reach a named process on a specific node, or a Port that is connected to an external program.
Message Passing send receiver, message Can be anything. Literally, anything. Be curious and daring, but also careful with that power.
Message Passing f = fn -> "You just ran my code" end send self(), { :func, f } receive do { :func, g } -> g.() end
Message Passing receive do pattern → code end
Message Passing receive do pattern_a → code pattern_b → code end
Message Passing receive do pattern_a → code pattern_b → code _ → default mode end
Message Passing receive do pattern_a → code pattern_b → code _ → default mode after timeout → code end
Message Passing def loop do receive do pattern_a → do_a(); loop pattern_b → do_b(); loop _ → :unexpected_message after timeout → :timeout end
Message Passing def loop state do receive do pattern_a → state |> do_a |> loop pattern_b → state |> do_b |> loop _ → :unexpected_message after timeout → :timeout end
Message Passing
Separation of Concerns OOP : Object :: Elixir : Process Thanks to the shared-nothing* design processes are isolated from each other, making them perfectly suited to representing instance-specific data and actions. * there are exceptions, but they are well-hidden details
Elixir’s Big Brother Plan: linking and monitoring Lifespan Tracking Elixir’s Big Brother Plan: linking and monitoring Linking connects one process to another, so if the process exits abnormally the linked process will also crash. Monitoring, via the :trap_exit process flag, allows handling of those events instead of crashing.
Supervisor.start_link(children, options) Lifespan Tracking Supervisor.start_link(children, options) Supervisors are processes that watch over other processes, called their children, and automatically take action on failure. Since supervisors are processes, supervisors may supervise other supervisors. This is called a supervision tree.
Supervisor.start_link(children, options) Lifespan Tracking Supervisor.start_link(children, options) This allows systems to be robust and predictable in cases of failure. Which in turn means failure is something that can be embraced and used as a tool.
Supervisor.start_link(children, strategy) Lifespan Tracking Supervisor.start_link(children, strategy) Children are a list of function specifications that the supervisor will start and then monitor. {child_id, {module, atom, [term]}, restart, shutdown, worker, modules}
Supervisor.start_link(children, options) Lifespan Tracking Supervisor.start_link(children, options) Options are used to define: name start/restart limits (no. of attempts, max time to wait) management strategy
Supervisor.start_link(children, strategy: strategy) Lifespan Tracking Supervisor.start_link(children, strategy: strategy) :one_for_one: → just restart failures :one_for_all → restart all children on any failure :rest_for_one → restart all started after the failing process :simple_one_for_one → one child template, spawn on demand
Supervisor.start_link(children, options) Lifespan Tracking Supervisor.start_link(children, options) Moral of our story: just use Supervisor, don’t try and roll your own. There is nothing to add, only bugs to write.
Challenges and Caveats Throughput traded for latency Non-deterministic order of process execution Reliability and message passing in an imperfect world Bottlenecks Shared resources Shared state
live demo → seeing it in action, or tempting fate?
live demo → 1. Processes
live demo → 2. Messages
live demo → 3. GenServer
live demo → 4. Supervisor
live demo → 5. UnreliableTask
live demo → 6. Cluster
Breaking large problems into little ones Flavors of process: State Tasks Workers Resources Supervisors Elixir programs are a collection of flavored processes
Fun stuff to play with Pipelines → https://github.com/elixir-lang/gen_stage Parallel Streams → https://github.com/elixir-lang/flow PubSub → https://github.com/phoenixframework/phoenix_pubsub Easy Services → https://github.com/pragdave/jeeves Pools → https://github.com/devinus/poolboy Circuit breaking → https://github.com/jlouis/fuse Rate limiting → https://github.com/grempe/ex_rated Tracing → https://github.com/liveforeverx/exrun
Q&A go forth and make awesome things