You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Erlang processes are a way to separate / isolate the runtime concerns of components of an application. Some examples of runtime concerns are parallel execution, fault tolerance, concurrent state management, independent scaling. Spawned processes can be linked to a parent, meaning that if the parent crashes, the child crashes and vice-versa.
Elixir has a few nice abstractions on top of processes for distinct purposes. These are inspired by Erlang behaviors and have been ported to Elixir as modules.
An abstraction around managing state in a way that is safe for concurrency. Erlang applications (and therefore our Phoenix web apps) will often use ETS as a way to store in-memory state. In a “rails-like” environment you would deploy a cluster of servers and share state with a service like Redis (i.e. cache). Erlang exposes the ability for processes to communicate across a distributed runtime (clustered nodes). This enables state to be stored hot in an Agent of a single node and be accessible by any process within the cluster. The main difference between caching in Redis and via Agents is in recovery. The reason to use a Redis store becomes the need for a persistence (and recovery) model for the store.
Spawned with a function for initializing the internal state. Think of this as the ability to define a “cache warming” function.
The agent exposes the ability to get or update values within state in a concurrent execution environment.
A server-like runtime that accepts messages from clients. It encapsulates 1+ aspect of an isolated process runtime and allows communication through message passing.
Spawned with the initial state of the server
Allows definition of callbacks to handle messages being passed to the server
call (handle_call): client is blocked waiting on a response
cast (handle_cast): client broadcasts a message to the server without receiving a response
send (handle_info): handle_call/handle_cast are for the public api of your server and handle_info is for other messages (i.e. monitoring). You should define a “catch-all” clause to avoid unexpected messages crashing the server.
An abstraction that defines a separation between components of a system. Applications have the ability to define a lifecycle that includes loading, starting, stopping (i.e. web app).
Releases are packaged and deployed with distillery. Releases can expose applications with different ports, or combine applications into a single port.
Can be supervised as a long running process (i.e. Phoenix app)
“Messages” can be sent between applications by calling module functions, enabling “library-like” separation without the runtime lifecycle.
Gotchas
Processes (like GenServer) can become a concurrency bottleneck. If multiple parallel client processes are calling a single GenServer, then the GenServer becomes a blocking component in the system.
Applications cannot have circular references within a single umbrella
Race conditions can be created within an Agent by the sequence P1 access, P2 modification, P1 modification based on previous access.
Agent crashes will drop the current state
What does this mean for Phoenix Applications?
Ranch (tcp socket pool) -> Cowboy (http server) -> Phoenix:
Provides concurrent requests. Each request is a single process. This means that multiple parallel requests calling a shared resource (i.e. a single GenServer or Application process) can create a performance bottleneck
Applications within an umbrella should separate runtime concerns or “peelable” bits of functionality
A common approach:
web app
admin app
business logic + ecto stuffs Additionally:
things you’d put in a dir under lib in rails
shared state that needs to be accessed/manipulated in a concurrent environment
things you may want to pull out into a separate clustered node but aren’t sure can be completely decoupled
"naive caching" should be accomplished by using an Agent (simple communications) or a GenServer (complex communications). What makes this a naive cache is the lack of a built in eviction strategy or expiration strategy.