The Soul of Erlang and Elixir • Sasa Juric • GOTO 2019

The Soul of Erlang and Elixir • Sasa Juric • GOTO 2019 — GOTO Conferences

GOTO Conferences

AI-assisted summary of The Soul of Erlang and Elixir • Sasa Juric • GOTO 2019 — original source. Check the original for full context.

Source published:

Key ideas

  • The true foundation uniting Erlang and Elixir is the BEAM virtual machine, which acts essentially as an operating system for code capable of spawning up to 130 million lightweight processes inside a single OS process. (0:00)
  • BEAM enforces preemptive context switching every millisecond via reduction counting, allowing a single-core instance to maintain an uninterrupted 10,000 ops/sec throughput even when a concurrent task triggers an infinite CPU-bound loop. (15:36)
  • Operators can attach a live remote IEx shell to a saturated production node to inspect stack traces, stream runtime function calls, and execute unconditional process termination (Process.exit(pid, :kill)) without dropping user connections. (20:11)
  • By providing native concurrency, distributed location transparency, and hot code reloading within a single runtime, BEAM eliminates the traditional trade-off between rapid MVP development and long-term enterprise scalability. (39:58)

Detailed notes

## Background Published on YouTube by GOTO Conferences on May 22, 2019, this presentation from GOTO Chicago 2019 features Saša Jurić, author of Elixir in Action. According to the session description, the presentation looks past syntax to explore the concurrency model of Erlang and Elixir, using theory and live demonstrations to explain why the underlying BEAM runtime is uniquely suited for building highly available, scalable server-side systems.

## TL;DR Dedicated to the memory of Erlang co-inventor Joe Armstrong, the presentation explores how the BEAM virtual machine delivers fault tolerance and high availability through lightweight process isolation, preemptive scheduling, and message passing. Through live demonstrations running on a single CPU core, Jurić shows how BEAM isolates unhandled exceptions, preempts tight CPU-bound loops, and enables live remote introspection and zero-downtime hot code reloading while sustaining 10,000 background operations per second. He also showcases distributed clustering via libraries like Swarm, contrasting BEAM's unified runtime with the fragmented operational overhead of mainstream multi-tier architectures.

## Atmosphere & style The presentation carries an enthusiastic, fast-paced, and pragmatic energy, underpinned by Jurić's dry humor and obvious passion for the technology. Watching the live demonstrations feels thrilling and confident as Jurić deliberately stresses the system with runaway infinite loops and hard kills, calmly navigating live shells and production charts to prove BEAM's resilience. The narrative balances a reverent tribute to Joe Armstrong with a bold, refreshing critique of modern infrastructure sprawl, creating an engaging and persuasive experience for backend engineers.

## Bulletin - [00:00](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=0s) The true foundation uniting Erlang and Elixir is the BEAM virtual machine, which acts essentially as an operating system for code capable of spawning up to 130 million lightweight processes inside a single OS process. - [15:36](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=936s) BEAM enforces preemptive context switching every millisecond via reduction counting, allowing a single-core instance to maintain an uninterrupted 10,000 ops/sec throughput even when a concurrent task triggers an infinite CPU-bound loop. - [20:11](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=1211s) Operators can attach a live remote IEx shell to a saturated production node to inspect stack traces, stream runtime function calls, and execute unconditional process termination (Process.exit(pid, :kill)) without dropping user connections. - [39:58](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=2398s) By providing native concurrency, distributed location transparency, and hot code reloading within a single runtime, BEAM eliminates the traditional trade-off between rapid MVP development and long-term enterprise scalability.

## Key takeaways - [00:00](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=0s) Erlang and Elixir serve primarily as distinct language interfaces to harness the underlying capabilities of the BEAM virtual machine. - [02:46](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=166s) A BEAM process is an isolated sequential runtime execution context with its own stack, heap, and garbage collection cycle, communicating purely through asynchronous message passing. - [06:00](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=360s) The entire BEAM system runs inside a single operating system process, scheduling up to tens of millions of lightweight processes across CPU cores using dedicated scheduler threads. - [13:09](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=789s) Structuring applications with dedicated processes per connection and per task establishes failure isolation, preventing unhandled exceptions from terminating the broader system. - [15:36](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=936s) Preemptive scheduling based on reduction slices ensures fair CPU allocation, isolating system latencies so that computationally intensive jobs do not block background traffic. - [17:39](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=1059s) High availability in BEAM focuses on resilience and user service continuity under partial system failure rather than chasing theoretical uptime metrics. - [20:11](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=1211s) Live remote introspection via interactive shells allows on-call operators to diagnose performance bottlenecks and runaway processes directly within production environments without node restarts. - [24:48](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=1488s) BEAM provides first-class process cancellation mechanisms that terminate rogue workers while allowing supervising processes to capture exit signals and report structured errors. - [26:29](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=1589s) Hot code reloading enables deploying compiled patches directly to active nodes to resolve boundary bugs and improve algorithmic complexity without service downtime. - [29:51](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=1791s) BEAM natively exposes cluster primitives that treat process spawning and messaging identically across local cores and remote network nodes via location-transparent PIDs. - [33:20](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=2000s) Embedding distributed coordination libraries like Swarm runs orchestration logic as internal supervision trees, eliminating dependencies on external coordination daemons. - [36:30](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=2190s) Consolidating caching, queuing, and real-time mechanics into a single language and OS process drastically lowers the architectural and onboarding complexity barrier. - [38:20](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=2300s) Technical uniformity across the development lifecycle removes the necessity for fragmented technology silos and brittle external configuration tooling. - [39:58](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=2398s) The BEAM runtime dismantles the conventional compromise between fast early-stage prototyping and robust, distributed high-scale production systems.

## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=0s) Honoring Joe Armstrong and the Core Focus of the Talk

Saša Jurić opens the presentation by dedicating it to the legacy of Joe Armstrong, the principal inventor of Erlang, who had recently passed away. Reflecting on Armstrong’s impact, Jurić highlights that everything presented exists first and foremost because of his immense contribution to the field of software engineering. Speaking from personal experience, Jurić describes working with Erlang and Elixir as by far the favorite part of his own professional career, and he hopes to spread some of that happiness by explaining why these technologies are so exciting.

He frames the presentation as a journey toward discovering the "soul" of Erlang and Elixir. While audience members might feel they are getting two languages for the price of one—and Jurić notes he could easily toss a few more BEAM languages into the mix—his real focus is the shared foundation underneath them: the Erlang virtual machine, commonly known as the BEAM. Because Jurić views the languages primarily as interfaces used to communicate with and harness the power of the BEAM, he intends to treat them interchangeably. Although most code samples and demos will be in Elixir, the concepts apply equally to Erlang or any other BEAM language.

Jurić outlines the talk in three main segments: - A basic introduction to the principles and mechanics of BEAM concurrency. - A central, demo-driven exploration of BEAM concurrency in practice, examining how and why systems are split into concurrent parts and the tangible benefits gained. - A final, somewhat controversial think piece examining an alternative architectural style for constructing software systems—one that runs counter to mainstream industry trends, yet offers a simpler, more powerful, and flexible foundation.

Warning the audience that his plan is ambitious and time is short, Jurić admits he will speak quickly: “Basically whatever I'm about to show you exists first and foremost owing to Joe's immense contribution to our field and speaking personally for myself I'm extremely grateful for that.”

[02:46](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=166s) The BEAM Concurrency Model and Processes

Jurić introduces BEAM-style concurrency as Joe Armstrong originally envisioned it, centered on the foundational abstraction called a process. In BEAM terminology, a process is an isolated runtime execution context. All runtime code, such as Elixir code, runs inside some process. Crucially, a BEAM process is neither an operating system process nor an operating system thread.

At its core, a process is strictly a sequential program with zero internal concurrency. Inside a single process, execution follows standard sequential mechanics: invoking functions, passing parameters, handling return values, branching, and executing loops. Within this sequential scope, Elixir exhibits functional characteristics. Concurrency is only achieved when multiple processes run at the same time. The most fundamental mechanism to start a process is the standard library function `spawn`, which takes an anonymous function (a lambda) and begins executing it in a newly created process. As soon as the new process is spawned, the originating process immediately resumes its own work.

```mermaid flowchart LR accTitle: Message Passing Concurrency in BEAM accDescr: Process A spawns and sends a message to Process B, which retrieves it from its mailbox queue.

subgraph Sender["Process A (Sequential)"] PA["Run code"] --> SP["spawn(fn ->... end)"] SP --> SND["send(target_pid, message)"] end

subgraph Receiver["Process B (Sequential)"] MB["Mailbox (FIFO Queue)"] --> RC["receive do... end"] RC --> PR["Process Message"] end

SP -.->|"creates"| Receiver SND -->|"delivers term"| MB ```

Once running, these processes exist as two completely independent programs. They share nothing: they have separate execution flows, separate memory spaces (each with its own stack and heap), and separate garbage collection cycles occurring at independent times. The only mechanism they have to coordinate or cooperate is message-passing concurrency. A process sends a message to another process using the primitive `send` function, addressing the target process directly. Messages can consist of any arbitrary Elixir data term. The receiving process retrieves messages sequentially from its internal mailbox, which behaves like a FIFO queue.

An audience member interjects to ask how processes find or address each other. Jurić clarifies that process addressing is handled through process identifiers (PIDs) or process registration, noting that it is a rich topic in its own right and that concrete examples will follow shortly in the demos. He then draws an analogy between BEAM processes and microservices architecture: both models feature independent entities that share no state and communicate exclusively via asynchronous messages. However, with BEAM processes, there is no network overhead, no HTTP protocol overhead, and no need for explicit serialization or deserialization; the runtime natively provides the entire mechanism with extreme efficiency.

As Jurić summarizes the essence of this isolation: “We have two completely independent programs running completely separately from each other they have nothing in common with each other at all.”

[06:00](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=360s) Under the Hood: The BEAM Runtime and the First Live Demo

Jurić explains the physical execution of a BEAM system on standard hardware. A typical project, consisting of regular source code files in a folder structure, is started via a single command. That command boots exactly one operating system process: an instance of the BEAM virtual machine. The entire application runs inside this single OS process.

Within that single OS process, developers can spawn thousands, hundreds of thousands, or even millions of lightweight BEAM processes. The virtual machine imposes an architectural upper limit of around 130 million concurrent processes. Because their memory overhead and initial startup costs are negligible, spawning large numbers of them is standard practice. To schedule these processes across hardware resources, the BEAM starts a small pool of OS threads called schedulers—by default allocating one scheduler per available CPU core. Conceptually, one can imagine a central queue of processes ready to execute: a scheduler pulls a process from the queue, runs it for a small slice of work, places it at the end of the queue, and picks up the next one. While the actual runtime implementation is considerably more advanced, this mental model accurately conveys how massive concurrency is managed without heavy OS thread context switching.

```mermaid flowchart TD accTitle: BEAM Runtime Architecture accDescr: A single OS process hosts schedulers mapped to CPU cores, running thousands of lightweight BEAM processes.

subgraph OSProc["Single Operating System Process (BEAM VM Instance)"] subgraph Hardware["CPU Core Mapping"] S1["Scheduler 1 (OS Thread)"] --- C1["CPU Core 1"] S2["Scheduler 2 (OS Thread)"] --- C2["CPU Core 2"] SN["Scheduler N (OS Thread)"] --- CN["CPU Core N"] end

subgraph RunQueue["Execution Scheduling"] Q["Processes Pending Execution"] S1 --> Q S2 --> Q SN --> Q end

subgraph LightweightProcs["Thousands to Millions of BEAM Processes"] P1["Process 1\n(Isolated Heap/Stack)"] P2["Process 2\n(Isolated Heap/Stack)"] P3["Process 3\n(Isolated Heap/Stack)"] PX["Process 10,000+\n(Isolated Heap/Stack)"] end

Q -.-> P1 Q -.-> P2 Q -.-> P3 Q -.-> PX end ```

Moving to the practical implications, Jurić transitions into a series of live demonstrations to answer how systems should be split into processes and what concrete architectural benefits emerge. He brings up an example system implemented in Elixir that is already running on his laptop as a headless background daemon, started identically to how it would run in a production environment. Everything shown on screen runs strictly inside that single OS process, with no external auxiliary processes or dependencies.

Navigating to an internal administrative dashboard, Jurić initiates synthetic load by triggering background jobs. Each individual job is simply an independent sequential process. Within a single second of clicking the button, 10,000 separate processes are spun up and actively running inside the VM. Each of these 10,000 processes executes an infinite loop that runs a short CPU-bound calculation to simulate realistic system work, after which it transmits an update message to a designated, well-known process in the system.

- Operating system footprint: exactly one OS process runs the entire BEAM node. - Concurrency scale: BEAM supports up to approximately 130 million lightweight processes inside that single OS process. - Hardware scheduling: by default, the BEAM runs one scheduler per available CPU core to distribute the lightweight processes. - Demo workload: 10,000 distinct processes were launched simultaneously in less than one second, each running an infinite loop containing a CPU-bound payload and message passing.

[08:45](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=525s) Benchmarking Architecture and Single-Scheduler Simulation

Saša Juric walks through the live dashboard metrics that monitor the demonstration system. The workload consists of thousands of synthetic operations: a singleton process acts as a well-known, addressable reference point that marks a success for each iteration, sleeps for a single second, and repeats the cycle. Other processes aggregate this reporting data and transmit it over to the browser page. The dashboard displays a real-time line chart on the left, indicating an expected baseline of roughly 10,000 successes per second. Juric notes that the line is not perfectly flat: small oscillations appear because the operational logic is not rigidly synchronized, and the modest machine running the demo is also handling heavy client-side SVG rendering inside the browser.

To expose the inner workings of the runtime under severe constraints, Juric explains that he deliberately constrained the virtual machine's concurrency settings. While the host machine has four CPU cores and the BEAM runtime would normally run four scheduler threads, Juric downgraded the configuration to run on a single scheduler thread. This simulates running on a single-core CPU. Despite the synthetic load of 10,000 operations per second, the single scheduler on the right-hand dashboard remains relaxed, running at a moderate workload of only around 20% CPU capacity.

```mermaid flowchart LR accTitle: System Process Architecture and Client Connection accDescr: Flow diagram showing how synthetic metrics, client WebSockets, and calculation tasks interact within BEAM.

subgraph BackgroundLoad ["Background Metric Pipeline"] A["Singleton Process"] -->|"Records success"| B["Aggregator Processes"] B -->|"Publishes stats"| C["Dashboard SVG Chart (~10k ops/sec)"] end

subgraph ClientFlow ["User Request Flow"] Browser["User Browser"] <-->|"WebSocket"| Conn["Connection Process"] Conn -->|"Spawns"| Task["Calculation Process (Task)"] Task -->|"Returns result or crash notice"| Conn end ```

[10:30](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=630s) Web Interface and Process Separation per Request

With the background load humming along, Juric navigates to a simple user-facing web interface. He describes it as capturing what he views as the fundamental essence of any web application: an input field where a user supplies data, followed by backend processing that returns an output. In this case, the application takes a natural number $n$ and computes the sum of the first $n$ integers (for example, entering $3$ yields $1 + 2 + 3 = 6$, and entering $4$ yields $10$).

Behind the interface sits a deliberate process design. Whenever a browser visits any page in this system, a persistent WebSocket connection is opened. All communication flows asynchronously over that socket. Critically, each individual WebSocket connection is owned and managed by its own dedicated BEAM process: - If three users connect, exactly three connection processes exist. - If a million users connect simultaneously, there will be one million connection processes. - If everyone disconnects, the count drops back to zero.

The internal architecture within a single connection goes one step further: the math calculation is offloaded to a separate, one-off, ephemeral process. When the connection process receives a calculation request, it spawns a calculation worker process (an Elixir `Task`), receives the computed integer asynchronously, forwards the response over the WebSocket to the browser, and lets the calculation process terminate normally. Juric points out that this structure was chosen for a deliberate architectural goal: to provide fault tolerance and maintain high availability under production stress.

[13:09](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=789s) Separating Failures: Surviving Unhandled Exceptions

To test the system's fault isolation, Juric demonstrates the most common production problem: an edge case leading to an unhandled runtime exception (analogous to dividing by zero, passing a negative number to a square root, or encountering `undefined is not a function`). In this application, submitting the input `13` triggers a deliberate crash. The user interface immediately displays an error badge. Tailing the background service logs reveals an unhandled runtime error where a `Task` crashed and terminated abnormally.

Despite the crash, the wider system remains completely unaffected. When Juric repeatedly spams input `13` to generate a stream of crashes, the dashboard line chart remains steady at 10,000 successes per second. Juric emphasizes the core BEAM design philosophy: > “Now an important thing about BEAM is that when a process crashes everything else progresses right, so most of the system still works.”

Furthermore, failures in BEAM are transparent rather than silent or fatal to the host node. When a process terminates—normally or abnormally—any interested process linked or monitoring it receives a failure message explaining that a particular process died due to a specific error reason. Because the parent connection process receives that notification, it does not drop the WebSocket connection; instead, it catches the termination event, maps it to the user's action, and reports a clean error message back to the web UI.

- Unhandled exceptions are isolated strictly within the single-use calculation process. - Background throughput does not dip below its 10,000 operations per second baseline during repeated crashes. - Sibling processes monitor workers via asynchronous messages to format and handle errors gracefully without crashing the connection.

[15:36](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=936s) Separating Latencies: Preemption and Fair Scheduling

Juric introduces a second major rationale for process isolation: latency separation. The application's summing algorithm is written in an intentionally inefficient, brute-force manner, computing $\sum_{i=1}^{n} i$ via a CPU-bound loop from $1$ up to $n$. Submitting an enormous input—specifically the nine-digit number `999,999,999`—triggers heavy, continuous CPU calculation.

When Juric submits this input, the calculation takes about ten seconds to complete, pushing the single scheduler thread straight to 100% CPU utilization. Yet, throughout that computation, the dashboard continues to record an uninterrupted 10,000 successes per second. Even though the application is pinned to a single operating system thread with no explicit `yield` calls or concurrency trickery in the user code, the background traffic does not stall.

Juric explains that this fairness is enforced directly by the BEAM virtual machine scheduler, which executes true preemptive context switching: > “The thing is that BEAM scheduler performs very frequent context switching like in less than 1 millisecond intervals and it does it with proper preemption.”

Every process, whether doing network I/O or tight CPU arithmetic, is allocated a short reduction slice before being preempted and swapped out. The runtime intentionally prioritizes overall system throughput and fair resource distribution over maximizing the raw execution speed of any single, isolated task—an architectural trade-off fine-tuned specifically for resilient multi-tenant server software.

[17:39](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=1059s) Surviving Stress and the True Meaning of High Availability

Saša Jurić highlights that separating failure domains and separating latencies are the two most fundamental reasons to split work across Erlang processes. To demonstrate this in the running system, he submits an intentionally CPU-intensive input (`456789`) through the UI. Because the calculation runs in its own dedicated process, the client connection remains responsive, allowing him to immediately issue subsequent inputs and trigger intentional errors without locking up the interface. Despite these artificial stresses, the overall workload across the cluster continues to progress steadily, and the individual user still receives their intensive computation result once it completes.

```mermaid flowchart LR accTitle: User Connection and Isolated Calculation Flow accDescr: The user connection process delegates a heavy task to a dedicated calculation process while remaining free to handle other requests. subgraph Client ["Client Session"] C["Connection Process"] end subgraph Isolation ["Process Isolation"] Calc["Calculation Process<br/>(CPU-intensive task)"] Other["Subsequent Input /<br/>Error Processing"] end C -->|"spawns & monitors"| Calc C -->|"processes immediately"| Other ```

Jurić draws an analogy to a ship that stays afloat even when taking on water: minor leaks should never sink the entire vessel. In his view, true reliability is not an abstract chase for arbitrary metrics, but rather making sure real users can continue getting work done. As he remarks, “this in my mind is what high availability is about it's not about chasing some mythical amount of nines of uptime it's about having a system which is there for its users which is ultimately its primary if not the only purpose.”

[18:44](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=1124s) The Infinite Loop Rogue Activity

While the system is resilient against isolated crashes and high latency, certain bugs can pose an existential threat to the runtime if left unmitigated. Jurić demonstrates one of the most insidious failure modes: feeding an invalid domain input, such as the negative number `-1`. The software fails to reject the value upfront, nor does it return an error. Instead, the computation hangs indefinitely while the scheduler utilization chart shoots up to a sustained 100%.

Under the hood, this rogue activity is running an unyielding, infinite CPU-bound loop—the functional equivalent of `while (true);`. Yet because of preemptive scheduling in the BEAM virtual machine and careful process design, the background throughput does not crash; it holds at its baseline rate of 10,000 successful operations per second. However, Jurić warns that this grace period is finite: if users retry failed requests or more clients hit the same codepath, those runaway loops will compound until the server grinds to a total halt. Diagnosing and resolving the issue before a widespread outage occurs becomes urgent.

- Invalid negative input `-1` bypasses domain validation and triggers an infinite CPU-bound loop. - Preemptive scheduling keeps the system operating at ~10,000 requests per second despite the runaway process. - Multiple unconstrained loops would eventually saturate all available scheduler capacity, making quick operator intervention essential.

[20:11](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=1211s) Investigating the Live BEAM via Remote Console

Jurić steps into the shoes of an on-call operator who has just received a high-CPU alert. Connecting over SSH, he first inspects the system log files. The logs are entirely static—aside from earlier recorded errors caused by testing the number `13`, there are no traces or warnings explaining where the CPU cycles are going. Traditional logging falls flat when an application is stuck in an active, non-crashing loop.

```mermaid flowchart TD accTitle: Remote Operator Diagnostics Flow accDescr: An operator connects to the BEAM node via remote shell and queries runtime introspection functions. Operator["Operator (SSH)"] --> Shell["Remote IEx Shell<br/>(Node Context)"] Shell --> Top["runtime.top()<br/>List Top CPU Spenders"] Top --> Suspect["Identify Culprit PID<br/>0.12605.0"] Suspect --> Inspect["Process Introspection<br/>Stack Trace & runtime.trace()"] Inspect --> Terminate["Process.exit(pid,:kill)<br/>First-Class Runtime Cancellation"] ```

Fortunately, BEAM provides built-in introspection without requiring preconfigured diagnostic flags or process restarts. Jurić launches a remote `iex` shell that connects straight into the running node, executing commands within the live environment. Because the single-threaded scheduler guarantees fair execution time, the debugging shell itself remains fully responsive alongside the active 10k ops/sec background workload. Jurić notes that the BEAM's cooperative guarantees extend directly to the operator: “going to beams scheduling I am actually able to even do this to debug the system itself.”

- Log inspection yields zero data about an infinite loop since no exceptions are being thrown. - A remote IEx console attaches to the live production instance without restarting the application. - BEAM scheduler fairness allows diagnostic shell interactions even while a core is pinned at 100% capacity.

[21:42](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=1302s) Hunting the Culprit with Reductions and Tracing

To find the specific process devouring compute resources, Jurić inspects the runtime process table. Calling `Process.list()` returns a list of PIDs (process identifiers), each representing an isolated unit of execution. Passing a PID into `Process.info/1` exposes core runtime metrics maintained by BEAM, most notably the *reductions* count. Reductions are a non-negative counter that acts as a proxy for the total instructions a process has executed since its creation.

By sampling reduction deltas across all processes over time, an operator can generate a `top`-like ranking of CPU consumption. Jurić invokes a pre-written helper function, `Runtime.top()`, which queries reductions over a one-second window and returns the highest spenders alongside their percentage of CPU usage. A single process—with PID `<0.12605.0>`—stands out as the clear outlier. Running the helper a second time confirms that this exact PID continues to consume nearly all the processor time.

```elixir # Conceptual representation of identifying the rogue process iex> Runtime.top() [%{cpu: 98.4, pid: #PID<0.12605.0>},...] iex> pid = hd(Runtime.top()).pid #PID<0.12605.0> iex> Process.info(pid,:current_stacktrace) ```

With the offending PID assigned to a variable, Jurić pulls its active stack trace using `Process.info(pid,:current_stacktrace)`. Even though the process is trapped in an infinite loop and cannot voluntarily yield or process incoming messages, the runtime itself inspects the memory directly. To supplement the static snapshot, he executes `Runtime.trace()`, which dynamically hooks into the process and streams the next 50 function invocations (or up to one second of execution) to show precisely what code is being run.

[24:48](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=1488s) Killing the Rogue Process and Runtime Cancellation

Having captured the stack trace and execution traces, Jurić terminates the rogue process by invoking `Process.exit(pid,:kill)`. This sends an unconditional exit signal—the BEAM equivalent of a UNIX `kill -9` directed at a specific lightweight process. The moment the function executes, the CPU scheduler graph on the monitoring dashboard plummets back to normal levels, while the continuous 10,000 requests per second remain undisturbed.

Crucially, the client UI does not hang; it immediately surfaces an error notification. Because the user's connection was governed by a separate connection process monitoring the calculation worker, the violent destruction of the worker process was captured and translated into a clean, structured failure message. As Jurić explains, “the calculation process itself was brutally killed and you can truly try catch your way out of that one but because we have another process which gets the information about it this is the connection process we are able to consistently report error information back to the user interface.”

He emphasizes that BEAM's exit signaling delivers true first-class cancellation at the VM level. This mechanism can be triggered manually from an interactive shell or wired programmatically into timeouts and client disconnections, wiping out runaway work without leaving lingering threads or corrupting state. With the system stabilized, Jurić turns back to the gathered stack traces and module traces to analyze the root cause of the bug.

[26:29](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=1589s) Tracking the Infinite Recursion and Hot-Fixing Production

```mermaid flowchart TD accTitle: Hot Code Reloading and Fix Pipeline accDescr: A flowchart depicting the diagnosis of the infinite loop, testing of the mathematical fix, and runtime hot patch without restarting the node.

subgraph Edge ["System Edge"] Input["User Input (-1, 0, 13, 9999...)"] --> Guard["Input Validation & Boundary Guards"] end

subgraph Engine ["Deep System Logic"] Guard -->|"Positive Integers Only"| Math["Gauss Sum: n * (n + 1) / 2"] Guard -.->|"Pre-fix Invalid Input"| InfiniteLoop["Unchecked Recursion: arg1 + 1, arg2 - 1"] end

subgraph Deploy ["Runtime Hot Reload"] Math --> Tests["Local Unit Tests Pass"] Tests --> HotPatch["Recompile & Inject Bytecode"] HotPatch --> RunningNode["Production BEAM Node (Zero Downtime / 10k req/s sustained)"] end ```

Looking deeper into the dynamic runtime inspection view, Saša Jurić examines the module name, file path, and exact line number to place himself in the right context within the codebase. The runtime inspection reveals what the runaway process was actually doing: it was stuck in a constant tail-recursion—the functional programming equivalent of an infinite loop. Inspecting the function arguments at each successive frame of the call stack tells the whole story: the first parameter incremented by one on every step, the second parameter decremented by one, and the third parameter grew by an irregular amount.

With the stack trace and the loop arguments laid bare, the underlying design bug becomes clear. The function running deep inside the system was written under the rigid assumption that its argument would always be a positive integer, failing completely when given any other input. As Jurić explains, that internal assumption is actually fine for the business domain, because the core domain models natural numbers. The real fault lies at the edge of the system, where incoming user input must be sanitized and rejected before it can contaminate internal components. While the edge already rejected certain inputs, it failed to cover every boundary condition: “It's a classical kind of bug, you know, I haven't thought of all possible scenarios.”

Jurić repairs the code live, joking that “all is fair in demos” as he uncomments the missing guard clauses and fixes all three flaws at once: - **Input Validation**: Edge cases like negative numbers and zeros are caught and cleanly rejected with an explicit error. - **The Number 13**: The synthetic bug triggered by this specific edge-case value is removed. - **Algorithmic Complexity**: Instead of naively summing numbers one by one, he substitutes the standard closed-form formula from high school math: $\frac{n(n + 1)}{2}$.

He runs the test suite to verify the logic, and everything passes. He then executes a deployment task to inject the updated compiled modules straight into the running production system without restarting the BEAM node. The dashboard proves zero downtime occurred: the throughput line never dips below its steady 10,000 successful operations per second. Testing the web page live confirms the fix instantly without even a browser reload. Submitting `-1`, `0`, or `-123` returns an explicit `invalid input`; submitting `3`, `4`, and `13` returns correct results; and submitting a massive string of nines completes as fast as lightning. Reflecting on the ease of observing, diagnosing, and hot-patching an active system, Jurić argues:

> “As a writer of systems and ultimately one of people who is responsible for managing those systems in production, this is what I want from my tool.”

He notes that he knows of no other runtime technology that comes remotely close to the operational service BEAM provides.

---

[29:51](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=1791s) BEAM Clustering and Distributed Primitives

Jurić turns to the BEAM's native support for distributed computing. While developers typically run one BEAM instance per physical or virtual machine, multiple instances can easily run on a single local host for testing. Connecting two separate BEAM instances over a standard TCP connection requires only a single function invocation. Once linked, the instances form a single BEAM cluster, and each instance within that cluster is referred to as a node.

Within a cluster, Erlang and Elixir expose low-level distributed primitives directly to the application layer. The programmer can enumerate all active nodes in the cluster, subscribe to cluster membership events (nodes joining or leaving), spawn a new process on any arbitrary remote node, and seamlessly send or receive messages across machine boundaries. The syntax for inter-process communication remains identical whether the target process lives on the local core or across the network, illustrating the BEAM philosophy that concurrency and distribution are two facets of the exact same model.

- A BEAM cluster is established by opening a TCP connection between nodes via simple API calls. - Process spawning (`spawn/4`), message sending (`send` or `!` operator), and message receiving work identically regardless of physical node location. - Nodes can monitor remote processes and listen for cluster topology changes using built-in system monitors.

---

[30:53](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=1853s) Live Demo: Swarm, Dynamic Service Placement, and Node Failover

To showcase distributed mechanics in practice, Jurić opens an administrative dashboard designed to manage named services across the cluster. In BEAM terminology, a "service" is simply a stateful concurrent process registered under a global, cluster-wide identifier. The interface initially shows only a single active node, so he launches a second node (`node2`) in the foreground from his terminal; the dashboard detects it immediately and displays both members.

Jurić registers a new service named `foo`. The cluster automatically chooses a host node, spawns the process, and registers its name globally. Clicking `foo` triggers a lookup: the web frontend locates the process by its global identifier anywhere in the cluster, sends it a message, receives a reply, and renders the payload. The output reveals that the process maintains state—retaining its own assigned name—and knows the identity of the specific node it is running on. When he registers a second service named `bar`, the system places it on `node2`, balancing the load across instances while providing an identical programming interface.

Next, Jurić simulates hardware failure by abruptly terminating `node2`. The cluster detects the loss, triggers automatic failover, and resurrects `bar` on `node1`. Clicking `bar` again shows that it now responds from `node1`, proving that application-level fault tolerance can withstand the sudden death of individual machines.

However, Jurić offers an honest assessment of standard distributed Erlang:

> “Full disclosure, complete honesty, and this all looks well and fine, but I would say that BEAM distribution is sort of plagued with a bunch of issues—serious issues.”

Because of these limitations—spanning from fundamental runtime networking assumptions down to the libraries built on top—many experienced practitioners advise against using native BEAM distribution in certain mission-critical environments. Nevertheless, Jurić emphasizes that these issues are purely mechanical implementation details rather than design flaws; they can be addressed and improved over time without breaking the high-level programmer-facing API contract.

---

[33:20](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=2000s) Embedding Distributed Orchestration via Elixir Dependencies

Walking through the source code behind the distributed demo, Jurić highlights its architectural simplicity. The underlying worker module is implemented as standard, concurrent Elixir code that remains completely agnostic of the network layer. It contains plain functions and process loops, functioning identically whether running on a standalone single node or across an interconnected multi-node cluster.

Transforming that standalone local worker into a resilient, cluster-aware service required only three concrete steps. The first step was adding a single line to the dependencies block of the project’s `mix.exs` build configuration file: bringing in the `swarm` library. Swarm acts as a high-level distributed service orchestrator that coordinates cluster-wide process registration, placement, and automatic failover.

Jurić emphasizes a fundamental difference between BEAM libraries and dependencies in other programming ecosystems: in most languages, pulling in an external package merely imports passive code (modules and functions) waiting to be called. In the BEAM world, bringing in a dependency can also introduce running processes. When the application boots, Swarm automatically launches its own internal supervision tree and background processes. These active processes monitor node-up and node-down events, maintain the distributed registration table, and continuously manage the state mapping process identifiers across the network—all triggered by adding that single line in `mix.exs`. Because the orchestrator runs directly inside the BEAM runtime as an integrated component of the running application, developers avoid the need for separate external coordination daemons.

[35:13](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=2113s) Clustering with Swarm and True Location Transparency

Saša Jurić demonstrates how easy it is to coordinate services across an entire cluster using an Elixir library called Swarm. Once Swarm is running inside the cluster, starting a globally named service boils down to invoking a single Swarm function. Swarm automatically selects a node, starts the process on that node, and tracks the association between the chosen name and the process identifier (`PID`). When a client process needs to find and invoke that service later, it calls another Swarm function with the service's name. Swarm returns a `PID`, which uniquely identifies the process across the entire cluster—not just on the local node.

Because `PID`s in Erlang and Elixir are location-transparent, they function identically whether pointing to a local process on the same machine or a remote process on another node in the cluster. Most BEAM-agnostic functions that take a `PID` do not care where the underlying process actually resides; you pass the `PID` to the function and it simply works. Jurić highlights that it took only three lines of code to turn the system into a distributed cluster. There are no sidecar processes running, no auxiliary operating system processes, no Docker containers, no virtual machines, and no external cloud services employed. The entire demo operates while completely disconnected from the network, and even the frontend user interface is driven straight from Elixir code without writing any JavaScript.

```mermaid flowchart LR accTitle: Distributed Service Discovery with Swarm accDescr: Swarm starts a named process on a chosen node, returns a location-transparent PID, and allows any node to communicate with it directly. subgraph Cluster["BEAM Cluster"] direction TB NodeA["Node A (Local)"] NodeB["Node B (Remote)"] SwarmReg["Swarm Registry"] ProcB["Service Process (PID)"] end

NodeA -->|"1. swarm:register_name / whereis_name"| SwarmReg SwarmReg -->|"2. Spawns / Resolves"| ProcB SwarmReg -->|"3. Returns cluster-wide PID"| NodeA NodeA -->|"4. Message passing via PID"| ProcB ```

- Starting, discovering, and communicating with a distributed service required just three lines of code. - A `PID` is completely location-transparent, seamlessly referencing either local or remote processes across nodes. - No third-party infrastructure (like service meshes or container orchestrators) was required; the entire stack ran self-contained and completely offline.

---

[36:30](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=2190s) Technical Uniformity: System A vs. System B

Jurić shares a comparison table originally published in his book *Elixir in Action*, reflecting on two real-world systems he worked on side-by-side around the start of the decade. System A started with a foundational technology that lacked native primitives for concurrent, distributed, fault-tolerant software systems. When faced with inherent operational requirements—such as background jobs, caching, task queuing, or real-time communication—the engineering team had no choice but to fall back to the operating system level. They spun up multiple OS processes, brought in different programming languages where concurrency was needed, and layered on a patchwork of third-party products. While this cobbled-together stack technically satisfied their feature requirements, the resulting complexity barrier was massive. A junior developer joining the project faced an intimidating mountain of disconnected tools and paradigms before they could safely touch production code.

In contrast, System B was built on Erlang and the BEAM runtime. Because the BEAM effectively acts as an operating system specifically tailored for running code, the language itself serves as the interface to concurrency and fault isolation. The team fulfilled every operational requirement—distributed state, background processing, monitoring, and orchestration—directly inside Erlang, in a single project running within a single OS process per machine. While native Erlang implementations might lack every specialized bell and whistle found in dedicated third-party enterprise tools, they were more than sufficient for the application's actual needs. In exchange, the project achieved complete technical uniformity. As Jurić observes:

> “It is in fact sometimes said that BEAM is an operating system for our code, and if I have the operating system directly in my runtime and I have the language as the main interface, then I don't need to fall back to the OS level.”

| System Attribute | System A (Fragmented Architecture) | System B (Erlang / BEAM) | |:--- |:--- |:--- | | **Foundation** | Tech lacking native concurrency/distribution | Erlang on top of the BEAM runtime | | **Concurrency Strategy** | OS processes, multi-language bridging | Native BEAM lightweight processes | | **Auxiliary Needs (Cache, Queues)** | External third-party tools & daemons | Handled in-language within the project | | **Runtime Footprint** | Many disparate processes, containers, configs | Single OS process per machine | | **Complexity Barrier** | Extremely high onboarding learning curve | Low; single language and operational model |

---

[38:20](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=2300s) The Compounding Value of Lowering the Technical Bar

Uniformity should never be pursued merely for its own sake, but Jurić argues that having an entire stack built in a single technology—one language, one project repository, and one OS process per physical machine—transforms every phase of the software engineering lifecycle. Setting up a local development environment becomes trivial, writing tests and configuring continuous integration (CI) pipelines is straightforward, and deployment shifts from coordinating five to ten fragile moving parts to deploying a single unified release. In production, operations engineers have a single target to hook into, monitor, log, and remotely debug.

Crucially, the greatest dividend of technical uniformity is how it empowers teams. When the technical bar is drastically lowered, developers do not need to fracture into rigid specialty silos. Organizations no longer need a dedicated Redis specialist, an NGINX specialist, a ZooKeeper specialist, a Kafka specialist, or a Kubernetes wrangler. Instead, onboarding engineers learn Elixir and can immediately build features across the entire stack. Teams are freed from wrestling with fragile configuration files:

> “And we don't need to wrestle with all these YAMLs and INIs and JSONs and TOMLs and XMLs and other kind of ad-hoc DSLs and their quite substandard tooling.”

Rather than relying on brittle markup formats and separate daemons, the entire system can be expressed in a single Turing-complete language that integrates cleanly with the host OS, offering full compile-time and runtime flexibility.

---

[39:58](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=2398s) Unifying Fast Starts and Long-Term Scalability

Looking at the broader software industry, Jurić identifies what he considers the greatest, yet still unrealized, potential of BEAM languages. In typical development, engineering teams face a painful trade-off: choose a "quick start" framework that gets a minimum viable product out the door rapidly but must be completely rewritten once traffic scales, or choose an enterprise-grade distributed stack upfront that incurs massive operational overhead from day one.

BEAM dismantles this artificial dichotomy. Jurić paints an ideal development scenario: an engineer needs to build a small-to-medium web-facing CRUD application, initializes a new Elixir project, adds a couple of libraries, and within five minutes possesses native support for distribution, clustering, and replicated SQL-based persistence running directly inside the application. Because the application runs on a Turing-complete language backed by a runtime refined over three decades of battle-testing across massive telecommunications systems, developers can rest confident that they will not hit a wall. When complex architectural challenges and massive scaling pressures arrive down the road, the system can evolve without requiring a total ground-up rewrite:

> “The artificial trade-off of having to choose between a quick start technology and the long-run one disappears, flies out the window, and I get both in the same piece of technology.”

Jurić concludes that this rare harmony between initial simplicity and infinite operational headroom is enabled entirely by the BEAM, making it the true soul of Erlang and Elixir.

---

[41:20](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=2480s) Concluding Remarks and Book Giveaway

Jurić wraps up his talk by offering a few parting gifts to the audience. He shares discount codes on screen for his book *Elixir in Action*, offering 50% off for the day of the conference and 40% off for subsequent days. He also announces that he brought along two physical hardcover copies and several digital eBook versions of *Elixir in Action* to give away to attendees. In a moment of dry humor, he invites anyone interested to come up and grab one directly:

> “And if you wanna grab that, just approach me after the talk and ask for it; you don't have to pretend that you're interested in anything else.”

He asks the audience to rate the session, provide feedback on what worked and what didn't, switches the slide back to the discount coupon codes for the room, and bids the audience thank you to closing applause.

## Notable quotes > [39:58](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=2398s) The artificial trade-off of having to choose between a quick start technology and the long-run one disappears, flies out the window, and I get both in the same piece of technology. — Saša Jurić

> [36:30](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=2190s) It is in fact sometimes said that BEAM is an operating system for our code, and if I have the operating system directly in my runtime and I have the language as the main interface, then I don't need to fall back to the OS level. — Saša Jurić

## Mentioned Joe Armstrong, Saša Jurić, Erlang, Elixir, BEAM, Swarm, Elixir in Action, Programming Phoenix 1.4, Chris McCord, Bruce Tate, José Valim, GOTO Chicago 2019, Docker, Kubernetes, Redis, NGINX, Apache ZooKeeper, Apache Kafka

## Video description This presentation was recorded at GOTO Chicago 2019. #gotocon #gotochgo http://gotochgo.com

Saša Jurić - Author of "Elixir in Action” @sasajuric5850

ABSTRACT Whether you're developing a small web site or a large-scale distributed system, Erlang and Elixir are great tools for building server-side systems. Both languages can help you start quickly and deal with highly complex problems at a large scale. This talk aims to explain what makes Erlang and Elixir suitable for such tasks. We'll look past the syntax and the ecosystem, focusing instead on the concurrency model of these languages. Combining a bit of high-level theory and a couple of demos, we'll examine some important properties of Erlang/Elixir concurrency, with a special emphasis on why these properties are very relevant when building highly available systems. The talk targets experienced backend developers, but no knowledge of Erlang or Elixir is required. You will walk away from [...]

Download slides and read the full abstract here: https://gotochgo.com/2019/sessions/712

RECOMMENDED BOOKS Saša Jurić • Elixir in Action • https://amzn.to/2RZh5eN McCord, Tate & Valim • Programming Phoenix 1.4 • https://amzn.to/3zcUqj4

https://twitter.com/GOTOcon https://www.linkedin.com/company/goto- https://www.instagram.com/goto_con https://www.facebook.com/GOTOConferences #erlang #elixir #programming #DistributedSystems #FunctionalProgramming #SasaJuric

CHANNEL MEMBERSHIP BONUS Join this channel to get early access to videos & other perks: https://www.youtube.com/channel/UCs_tLP3AiwYKwdUHpltJPuA/join

Looking for a unique learning experience? Attend the next GOTO conference near you! Get your ticket at https://gotopia.tech Sign up for updates and specials at https://gotopia.tech/newsletter

SUBSCRIBE TO OUR CHANNEL - new videos posted almost daily. https://www.youtube.com/user/GotoConferences/?sub_confirmation=1

[Source](https://www.youtube.com/watch?v=JvBT4XBdoUE)

中文简报

Erlang 与 Elixir 的灵魂 • Sasa Juric • GOTO 2019 本演讲旨在纪念 Erlang 联合发明人 Joe Armstrong,深入探讨了 BEAM 虚拟机如何通过轻量级进程隔离、抢占式调度和消息传递来实现容错性与高可用性。通过在单 CPU 核心上运行的现场演示,Jurić 展示了 BEAM 如何在保持每秒 10,000 次后台操作的同时,隔离未处理的异常、抢占紧密的 CPU 密集型循环,并实现实时远程自省以及零停机热代码重载。他还演示了通过 Swarm 等库构建的分布式集群,将 BEAM 统一的运行时与主流多层架构分散的运维开销进行了对比。 将 Erlang 和 Elixir 联系在一起的真正基石是 BEAM 虚拟机,它本质上就像一个代码操作系统,能够在单个操作系统进程中生成多达 1.3 亿个轻量级进程。 (0:00) BEAM 通过归约计数(reduction counting)在每毫秒强制执行抢占式上下文切换,这使得单核实例即使在并发任务触发无限 CPU 密集型循环时,也能保持不间断的每秒 10,000 次操作吞吐量。 (15:36) 运维人员可以将实时远程 IEx shell 连接到饱和的生产节点,以检查堆栈跟踪、流式传输运行时函数调用,并在不中断用户连接的情况下执行无条件进程终止(Process.exit(pid, :kill))。 (20:11) 通过在单一运行时内提供原生并发、分布式位置透明性和热代码重载,BEAM 消除了快速 MVP 开发与长期企业可扩展性之间的传统权衡。 (39:58)

Reading summary

Saša Jurić argues that the true power of Erlang and Elixir lies in the BEAM virtual machine, which functions as an integrated operating system for code. By providing lightweight concurrency, fault isolation, and native distribution, the BEAM enables resilient, high-availability software without the operational complexity of fragmented infrastructure stacks. Process Isolation and Preemptive Fairness (2:46) At the core of the BEAM concurrency model is the process—a sequential, isolated execution context with its own stack, heap, and independent garbage collection cycle. A single operating system process can host millions of lightweight BEAM processes, coordinated across CPU cores by runtime schedulers. Communication occurs purely via asynchronous message passing, eliminating shared memory bugs. Through live demonstrations sustaining 10,000 synthetic operations per second, Jurić illustrates two fundamental reasons to split work across processes: separating failure domains and separating latency. When an edge-case calculation crashes from an unhandled exception, only the ephemeral worker process terminates. The parent connection process catches the exit signal and reports a clean error to the client while the rest of the application continues uninterrupted. Furthermore, the BEAM scheduler enforces true preemptive multitasking via frequent reduction-slice context switching, preventing massive CPU-bound calculations from stalling concurrent traffic even when artificially restricted to a single scheduler thread. Live Node Introspection and Hot-Patching (20:11) When bugs like unbounded tail-recursion produce an infinite loop, traditional logging yields no exceptions even as CPU usage pins at 100%. Jurić shows that the BEAM allows operators to attach a live remote interactive shell directly into a saturated production node. Because preemptive scheduling guarantees execution time to the diagnostic shell, operators can inspect process tables, sort processes by instruction counts (reductions), retrieve live stack traces, and stream execution traces dynamically without restarting the application. The runtime also provides first-class cancellation: issuing `Process.exit(pid, :kill)` immediately terminates a rogue process without leaking state or dropping client connections. Jurić repairs the flawed validation logic, replaces inefficient loops with closed-form mathematical logic, and hot-patches the compiled bytecode directly into the running instance. The fix takes effect instantly with zero downtime, maintaining steady baseline throughput throughout the intervention. Native Distribution and Process Transparency (29:51) BEAM nodes can form distributed clusters over standard TCP connections using built-in language primitives. Spawning processes and sending messages function identically whether a process lives locally or on a remote machine. Process identifiers (PIDs) maintain complete location transparency across the cluster. Demonstrating the library Swarm, Jurić shows how service discovery, cluster-wide naming, and automatic failover can be embedded into the application simply as supervised background processes, eliminating external coordination daemons or sidecars. However, Jurić explicitly qualifies this capability, noting that native BEAM distribution suffers from serious operational limitations that lead many practitioners to avoid it in mission-critical environments. He argues, however, that these are mechanical networking constraints rather than fundamental design flaws. Technical Uniformity Over Stack Fragmentation (36:30) Contrasting fragmented architectures with BEAM-native designs, Jurić argues that technologies lacking concurrency primitives force developers to rely on complex amalgams of external tools—such as Redis, Kafka, background workers, and container orchestrators. This multi-language operational overhead creates steep onboarding barriers and brittle configuration maintenance across various DSLs and markup formats. Because the BEAM acts as an in-runtime operating system, requirements like state management, caching, scheduling, and distribution can be expressed directly in one Turing-complete language running within a single OS process per machine. According to Jurić, this technical uniformity dismantles the conventional industry compromise between rapid prototyping frameworks and scalable enterprise architectures, providing both quick-start agility and long-term headroom in a unified runtime. While acknowledging that native BEAM distribution possesses practical network limitations in certain production settings, Jurić concludes that the VM's operational visibility, failure isolation, and self-contained runtime provide an unmatched alternative to mainstream, heavily fragmented architectural patterns.

精炼阅读摘要

Saša Jurić 指出,Erlang 与 Elixir 的核心力量在于底层的 BEAM 虚拟机,它将轻量进程、抢占式调度与高内省机制深度内建,使开发者能在一个技术栈内同时实现高并发、故障隔离与极简的单体架构。 BEAM 进程机制与资源调度 (2:46) BEAM 平台的核心构建单元是轻量进程,这并非操作系统原生的线程或进程,而是一种内存空间完全独立的顺序执行体。每个 BEAM 进程拥有专属的堆、栈及独立的垃圾回收周期,彼此间不共享内存,仅依赖异步消息传递与先进先出的邮箱机制进行通信。在物理层面,整套应用通常只占用单个操作系统进程,虚拟机内部则默认针对每个 CPU 核心分配一个调度器线程来分发任务。BEAM 在架构上最高可支持约 1.3 亿个轻量进程,由于启动成本微乎其微,在极短时间内拉起数以万计的并发任务成为标准操作。 在演示中,Jurić 在单台笔记本的单一操作系统进程内一秒内创建了 10,000 个后台进程,各进程以死循环运行 CPU 密集计算并持续上报心跳。为了测试极端边界条件,他刻意将虚拟机约束至仅运行单个调度器线程。即使在这样等同于单核 CPU 的严苛环境下,系统基线吞吐量仍稳定维持在每秒约 10,000 次操作,而调度器负载仅占 20% 左右,展示了其脱离重型操作系统上下文切换的高效调度能力。 故障与延迟的双重隔离保证 (10:30) 在面向用户的 Web 架构设计中,BEAM 会为每一个建立的 WebSocket 连接分配一个独立的专属进程,而在具体处理特定运算时,该连接进程会进一步衍生出一个一次性计算子进程(Task)。这种架构设计确立了两道安全屏障:故障隔离与延迟隔离。当计算子进程遭遇未捕获异常崩溃时(如传入测试异常值 13),故障仅停留在该单次任务内。由于父级连接进程监控着子任务并接收到崩溃原因通知,WebSocket 连接不会中断,前台界面只会显示友好的错误提示,而全系统每秒 10,000 次的后台基线吞吐量完全未受影响。 延迟隔离则归功于 BEAM 调度器具备的高频强占式机制,其上下文切换频率通常在 1 毫秒以内,依托 reduction 计数器强制分配执行时间片。当演示中故意输入九位数的大数触发耗时约 10 秒的暴力穷举求和、使单核 CPU 利用率飙升至 100% 时,后台吞吐指标依然保持平稳无卡顿。该机制优先保障整体系统的公平吞吐,防止长耗时或陷入死循环的失控任务霸占 CPU,从而重塑了服务可用性的实际意义。 动态探查与生产不停机热修复 (20:11) 面对最棘手的隐性 Bug——例如程序未进行边界校验,在收到负数输入 `-1` 后陷入无异常抛出的无限尾递归并导致 CPU 持续占满——传统的被动日志系统往往毫无痕迹。BEAM 提供了直接连入运行中节点的交互式远程控制台(IEx shell),且得益于公平调度,即便 CPU 处于满载状态,诊断 shell 依然响应迅速。运维人员无需预先配置侵入性调试开关,即可通过系统内建的原生函数遍历进程表,以 reductions 指标的变化增量精准定位消耗 CPU 的进程 PID。 在演示中,Jurić 定位到失控进程后,不仅当场抽取了其当前调用栈帧与即时函数调用追踪链路,还利用 `Process.exit(pid, :kill)` 信号予以精准销毁。这种由父进程监听并处理消亡事件的一等公民取消机制,瞬时化解了危机而不产生脏状态。随后,他在本地补齐了正整数边界断言防线,换用高斯求和公式优化算法复杂度,跑通单元测试后,直接将编译后的字节码热注入到正在运行的生产节点。修复全程无停机,全套系统的持续数据处理从未间断,前端无需刷新即刻恢复正常。 分布式编排与技术同构的架构红利 (29:51) BEAM 原生将并发与分布式视为同一套模型,两个 BEAM 节点只需一条 TCP 连接即可组网形成集群。其核心优势在于进程标识符(PID)具备完全的位置透明性,消息发送操作与具体进程位于本地核还是远程主机完全解耦。在演示中,借助 Swarm 这类纯 Elixir 库,仅需引入少量配置并在代码中调用三行指令,系统便自动拉起监控树,实现了跨节点的命名服务注册、动态服务摆放与节点宕机自动漂移,全程无需借助外部侧车代理、容器编排工具或额外网络组网。 Jurić 将这种模式与堆砌第三方工具的传统架构(系统 A)进行了对比。传统方案由于语言层面缺乏原生的并发与分布原语,不得不引入大量独立操作系统进程,并在外围集成 Redis、NGINX、ZooKeeper、Kafka 或 Kubernetes 以及充斥各类 ad-hoc DSL 的配置文件。而依托 BEAM 的架构(系统 B)将虚拟机视为承载代码的操作系统,在一套图灵完备的语言内搞定后台任务、分布式状态、缓存与编排,大幅降低了团队成员的认知负荷,打破了“初期敏捷但后期必须重写”与“初期就承担沉重分布式架构”的两难困境。 Jurić 坦承原生分布式 Erlang 在网络底层机制和上层库支持上仍面临一些严肃的工程问题,许多从业者在严苛生产环境中对此持谨慎态度,但他认为这些属于可迭代优化的机械实现细节。整场演讲论证了 BEAM 凭借进程隔离、调度公平性及运行时同构能力,构成了极具弹性的系统工程底座。

Detailed notes — zh

## Background 该视频由 GOTO Conferences 于 2019 年 5 月 22 日在 YouTube 平台发布,记录了《Elixir in Action》作者 Saša Jurić 在 GOTO Chicago 2019 大会上的演讲。视频简介指出,演讲超越了表层语法和生态,聚焦于 Erlang 与 Elixir 的底层并发模型与高可用特性,旨在帮助有经验的后端开发者理解如何利用它们快速构建从小型网站到超大规模分布式系统的服务端应用。 ## TL;DR Saša Jurić 深入剖析了 Erlang 与 Elixir 的底层基石——BEAM 虚拟机的并发架构与运行机制。他通过生动的现场 Demo 演示了单调度器环境下 10,000 个轻量级进程的并发运行,展示了 BEAM 进程如何实现近乎完美的故障隔离、微秒级抢占式调度以及运行期内省与热代码修复。随后,他展示了原生分布式能力与位置透明性,并对比了现代拼凑式微服务与“技术单一性”架构的差异。最后他指出,BEAM 彻底消除了“敏捷起步原型”与“支撑长远海量扩展”之间的对立,是构建高可用系统的真正灵魂。 ## Atmosphere & style 整场演讲节奏紧凑且干货密集,讲者充满激情与感染力,开篇对 Joe Armstrong 的致敬更是奠定了一层由衷敬佩的情感底色。在技术演示环节,气氛在面对失控死循环与线上告警的紧张排错中推向高潮,讲者戴上“运维帽子”现场远程取证、强制杀进程并热部署修复代码的过程令人拍案叫绝。观众不仅能感受到资深工程师对工业级架构的深刻洞察,更能体会到那种从复杂运维泥潭中解脱出来的纯粹快乐。 ## Bulletin - [06:00](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=360s) BEAM 虚拟机将 Erlang/Elixir 系统精简为单一操作系统进程,内部支持派生数百万级轻量进程,且天然具备微秒级抢占式调度以实现延迟隔离。 - [12:40](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=760s) 通过解耦长连接进程与一次性计算进程,系统在遭遇代码未捕获异常或 CPU 密集型长任务时,背景每秒万次请求的吞吐量保持完全稳定。 - [20:11](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=1211s) BEAM 原生提供基于 Reductions 指标的运行时内省与一等公民取消机制(Process.exit(pid, :kill)),支持在无外部工具干预下精准排查并热修复线上故障。 - [35:13](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=2113s) 借助 Swarm 等编排库与位置透明的 PID 特性,系统仅需三行代码即可实现具备故障转移与服务注册的分布式集群,免去笨重的外部中间件胶水层。 ## Key takeaways - [00:00](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=0s) Erlang 与 Elixir 的核心竞争力不在于表层语法,而在于底层统一的 BEAM 虚拟机的并发模型与运行时机制。 - [02:46](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=166s) BEAM 进程是拥有独立内存堆栈与独立垃圾回收的顺序执行上下文,各进程间纯粹依靠异步消息传递进行交互。 - [06:00](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=360s) BEAM 将系统整体封装在单一 OS 进程中,通过与 CPU 核心绑定的调度器管理海量轻量进程,技术上限可承载数以亿计的并发单元。 - [10:24](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=624s) 将 WebSocket 连接与运算任务拆解为双层进程拓扑,可有效阻断未捕获异常对网络连接及全局系统的连锁破坏。 - [15:36](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=936s) BEAM 调度器基于指令规约数(Reductions)在极短时间片内强制抢占,牺牲单任务极致性能以换取全系统的公平响应与延迟隔离。 - [17:39](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=1059s) 高可用性的本质不在于盲目追求“几个9”的抽象数字,而在于即使系统发生局部故障也能持续陪伴并服务用户。 - [20:11](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=1211s) BEAM 具备出色的生产内省能力,运维人员无需重启或预设探针,即可通过 IEx Shell 对生产节点进行动态诊断与 top 分析。 - [23:42](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=1422s) Process.exit(pid, :kill) 构成了运行时底层的一等公民取消机制,能瞬间终止脱缰死循环而不干扰其他正常业务事务。 - [26:29](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=1589s) BEAM 支持零停机热代码替换,允许修复模块在不停机、不掉线的情况下动态加载并即时生效。 - [29:51](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=1791s) BEAM 原生打通了单机并发与分布式边界,通过位置透明的 PID 实现跨节点透明调用与自动故障转移。 - [36:30](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=2190s) “技术单一性”主张通过统一的图灵完备语言与运行时覆盖全栈需求,大幅替代拼凑式架构中的碎片化中间件与细分专家壁垒。 - [40:00](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=2400s) BEAM 消除了“轻量快速启动工具”与“超大规模长远演进系统”之间的伪权衡,使敏捷开发与高可用架构在同一选型下得以兼得。 ## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=0s) 致敬 Joe Armstrong 与寻找 BEAM 语言的“灵魂” 演讲在掌声中开始。讲者 Saša Jurić 首先向刚逝世不久的 Erlang 主要发明者 Joe Armstrong 致以深切的哀悼与崇高的敬意。他感慨道,虽然 Joe 离开了人世,但他留下的思想和技术遗产依旧熠熠生辉,今天整个演讲所探讨的内容,归根结底都建立在 Joe Armstrong 对计算机科学领域的巨大贡献之上。谈到个人经历时,Saša Jurić 充满感情地表示,自己在相当长一段时间内都在使用 Erlang 和 Elixir,这毫无疑问是他整个职业生涯中最享受、最开心的阶段,因此他希望通过这场演讲,将这份快乐传递给大家,解释自己为什么对这些语言如此着迷。 ```mermaid flowchart TD accTitle: BEAM Runtime Architecture accDescr: High-level architectural mapping showing how multiple languages compile to run on the unified BEAM runtime layer inside a single OS process. subgraph Languages["上层开发语言与代码实现"] direction LR L1["Erlang"] L2["Elixir"] L3["其他 BEAM 语言"] end subgraph BEAM["统一运行层:BEAM / Erlang 虚拟机"] VM["BEAM 虚拟化执行环境"] end subgraph OperatingSystem["底层操作系统环境"] OSProcess["单一操作系统进程 (Single OS Process)"] end Languages -->|"统一接口与语义"| VM VM -->|"运行于宿主系统"| OSProcess ``` 他将本次演讲的主题定为寻找 Erlang 与 Elixir 的“灵魂”。虽然市面上看起来是两种甚至更多的语言,但讲者指出,他完全可以“买一送一”,因为本次探讨的核心不是表层的语法差异,而是底层的统一运行时——也就是 Erlang 虚拟机(通常称为 BEAM)。从使用者的角度和编写 BEAM 系统的开发者视角来看,所有语言都只是与 BEAM 交互、借用其威力的接口。因此他在演讲中可能会混用这些名字,有时说 Elixir(因为现场所有的演示 Demo 和代码示例都是用 Elixir 编写的),有时说 Erlang,但他真正指代的其实是任何运行在 BEAM 之上的语言。 - 整个演讲结构划分为清晰的三大部分: 1. 基础理论部分:介绍 BEAM 并发的核心原理与底层机制。 2. 核心演示部分:基于生动的实战 Demo,演示如何将一个系统拆分成不同的并发单元,并深入探讨这种架构设计带来的收益。 3. 观点探讨部分:抛出一个简短且略带颠覆性的思想实验,探讨一种与当今主流软件构建方式背道而驰的新型架构风格——这是一种依托 BEAM 运行时才得以成立的架构方式,讲者坚信它比当今行业盛行的构建方案更加简单,同时也更强大灵活。 Saša Jurić 坦言自己的议程安排非常宏大,而演讲时间十分紧迫,因此他提前向全场打好招呼,表示自己会讲得非常快,希望能为所有人带来大量值得思考的技术干货。 --- ### [02:46](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=166s) BEAM 并发模型的基石:轻量级进程与消息传递 讲者切入正题,正式剖析 Joe Armstrong 心中构想的“BEAM 风格并发”(BEAM-style concurrency)。该模型的核心基石是被称为“进程”(Process)的概念。在 BEAM 的术语中,进程是代码在运行时的执行上下文,任何 Elixir 代码在运行状态下都必然从属于某一个进程。 Saša Jurić 特别强调,必须纠正初学者的概念混淆:这里的进程既不是操作系统的 OS 进程,也不是操作系统的 OS 线程。从本质上讲,一个 BEAM 进程就是一个完全顺序执行的单线程程序(Sequential Program),进程内部没有任何并发机制。在其中,开发者按最传统的方式写代码——调用函数、传递参数、获取返回值、执行条件分支逻辑、写循环等。只要局限在单个顺序执行的范畴内,Elixir 展现出的是某种定义下的函数式编程特性;而一旦涉及跨进程的交互,就进入了该语言的另一个核心范式——并发。 要实现并发,就必须启动多个进程。Elixir 标准库提供了最底层的原语函数 `spawn`。当一个正在运行的顺序程序调用 `spawn` 并传入一个匿名函数(Lambda)时,系统就会立即派生出一个全新的进程。一旦新进程启动完成,调用方的进程立刻继续向下执行。在概念模型上,此时就有了两个完全独立、互不相干的程序在并行运行。 讲者用极其强调的语气总结这两个进程之间的完全隔离性: > “They have their own separate execution flow, they have their own separate memory space (stack, heap), they are separately garbage collected at different points in time, and they are really completely two separate programs.” > —— *Saša Jurić* 由于两者没有任何共享内存,彼此协作的唯一途径就是通过发送消息,即基于消息传递的并发(Message Passing Concurrency)。在语言底层,开发者可以通过 `send` 函数向指定的目标进程发送消息。这里的消息可以是 Elixir 中能够构建的任意数据项(Term)。在接收端,目标进程则从自己的邮箱(Mailbox,一个类似先进先出 FIFO 的队列)中拉取消息并进行处理。 ```mermaid flowchart LR accTitle: BEAM Process Message Passing accDescr: Sequence showing Process A sending an arbitrary data message into Process B's FIFO mailbox. subgraph ProcessA["进程 A (顺序程序)"] ExecA["调用 send(PID, 消息)"] end subgraph ProcessB["进程 B (顺序程序)"] Mailbox["进程邮箱 (FIFO Queue)"] ExecB["拉取消息并执行处理"] end ExecA -->|"异步发送消息数据"| Mailbox Mailbox --> ExecB ``` 为了帮助现场听众建立直观类比,Saša Jurić 将这种模式比作“微服务架构”(Microservices):正如微服务之间是两个相互独立的 OS 进程,没有共享状态,只能通过 HTTP 等网络请求进行通信;BEAM 进程也是两个完全独立的程序,通过消息交流。但最大的颠覆在于:BEAM 内部没有网络开销,没有复杂的 HTTP 协议封装,不需要反反复复的对象序列化与反序列化,这种隔离机制直接由语言底层和运行时本身提供原生支持。当现场有听众提问如何定位目标时,讲者简要回应,每个进程都有唯一的进程标识符(Process ID / PID),可以据此进行寻址与引用。 --- ### [06:00](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=360s) 运行时底层调度机制与万级并发 Demo 启动 解释完概念之后,讲者进一步揭开 BEAM 在底层的运行真相。当开发者在 Elixir 中写完系统源码并输入一条启动命令时,操作系统层面实际上只启动了一个单一的操作系统进程(Single OS Process),即运行了一个 Erlang 虚拟机(BEAM)实例。整个庞大的分布式或服务端系统,在底层就驻留在这个单一 OS 进程内。 在这个 OS 进程中,开发者可以随心所欲地利用代码 `spawn` 产生海量的轻量级进程。这些进程启动速度极快,内存开销微乎其微。开发者完全不需要节制,可以在系统内部轻松运行数千、数十万甚至数百万个进程;BEAM 虚拟机自身施加的技术上限甚至高达约 1.3 亿个进程。 在虚拟机的多核利用与调度方面,BEAM 的内部设计非常精巧: - 虚拟机在底层会启用若干个名为调度器(Schedulers)的操作系统线程。 - 默认情况下,BEAM 会严格按照硬件环境,为每个可用的 CPU 核心绑定分配一个专属的调度器线程。 - 调度器负责具体执行这些轻量级进程的代码。一个通俗但不失本质的理解模型是:假设后台存在一个等待执行的进程队列,每个调度器从队列头部取出一个进程,让其运行一段极微小的时间片,随后将其放回队列尾部,接着处理下一个进程,如此循环往复。 为了向听众展示这种并发并非空中楼阁,讲者切入准备好的实战 Demo。他提前在本地机器上将一个用 Elixir 开发的小型演示系统作为后台服务(Headless Daemon)启动。他强调,这个服务的启动配置方式与真实生产环境完全一致,它是一个完全独立的单一 BEAM OS 进程,没有任何外部辅助支撑组件。 随后,讲者打开系统内置的 Web 仪表盘(Dashboard),准备通过手动触发后台任务来模拟合成负载(Synthetic Load)。在底层设计中,系统中的每一个任务(Job)都是一个完全独立的轻量级进程。讲者在控制界面上点击启动按钮,仅仅在一秒钟之内,仪表盘上的活跃进程数直接飙升,10,000 个独立的轻量级并发进程瞬间在后台全速运转起来。 每个进程都在执行一个无限循环(Infinite Loop);在每一次循环中,它们都会执行一小段 CPU 密集型计算来制造真实算力负载,随后向一个预先设定好的知名进程发送通信消息。即使面对瞬间创建的万级并发程序,单一 BEAM 节点依然举重若轻地并行处理着所有任务。 ### [08:45](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=525s) 监控面板与单调度器压力环境 演示系统的指标仪表板上正在实时展示系统的运行状态。系统内部设计了一个单例且全局寻址的进程,该进程在每次迭代中标记一次操作成功,随后休眠整整一秒并循环往复;同时有若干辅助进程负责收集指标数据,并通过 WebSocket 推送给任何打开此页面的用户。 左侧图表显示了每秒成功操作数(successes per second),当前维持在预期的 10,000 次/秒(10K/s)左右。图表线条并非完全平直,而是伴随着轻微振荡。产生波动的原因有两个:一是内部逻辑并未严格同步;二是演示所用的单台机器配置相对普通,除了承载服务端系统外,浏览器还在高频利用 SVG 渲染动态图表,从而占用了一部分计算资源。 该系统被刻意配置为仅运行一个调度器线程(Scheduler Thread)。在当前的四核宿主机上,BEAM 默认会启动四个调度器,但为了使演示效果更加直观极端,演讲者将其硬编码限制为单个调度器线程。这意味着所有进程都在模拟单核 CPU 环境下的并发竞争。右侧负载图表显示,单调度器在 10K/s 负载下的 CPU 占用率仅在 20% 左右,系统尚有充足的余量。 - **基准吞吐量**:维持在约 10,000 次成功操作/秒(10K successes per second)。 - **调度器配置**:默认可利用宿主机 4 个核心,被强制限定为 1 个调度器线程,以模拟单核环境。 - **基准负载占用**:在维持 10K/s 吞吐的情况下,单调度器的 CPU 占用率仅约 20%。 --- ### [10:24](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=624s) 页面交互模型与双层进程架构 在合成用户(Synthetic Users)以 10K/s 频率产生背景流量的同时,演讲者打开了一个供真实用户访问的 Web 界面。这个界面极为朴素,却概括了所有 Web 系统的核心本质:用户输入数据,系统消化计算,再将结果输出。该页面的具体功能是输入一个正整数 $n$,系统计算前 $n$ 个自然数的累加和(例如输入 3 计算 $1+2+3=6$,输入 4 计算 $1+2+3+4=10$)。 支撑该功能的底层进程拓扑具有精细的分工结构。浏览器与后台建立长连接 WebSocket,所有用户交互均通过该通道进行。每条活跃的 WebSocket 连接均对应一个由系统动态派生的连接进程(Connection Process)。如果同时有 3 位用户在线,系统内就存在 3 个连接进程;若有 100 万用户在线,系统便生成 100 万个连接进程;若无人在席,数量归零。 计算任务并没有直接放在连接进程内执行。每当用户提交请求,连接进程都会单独派生一个专职的、一次性的“计算进程”(Calculation Process / Task)。计算进程完成运算后,将结果异步发送回连接进程,连接进程再推送到浏览器渲染,而计算进程随即正常退出并销毁。 ```mermaid flowchart TD accTitle: 用户交互与进程生命周期架构 accDescr: 展示浏览器通过 WebSocket 连接到独立的连接进程,连接进程动态派生一次性计算进程并回收结果的流程。 subgraph BrowserClient["客户端浏览器"] UI["用户界面 (输入 / 结果渲染)"] end subgraph BeamRuntime["BEAM 运行时"] ConnProc["连接进程 (Connection Process)<br/>随 WebSocket 长期存活"] CalcProc["计算进程 (Calculation Process / Task)<br/>一次性、算完即销毁"] end UI -->|"1. WebSocket 提交自然数 N"| ConnProc ConnProc -->|"2. 动态派生 (Spawn)"| CalcProc CalcProc -->|"3. 累加计算并回传消息"| ConnProc ConnProc -->|"4. 推送计算结果"| UI CalcProc -.->|"5. 正常退出终止"| Terminate(["销毁回收"]) ``` --- ### [12:40](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=760s) 故障隔离:单进程崩溃不波及系统 之所以采取连接进程与计算进程分离的设计,最根本的目标是提高系统的容错能力(Fault Tolerance)与高可用性(High Availability)。真实生产环境中总会隐藏各样错误,演讲者演示了最常见的一类缺陷:业务逻辑未能覆盖边缘分支,导致服务端抛出未捕获的运行时异常(如被零除、负数求平方根或“undefined is not a function”)。 在此演示中,数字 13 被预设为会触发崩溃的异常输入。当用户提交 13 时,前端界面提示了错误信息。通过查看后台以独立服务运行的系统日志,可以观察到明确的崩溃记录:一个名为 `Task` 的组件异常终止。这意味着专职负责该次求和的“计算进程”已经崩溃挂掉,但整个系统的运行并没有中断。 Saša Jurić 强调了 BEAM 错误隔离机制的核心特质: > “Now an important thing about BEAM is that when a process crashes, everything else progresses, right? So most of the system still works.” 即使连续不断地提交 13 触发异常,控制台频繁抛出错误日志,左侧监控大盘上的背景吞吐量仍然稳稳保持在 10K/s。同时,进程的崩溃绝非悄无声息。BEAM 允许其他进程监听或监控其生命周期;计算进程崩溃时,连接进程收到了一条结构化退出消息(指出“进程 Foo 因原因 Bar 死亡”),因而连接进程得以在保持 WebSocket 不断开的前提下,将友好的错误提示推送回客户端。 - **崩溃现象**:计算进程在遭遇未处理异常时直接挂掉(Task termination),由日志捕获错误堆栈。 - **稳定性保障**:后台 10,000 次/秒的操作未受波及,系统其他用户的并发事务正常推进。 - **消息感知机制**:连接进程通过进程间消息感知下游崩溃,从而向 UI 正确反馈错误信息,而不会导致网络连接掉线。 --- ### [15:36](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=936s) 延迟隔离与抢占式调度的公平机制 进程不仅能够隔离错误,更能“隔离延迟”(Separate Latencies)。为了验证这一点,演讲者展示了系统内的另一个缺陷:求和计算采用了一种极其低效、纯 CPU 绑定的暴力循环实现(从 1 循环累加至 $n$),没有任何公式化优化。 当输入一个巨大的数值(9 个 9:`999,999,999`)时,计算进程陷入了长达约 10 秒的纯 CPU 密集型循环。监控面板显示,单核调度器的 CPU 使用率瞬间被打满到 100%。在常规的单线程协作式系统或事件循环系统中,这类耗时任务必然会阻塞整个主线程,导致背景任务与网络 I/O 陷入停滞。然而,在该单调度器系统中,背景的 10K/s 吞吐量曲线依旧保持平稳,没有发生断崖式下跌。 这得益于 BEAM 调度器具备微秒级的真正抢占式调度能力(Preemption)。无论进程在执行 I/O 操作还是密集的 CPU 计算,调度器都会在极小的时间片(通常小于 1 毫秒)内强制执行上下文切换。关于这种设计取舍,Saša Jurić 总结道: > “BEAM essentially favors a fair distribution of CPU time for every single activity in the system, or in other words, it promotes the progress of the system as a whole at the expense of the maximum efficiency of any single activity in the system.” BEAM 不追求让单个计算任务以极限速度跑完,而是将全系统的整体向前推进置于首位。这种牺牲单任务极致性能来换取全系统响应度与公平性的调度模型,精确切中了并发多用户 Web 系统的运行本质。 - **暴力计算规模**:输入数值为 9 个 9($999,999,999$),造成约 10 秒的纯 CPU 循环消耗。 - **调度开销与时间片**:调度器在上百万个 Reductions(指令消耗计数)基准下切分时间片,上下文切换间隔小于 1 毫秒(< 1 ms)。 - **抢占特质**:实现真正的抢占式多任务,CPU 密集型进程无法独占执行资源,避免了由单点繁重计算引发的系统级高延迟。 ### [17:39](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=1059s) 高可用性的本质:在故障与重算中保证系统前行 Saša Jurić 强调,把系统拆分成独立进程并将故障隔离与延迟隔离绑定在一起,是构建高可用架构的核心所在。他在演示中作为单一用户输入了高 CPU 消耗的计算任务(输入 `456789`),随后展示出连接依然可用,甚至可以继续输入其他数据并制造一批错误。令人惊叹的是,整个大盘依然保持着对其他用户的稳定推进,当前受测用户自身也保持着稳定响应,而后台那个漫长的密集计算任务最终也顺利完成。这种即使生产环境发生各种局部问题、依然能够牢牢掌控局面的表现,正是健壮系统的典范。Saša 形象地比喻道:尽管船体被凿穿了几个洞,但整艘船依然平稳航行,没有沉没,并且继续为绝大多数用户提供服务。 Saša Jurić 深刻地指出:“This in my mind is what high availability is about it's not about chasing some mythical amount of nines of uptime it's about having a system which is there for its users which is ultimately its primary if not the only purpose.”(在我看来,这就是高可用性的真正意义所在:它不是去盲目追逐某种虚无缥缈的‘几个9’的正常运行时间指标,而是要让系统始终能够陪伴并服务它的用户——这终究是系统最核心甚至是唯一的目的。) ```mermaid flowchart TD accTitle: BeamProcessIsolationArchitecture accDescr: 架构图展示了一个重度计算任务或故障请求如何被限制在独立子进程中,而不影响全局调度器和其他用户的处理。 subgraph UserSpace["用户交互层"] U1["当前测试用户"] U2["其他系统用户 (10k 请求/秒)"] end subgraph BeamRuntime["BEAM 运行时环境"] ConnProc["当前用户连接进程 (PID)"] CalcProc["重度计算 / 故障计算进程"] OtherProc["成千上万个轻量进程"] end U1 -->|"发送复杂计算 / 触发错误"| ConnProc ConnProc -->|"隔离派发计算"| CalcProc U2 -->|"常规业务交互"| OtherProc OtherProc -->|"正常稳定处理"| BeamRuntime ``` --- ### [18:44](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=1124s) 恶性隐患:负数输入引发的失控死循环 即便系统具备高可用弹性,线上遗留的隐蔽缺陷若不排除,依然可能威胁到整个系统的存亡。Saša 现场输入了一个负数 `-1`,展示了这类极具杀伤力的隐患。系统在业务逻辑上本就不应接受这种非法的值域输入,但一旦接收并进入计算,由于代码缺陷,它迟迟无法返回结果。观察监控调度器可以看到,CPU 使用率瞬间被持续拉满到 100%。这个操作直接演变成了一个失控的(rogue)CPU 密集型死循环,相当于执行了无休止的 `while(true);`。 得益于 BEAM 进程调度的优越性,系统依然展现出强大的韧性:在单线程调度器跑满的情况下,系统面向其他用户的吞吐依然保持在每秒 10,000 次成功的基准线。然而这只是一时的侥幸。如果用户因没有收到结果而反复重试,或者其他用户也踩中同样的负数边界条件,系统最终会被这些空转的死循环拖垮,彻底陷入瘫痪。因此,运维人员必须具备在系统完全崩溃前快速定位并排查此类隐形炸弹的能力。 --- ### [20:11](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=1211s) 穿透黑盒:利用 BEAM 运行时内省与可观测性快速诊断 为了还原真实的排错场景,Saša 戴上“运维人员的帽子”,假定自己并不知道刚才发生的操作,只是收到了 CPU 飙升至 100% 的线上告警。他通过 SSH 登录到服务器查看日志,但日志一片死寂,只有之前测试数字 13 时的旧错误记录,对于眼前这个无任何输出的无限循环完全没有帮助。 此时 BEAM 的核心优势显露无遗:它是一个高度具备可调试性(debuggable)、内省性(inspectable)和可观测性(observable)的运行时系统。开发者无需在启动时预设特殊调试标志,也无需重启系统,就能直接接入正在生产运行的节点进行动态探查。Saša 执行了远程控制台命令,打开了一个连接到线上生产环境的 IEx(Interactive Elixir)Shell 会话。在这个仅有一个调度线程(单核运行)、负载跑满且并发处理着每秒 10,000 次请求的系统里,运维人员执行的代码依然被平稳调度。 - **获取全局进程**:通过调用 `Process.list()`,列出当前系统中所有轻量进程的 PID(Process ID)。 - **进程指标内省**:通过 `Process.info(pid)` 查询特定进程属性,其中最为核心的指标是 `reductions`(规约数)。 - **Reductions 原理**:这是一个非负整数,大致等同于进程自诞生以来执行的虚拟机指令数量。要找出最消耗 CPU 的“罪魁祸首”,只需计算两个时间点之间各进程 reduction 计数的增量差值,再倒序排列即可获得类似操作系统 `top` 的视图。 - **定位元凶**:Saša 运行了预先封装的工具函数 `runtime.top()`,立刻获取到过去一秒内 CPU 占用最高的前十个进程。排名第一的进程(PID 如 `0.12605.0`)占用了绝大部分 CPU;再次运行验证,该 PID 依然稳居榜首,确凿无疑为消耗资源的失控进程。 --- ### [23:42](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=1422s) 运行时取证与一级取消机制:`Process.exit(pid, :kill)` 的威力 锁定恶意进程后,即使它是一个陷入死循环、不让出时间片、不响应任何消息的脱缰程序,BEAM 运行时仍能从外部强行读取其状态。Saša 将该异常进程的 PID 存入变量,调用 `Process.info(pid,:current_stacktrace)` 直接拉取其当前的调用堆栈轨迹,随后又运行了辅助函数 `runtime.trace()`,在最多 1 秒或 50 次调用的限制下,动态捕获该进程正在执行的函数调用轨迹。 取证完成后,Saša 直接调用标准库中的终止函数对该死循环进程进行清理: ```elixir Process.exit(pid,:kill) ``` 指令刚一敲下,仪表盘上的 CPU 调度使用率瞬间断崖式下跌,恢复至正常水准,而整个系统每秒 10,000 次的业务处理丝毫不受干扰。与此同时,在前台用户界面上,用户立刻收到了计算失败的报错通知。这是因为虽然执行计算的子进程被底层的 `:kill` 强行处决(类似 BEAM 级别的 `kill -9`,无法被代码内的 `try-catch` 拦截),但负责网络连接的进程依然活着,它接收到了子进程夭折的退出信号,从而能够体面地向客户端派发异常提示。 Saša 评价道:“This process exiting it's like a seemingly simple thing but one which is as far as I know exclusive to BEAM in terms of guarantees this is essentially a first-class cancellation implemented at the runtime layer.”(这种进程退出看似是一件微不足道的小事,但就其所能提供的保障而言,据我所知是 BEAM 所独有的——这本质上是运行时底层所实现的一等公民级别的取消机制。)无论是通过交互式终端介入,还是在代码中使用高阶抽象实现超时取消或用户主动取消,这种由底层保障的强力取消机制都只需要极少的代码就能发挥极其惊人的威力。随后,他将注意力转回刚才抓取的栈帧与模块轨迹数据,展开具体的代码级根因分析。 ### [26:29](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=1589s) 深入崩溃现场与热代码修复 ```mermaid flowchart TD accTitle: 热代码修复流程图 accDescr: 展示从系统边界拦截错误、修复算法问题到热升级生产环境的完整流程。 subgraph Edge ["系统边界 (边界拦截)"] A["客户端请求"] --> B["输入校验 (系统边缘)"] B -->|"非法输入 (-1, 0, 123)"| C["直接丢弃并返回 invalid input"] end subgraph Core ["业务核心层 (问题修复)"] B -->|"正整数输入"| D["高斯求和算法 n*(n+1)/2"] D --> E["解决数字 13 的边界问题"] E --> F["单元测试全部通过"] end subgraph Deploy ["热部署到生产环境"] F --> G["执行自动化任务编译模块"] G --> H["无缝热替换 BEAM 代码"] H --> I["生产环境吞吐保持 10k req/s (零停机)"] end ``` 通过堆栈追踪(Stack Trace)定位到具体的文件和行号后,Saša Jurić 开始结合运行时的动态视图剖析故障原因。该进程陷入了无尽的递归调用(在函数式编程中,恒等递归即等同于死循环)。通过逐帧观察循环参数的变化,他发现第一个参数每次递增 1,第二个参数每次递减 1,而第三个参数则按一定规律持续攀升。代码深处的这个函数默认输入必须是正整数,一旦传入负数或不满足约定的数据,就会引发无限递归。 在业务领域模型中,这部分代码处于系统较深的位置,假设处理的都是自然数并没有逻辑错误。真正的问题在于系统的边缘——即接收用户输入的地方——缺乏足够严格的校验,导致无效数据层层穿透进入核心。为了解决这些累积的问题,Saša 现场修复了代码:在边缘取消了防御性校验的注释以拦截负数;修复了导致数字 13 报错的边界缺陷;并运用高中数学的高斯求和公式(`n * (n + 1) / 2`)彻底取代原先效率低下的求和实现。 在所有本地自动化测试验证通过后,Saša 运行了一个专用的热部署脚本。该脚本重新编译了两个模块,并在不重启生产进程的前提下,将新代码动态加载到运行中的系统。监控仪表盘上的指标表明,系统在代码加载期间未受丝毫干扰,依然以每秒 10,000 次成功的稳定速率处理流量。在前端页面无需刷新的情况下,负数和零被即时判定为非法输入,数字 13 顺利得出结果,而一长串极大数字的求和也在瞬间完成。 > “Essentially what happened here: we had a system with problems, and it was staying on top of the game, and it allowed me to approach it, the system, and look from inside it to figure out what the problems are, but I was able to quickly fix those problems and deployed into production without disturbing anything in the system itself, right, the other activities.” — Saša Jurić - **诊断信息提取**:从单条崩溃进程的状态不仅能查明所在函数与行号,还能还原每一次递归迭代所接收的具体参数值。 - **架构原则**:边缘负责严格的数据过滤,内部业务领域则专注合法实体的核心逻辑。 - **零停机动态加载**:BEAM 允许在单机运行时中并行存在旧版本与新版本的代码模块,实现平滑过渡且不中断正在处理的网络连接与计算任务。 --- ### [29:51](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=1791s) BEAM 集群与分布式原生并发 BEAM 不仅在单机多核上表现优异,其底层对分布式系统也具备开箱即用的支持。开发者可以在单台机器或跨独立物理机/虚拟机启动多个 BEAM 节点(Node)。仅需调用一个简单的原生函数,即可在两个节点之间建立底层的 TCP 连接,从而组成一个原生的 BEAM 集群。 在形成集群后,Erlang 和 Elixir 为开发者提供了极具吸引力的编程能力:开发者可以枚举集群内的全部节点、动态监听节点的加入(Join)与脱离(Leave)事件,并且可以在任意指定节点上生成进程,甚至完全跨节点透明地发送与接收消息。在 BEAM 的哲学体系中,单机并发与分布式在语义和抽象上是等同的,开发者编写分布式通信的代码与在单机内向某个进程收发消息几乎毫无区别。 Saša 通过一个服务注册仪表盘演示了分布式特性的工作方式。在界面上启动一个名为 `node2` 的前台节点后,集群立即感应到了新成员。当在 UI 中创建全局命名的服务(在 BEAM 中本质上就是一个被赋予集群级名称的独立进程)`foo` 时,系统自动在某一个节点上实例化了该进程。点击查询时,客户端向集群发起消息调用,该进程捕获自身的状态、获取所宿留的节点名称并将数据打包响应。接着创建的服务 `bar` 则被调度到另一个节点上运行,展示了简单的集群负载均衡。 为了展示机器宕机时的容错能力,Saša 直接在终端终止了 `node2` 的运行。仪表盘上的视图随之改变:原本存活在 `node2` 上的 `bar` 进程被系统感知丢失,并立刻在依然存活的 `node1` 上复活,再次向该服务发送请求时,响应的提供方已自动切换为 `node1`。 > “Because in fact in BEAM, concurrency and distribution are one and the same thing.” — Saša Jurić - **节点互联机制**:多个虚拟机实例之间通过单条长连接构成全互联网络拓扑(Full Mesh)。 - **进程位置透明性**:向进程发送消息或注册名称不需要硬编码目标 IP 或端口,底层分发由虚拟机和集群协议自动路由。 - **高可用故障转移**:当某个 BEAM 节点宕机断网时,其余节点会迅速触发事件钩子,支持关键状态自动重构与恢复。 --- ### [32:34](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=1954s) 现实权衡与基于 Swarm 的服务编排 尽管分布式特性在演示中看似无缝且强大,Saša 对 BEAM 的原生分布式机制给出了极其坦诚的客观评价。他指出,分布式 BEAM 在工程实践中伴随着一系列非常严重的现实缺陷,从最底层的运行时协议(如全互联网络带来的节点数量扩展上限,以及对网络分区的脆弱性)一直延伸到上层的抽象库。因此在工业界,很多人对其在生产环境的采用持保留态度。 然而,Saša 特别强调,这些问题在本质上是“机械实现层面”的缺陷(Mechanical issues),而不是概念模型或顶层 API 设计的失败。这意味着底层的通信协议、连接策略和状态同步机制完全可以在未来被持续重写与改良,而无需颠覆现有的接口契约。这种高层概念的优雅性正是其蕴含巨大潜力的关键。 在代码架构层面,让一个本地并发模块升级为支持全局高可用的分布式服务极其简洁。核心业务模块 `local` 仅仅是标准的 Elixir 并发代码,对分布式机制完全解耦且一无所知。赋予其分布式能力的关键在于项目配置文件 `mix.exs` 中引入的高阶分布式编排库——**Swarm**。在 BEAM 生态中,引入第三方依赖不仅仅是导入一组静态函数库,更意味着直接将一整套自主运转的后台进程组引入到应用系统内部。这一行依赖声明,就在系统启动时拉起了一组负责监听节点拓扑变动、维护全局名称注册表以及管理生命周期的常驻进程。 > “While many people do use BEAM distribution, distributed BEAM in production, there are also many people who would tell you like maybe not use that thing... but I would also say that these issues are purely mechanical, which means that they can be fixed in the implementation without significantly changing or without changing at all the interface, the contract, the way we use this thing.” — Saša Jurić - **依赖项的双重属性**:在常见语言中,依赖库仅提供供调用的代码逻辑;在 BEAM 体系下,依赖项自身可以携带常驻的并发进程(Activities / Processes)嵌入到系统的运行拓扑中。 - **Swarm 编排器职责**:负责管理分布式注册表,监听节点的在线与离线事件,并自动实现进程在存活节点之间的漂移与状态重构。 - **内聚性优势**:不再依赖外部笨重的外部协调服务(如独立部署的 ZooKeeper 或 Consul 代理),协调机制直接以内置参与者的形式运行在宿主语言的运行时内部。 ### [35:13](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=2113s) 三行代码的分布式:基于 Swarm 的服务注册与位置透明性 Saša Jurić 展示了如何通过极其简洁的方式构建分布式系统。在启动了名为 Swarm 的库之后,在集群中的任意位置启动一个具名服务(named service)仅仅退化为调用一个特定的 Swarm 函数。该函数会自动在集群中挑选一个节点启动对应进程,并在内部维护给定名称与该进程之间的映射关系。当需要查找并调用该服务时,只需调用另一个 Swarm 函数并传入名称,它就会返回一个 PID(进程标识符)。 在 BEAM 虚拟机中,PID 是一个具备位置透明性(location transparency)的数据类型。无论目标进程位于本地节点还是远程集群节点,绝大多数操作 PID 的函数表现完全一致,开发者根本无需关心物理拓扑。由于底层基础设施透明地抹平了网络差异,开发者直接将拿到的 PID 传给普通的 BEAM 函数即可直接通信。Saša 强调,整套分布式能力的引入仅仅花费了三行代码,侧边没有任何额外运行的操作系统进程、容器、虚拟机或第三方服务;即便在完全断开外网的环境下,这套演示依然顺畅运行。更进一步,系统的用户界面也是完全由 Elixir 代码驱动的,甚至没有手写 JavaScript,整套系统在技术上表现出高度的一致与内聚。 ```mermaid flowchart TD accTitle: Swarm 分布式服务注册与位置透明调用流程 accDescr: 展示客户端通过 Swarm 注册并解析服务 PID,进而透明调用本地或远程进程的过程。 subgraph Cluster["分布式 BEAM 集群"] Client["客户端 Elixir 代码"] Swarm["Swarm 分布式注册表"] subgraph NodeA["节点 A (本地)"] P1["服务进程 A"] end subgraph NodeB["节点 B (远程)"] P2["服务进程 B"] end end Client -->|"1. swarm_register(name)"| Swarm Swarm -->|"2. 调度并启动"| P2 Client -->|"3. swarm_whereis(name)"| Swarm Swarm -->|"4. 返回统一 PID"| Client Client -->|"5. 透明消息发送 / 调用"| P2 ``` - 在 BEAM 集群中,PID 是全局通用的“位置透明”标识符,本地进程与跨节点远程进程的调用语法完全相同。 - 实现具备容错与服务发现的分布式架构仅需大约三行 Elixir 代码,无需依赖外部编排系统或代理。 ### [36:30](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=2190s) 技术单一性(Technical Uniformity):系统 A 与系统 B 的真实对比 Saša 引用了其著作《Elixir in Action》中的一张真实项目对比表,回顾了他在 2010 年代初并行参与维护的两套系统。系统 A 最初选型的核心技术对现代软件系统天然伴随的高并发、高可用挑战缺乏底层支持。面对这些挑战,团队不得不将控制权退让给操作系统层面:他们不得不运行大量的 OS 进程,在需要更好并发支持的地方引入其他编程语言,并采购大量的第三方中间件产品。虽然业务需求最终得以勉强满足,但解决方案的技术复杂度与技术门槛高得惊人,新入职的初级工程师需要消化海量的概念才能有信心动手改动代码。 与之形成鲜明对比的是系统 B。该系统基于运行在 BEAM 之上的 Erlang 实现。BEAM 常被称为“专为代码打造的操作系统”,因为运行时的语言接口本身就直接提供了类似操作系统的能力(如进程隔离、并发调度、故障恢复)。因此,团队完全不需要下沉到宿主 OS 层面去缝合零件,而是用纯粹的 Erlang 在同一个代码库内搞定了一切,在运行时每个机器节点只需运行单个 OS 进程。尽管自研的 Erlang 方案无法在所有花哨功能或极端峰值性能上百分之百匹敌成熟的商业第三方软件,但它完全足以满足业务需求,并换来了无价的“技术单一性”(Technical Uniformity)。 > “It is in fact sometimes said that BEAM is an operating system for our code, and if I have the operating system directly in my runtime and I have the language as the main interface, then I don't need to fall back to the OS level.” —— Saša Jurić | 架构维度 | 系统 A(传统拼凑架构) | 系统 B(Erlang / BEAM 统一架构) | |:--- |:--- |:--- | | **基础运行环境** | 语言本身缺乏高可用支持,下沉依赖操作系统及外部服务 | BEAM 自带进程调度与故障隔离(“针对代码的操作系统”) | | **技术栈构成** | 多语言混杂、大量第三方中间件产品与 OS 进程 | 单一语言(Erlang)、单项目工程、每机单 OS 进程 | | **技术复杂度与门槛** | 极高;新手需理解多种中间件协议与不同抽象模型 | 极低;只需掌握一套语言概念即可参与全栈开发 | | **运维与交付负担** | 需编排 5-10 个不同组件,监控与日志高度碎片化 | 统一接入点,调试、度量指标与日志采集高度集约 | ### [38:10](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=2290s) 降本增效:告别“胶水代码”与细分专家壁垒 Saša 阐明,追求技术单一性并不是为了单一而单一,而是因为它能在整个软件工程生命周期中释放出巨大的工程红利。如果整套系统由单一技术(例如 Elixir)实现,开发团队在日常工作中的摩擦阻力将显著降低:从本地搭建开发环境、编写单元测试,到配置持续集成(CI)流水线以及生产部署,管理一个纯粹的单一系统远比同时协调编排 5 到 10 个独立运转的组件要轻松得多。在生产环境中,团队只需监控一个进程,日志收集、指标埋点和交互式调试都有唯一且强大的切入点。 更为关键的是团队协作维度的质变。当技术门槛被大幅拉低,团队不再需要人为划分狭隘的专业技术壁垒。团队不再被迫聘用专门的 Redis 专家、Nginx 专家、ZooKeeper 专家、Kafka 专家或 Kubernetes 专家;新员工入职后只需学习 Elixir,便能立刻对系统整体做出实质性贡献。此外,工程师们也能从充斥着 YAML、INI、JSON、TOML、XML 等非图灵完备且工具链粗糙的 Ad-hoc DSL 地狱中解脱出来。整个软件系统完全可以使用一门图灵完备、兼具编译期与运行期灵活性、能无缝对接 OS 底层能力的通用语言进行端到端描述。 > “And so we don't need to have all these narrow specialists — we don't need to have like a Redis specialist, Nginx specialist, HAProxy specialist, ZooKeeper specialist, Kafka specialist, Kubernetes specialist, and so on and so forth. And we don't need to wrestle with all these YAMLs and INIs and JSONs and TOMLs and XMLs and other kind of ad hoc DSLs and their quite substandard tooling.” —— Saša Jurić ### [40:00](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=2400s) BEAM 的终极潜力:兼顾“快速启动”与“长远扩展”的双赢 Saša 展望了 BEAM 语言尚未被业界完全挖掘的真正潜力(他坦言目前社区的发展或许只走到了一半)。他描绘了一个理想的工程场景:当开发者需要构建一个面向 Web 的中小型 CRUD 系统时,只需新建一个 Elixir 工程,引入少量依赖项,5 分钟之内便能一应俱全——直接在同一个项目中获得开箱即用的分布式能力、带复制的高可用 SQL 数据持久化能力,没有任何多余的外部依赖,这构成了名副其实的“极简起步”(Simple Start)。 这种极简起步之所以不同于常规的“玩具框架”,是因为它的基石是一门成熟的图灵完备语言,其底层承载的是拥有三十余年工业级洗礼与海量各色项目验证的 BEAM 虚拟机。这意味着团队在业务规模急剧扩大、面临极端高并发与分布式复杂挑战时,完全有底气在现有架构上持续演进,而根本不需要经历“推倒重写”的阵痛。以往软件工程界认为“快速原型开发选型”与“支撑超大规模长远系统选型”不可兼得的人工假象被彻底打破。Saša 总结道,BEAM 正是这种融合能力的根基,它当之无愧是 Erlang 与 Elixir 的“灵魂”。 在演讲结尾,Saša 为现场听众提供了其著作《Elixir in Action》的专享购书优惠代码(当天享有 5 折,后续提供 4 折优惠),并宣布向现场观众赠送两本实体书以及若干电子书名额。他风趣地提醒大家演讲结束后可直接上台索取,打趣道“你们甚至不用假装对其他话题感兴趣”,并在引导大家对本次分享打分反馈后圆满结束了整场演讲。 > “The artificial trade-off of having to choose between a quick start technology and the long run one disappears, flies out the window, and I get both in the same piece of technology. And this is all in my opinion possible... owing to BEAM, this wonderful runtime which is in my view indeed the soul of Erlang and Elixir.” —— Saša Jurić - **BEAM 的双重杀手锏**:消除了“易于敏捷起步的轻量技术”与“支撑海量规模的高可靠技术”之间的割裂对立。 - **书籍福利**:Saša 在现场提供了《Elixir in Action》的限时 50% / 40% 折扣代码,并免费派送了两本纸质版及多份电子版书籍。 ## Notable quotes > [17:39](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=1059s) This in my mind is what high availability is about it's not about chasing some mythical amount of nines of uptime it's about having a system which is there for its users which is ultimately its primary if not the only purpose. — Saša Jurić > [29:51](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=1791s) Because in fact in BEAM, concurrency and distribution are one and the same thing. — Saša Jurić > [15:36](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=936s) BEAM essentially favors a fair distribution of CPU time for every single activity in the system, or in other words, it promotes the progress of the system as a whole at the expense of the maximum efficiency of any single activity in the system. — Saša Jurić > [40:00](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=2400s) The artificial trade-off of having to choose between a quick start technology and the long run one disappears, flies out the window, and I get both in the same piece of technology. — Saša Jurić > [36:30](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=2190s) It is in fact sometimes said that BEAM is an operating system for our code, and if I have the operating system directly in my runtime and I have the language as the main interface, then I don't need to fall back to the OS level. — Saša Jurić > [12:40](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=760s) Now an important thing about BEAM is that when a process crashes, everything else progresses, right? So most of the system still works. — Saša Jurić > [38:10](https://www.youtube.com/watch?v=JvBT4XBdoUE&t=2290s) And so we don't need to have all these narrow specialists — we don't need to have like a Redis specialist, Nginx specialist, HAProxy specialist, ZooKeeper specialist, Kafka specialist, Kubernetes specialist, and so on and so forth. — Saša Jurić ## Mentioned Saša Jurić, Joe Armstrong, BEAM, Erlang, Elixir, Swarm, Elixir in Action, GOTO Conferences, Redis, Nginx, HAProxy, ZooKeeper, Kafka, Kubernetes ## Video description This presentation was recorded at GOTO Chicago 2019. #gotocon #gotochgo http://gotochgo.com Saša Jurić - Author of "Elixir in Action” @sasajuric5850 ABSTRACT Whether you're developing a small web site or a large-scale distributed system, Erlang and Elixir are great tools for building server-side systems. Both languages can help you start quickly and deal with highly complex problems at a large scale. This talk aims to explain what makes Erlang and Elixir suitable for such tasks. We'll look past the syntax and the ecosystem, focusing instead on the concurrency model of these languages. Combining a bit of high-level theory and a couple of demos, we'll examine some important properties of Erlang/Elixir concurrency, with a special emphasis on why these properties are very relevant when building highly available systems. The talk targets experienced backend developers, but no knowledge of Erlang or Elixir is required. You will walk away from [...] Download slides and read the full abstract here: https://gotochgo.com/2019/sessions/712 RECOMMENDED BOOKS Saša Jurić • Elixir in Action • https://amzn.to/2RZh5eN McCord, Tate & Valim • Programming Phoenix 1.4 • https://amzn.to/3zcUqj4 https://twitter.com/GOTOcon https://www.linkedin.com/company/goto- https://www.instagram.com/goto_con https://www.facebook.com/GOTOConferences #erlang #elixir #programming #DistributedSystems #FunctionalProgramming #SasaJuric CHANNEL MEMBERSHIP BONUS Join this channel to get early access to videos & other perks: https://www.youtube.com/channel/UCs_tLP3AiwYKwdUHpltJPuA/join Looking for a unique learning experience? Attend the next GOTO conference near you! Get your ticket at https://gotopia.tech Sign up for updates and specials at https://gotopia.tech/newsletter SUBSCRIBE TO OUR CHANNEL - new videos posted almost daily. https://www.youtube.com/user/GotoConferences/?sub_confirmation=1 [Source](https://www.youtube.com/watch?v=JvBT4XBdoUE)

Source quotes

[17:39] Saša Jurić: This in my mind is what high availability is about it's not about chasing some mythical amount of nines of uptime it's about having a system which is there for its users which is ultimately its primary if not the only purpose. [29:51] Saša Jurić: Because in fact in BEAM, concurrency and distribution are one and the same thing. [15:36] Saša Jurić: BEAM essentially favors a fair distribution of CPU time for every single activity in the system, or in other words, it promotes the progress of the system as a whole at the expense of the maximum efficiency of any single activity in the system. [40:00] Saša Jurić: The artificial trade-off of having to choose between a quick start technology and the long run one disappears, flies out the window, and I get both in the same piece of technology. [36:30] Saša Jurić: It is in fact sometimes said that BEAM is an operating system for our code, and if I have the operating system directly in my runtime and I have the language as the main interface, then I don't need to fall back to the OS level. [12:40] Saša Jurić: Now an important thing about BEAM is that when a process crashes, everything else progresses, right? So most of the system still works. [38:10] Saša Jurić: And so we don't need to have all these narrow specialists — we don't need to have like a Redis specialist, Nginx specialist, HAProxy specialist, ZooKeeper specialist, Kafka specialist, Kubernetes specialist, and so on and so forth.

Source quotes

[39:58] Saša Jurić: The artificial trade-off of having to choose between a quick start technology and the long-run one disappears, flies out the window, and I get both in the same piece of technology. [36:30] Saša Jurić: It is in fact sometimes said that BEAM is an operating system for our code, and if I have the operating system directly in my runtime and I have the language as the main interface, then I don't need to fall back to the OS level.

Original transcript

[0:00] [Music] [0:14] thank you so hello everyone and thank [0:16] you for joining me in this talk where [0:18] we're going to explore the legacy of Joe [0:20] Armstrong the principal inventor of [0:22] Erlang who unfortunately recently passed [0:25] away and so Joe sadly will not be with [0:27] us anymore [0:28] but his work lives on and this is what [0:30] we're going to explore today basically [0:32] whatever I'm about to show you exists [0:34] first and foremost owing to Joe's [0:35] immense contribution to our field and [0:37] speaking personally for myself I'm [0:40] extremely grateful for that so I've been [0:42] using Erlang an elixir for quite some [0:44] time now and for me personally this is [0:46] my by far my favorite part of my own [0:49] professional career and with this talk I [0:51] basically want to try to spread a little [0:52] bit of this happiness and explain why [0:54] exactly am I so excited about these [0:57] languages and so I'm going to take you [0:58] with me into the quest for the soul of [1:00] our long and Alex here and you're [1:01] getting two languages at the price of [1:03] one and I'm gonna throw in a couple of [1:05] more into the mix and the main reason [1:07] why I can do this is because I'm [1:08] focusing on the common theme between [1:10] these languages and this is the runtime [1:12] layer the Erlang virtual machine which [1:14] is also known under the name beam and so [1:16] despite all the differences that exist [1:18] between these languages I'm going to [1:19] treat them as if they are exactly the [1:20] same as if there are a mere interface [1:22] that we use to talk to beam and harness [1:25] its power and so I'm gonna be very [1:28] sloppy about these names most of the [1:29] time I'm going to say elixir because all [1:31] of the demos and samples code samples [1:33] that you see are written in a list here [1:34] maybe occasionally I'll say Erlang but [1:37] what I really mean to say is any beam [1:38] language because beam is the focus of [1:40] this talk from a user's perspective from [1:42] a standpoint of a developer writing a [1:44] beam powered system so the talk is going [1:46] to consist of three parts I'm gonna kick [1:47] off with a basic introduction to the [1:50] principles and mechanics of beam [1:51] concurrency and this is then followed [1:53] with the central piece which is a demo [1:56] driven exploration of being concurrency [1:58] in practice so we're going to take a [1:59] look at how to split system into [2:00] different concurrent parts and we're [2:02] going to discuss a little bit why do we [2:03] do why do we take these approaches that [2:05] what kind of benefits do we get and then [2:07] finally I'm going to wrap up with a [2:08] short and somewhat controversial think [2:10] piece exploring a different style of [2:12] building software systems a style which [2:14] goes against the grain of what we're [2:15] typically doing today a style which is [2:17] first and foremost made possible when we [2:19] have a runtime such as beam as our [2:20] foundation a style which is in my [2:22] personal opinion simpler yet at the same [2:25] time more powerful and flexible [2:26] compared to however people can be [2:28] building software systems today so I [2:30] have a quite a lot of I have a very [2:32] ambitious plan obviously the time is [2:35] limited so I gotta warn you upfront then [2:36] I'm gonna speak really fast and I [2:38] apologize for this but I do want to [2:39] share a lot of information with you and [2:41] I hope that it's going to be interesting [2:43] for you and that you're going to get a [2:44] lot of food for thought so without [2:46] further ado let's kick off with [2:47] concurrency the way Joe Armstrong [2:49] envisioned it this beam-style [2:50] concurrency and the central idea the [2:52] central concept here is the concept of a [2:54] thing called process and process in beam [2:57] terminology is a runtime execution [2:59] context of code any code like elixir [3:01] code runs in some process at runtime now [3:04] process is not a novice process and it [3:05] is not a novice thread and we're gonna [3:07] clarify this distinction a bit later on [3:08] but essentially what process is is [3:11] basically a sequential program right so [3:13] there is no internal concurrency in the [3:14] process and you have regular things [3:16] happening in there you're invoking some [3:18] functions passing some parameters taking [3:20] return values have some branching logic [3:21] have some loops so on so forth [3:24] basic programming 101 and as long as [3:26] it's a quench all we say that the leaks [3:27] here is functional by some definition of [3:30] functional not a pure functional [3:31] language but it has some functional [3:33] properties and then you have this whole [3:34] other paradigm within the same language [3:36] this concurrent elixir and to become [3:39] concurrent you knew you really need to [3:41] run multiple processes and there are a [3:43] bunch of different ways how you can do [3:44] this but the most fundamental one would [3:46] be this function here from standard [3:48] library called spawn which you invoke to [3:51] start or spawn another process so here [3:54] we have one process one sequential [3:55] program we invoke spawn we pass it a [3:58] lambda and anonymous function and what [4:00] happens is a new process is started or [4:02] spawned and as soon as that process is [4:04] started this process here moves forward [4:06] and now in theory on paper we have two [4:09] processes running in parallel or the way [4:11] I prefer to say it is we have two [4:12] completely independent programs running [4:14] completely separately from each other [4:15] they have nothing in common with each [4:17] other at all so have their own separate [4:19] execution flow they have their own [4:21] separate memory space stack heap they [4:23] are separately garbage collected at [4:25] different points in time and they are [4:26] really completely two separate programs [4:28] and the only way they can actually work [4:30] together cooperate is by sending [4:32] themselves messages which is also known [4:34] as message passing concurrency so again [4:36] a bunch of different ways how you can do [4:38] this in a least here the most [4:40] where would be the function called send [4:42] which you can invoke to send a message [4:44] to another process so somehow you [4:46] address a process the target the [4:47] receiver and send it a message and a [4:50] message is an arbitrary piece of data [4:51] whichever term you can construct in [4:52] elixir and then on the receiver side you [4:55] instruct the process to pull one message [4:57] from its mailbox which is sort of like a [4:59] FIFO queue and then you do something [5:00] with this message so if it helps you can [5:02] think of this as being similar to the [5:04] idea of microservices because there you [5:06] have a similar kind of approach to [5:07] micro-services are two completely [5:09] independent programs like typical to [5:11] always processes they have nothing in [5:12] common and they therefore the only way [5:14] they can work together is via some form [5:16] of message passing like using HTTP [5:18] requests for example and we have a [5:21] question or it's a complex topic in [5:30] itself you're gonna see a little bit [5:31] more examples later but there is of [5:32] course a idea of a process ID you can [5:35] somehow reference a process ok so right [5:39] with microservices you have the similar [5:40] idea you have two independent programs [5:42] then you use some message passing to [5:44] communicate with them like HTTP requests [5:46] here you have a similar idea except of [5:49] course there's no networking involved [5:50] there's no HTTP protocol no [5:52] serialization deserialization and the [5:55] mechanics the mechanism is supported [5:57] directly by the language and in fact [5:59] it's supported directly by the runtime [6:00] and so let me briefly explain what [6:02] happens at runtime so let's say that you [6:05] have implemented the system in a Lix [6:06] here somehow you're going to have a [6:07] project where a project is basically a [6:09] folder structure of a bunch of source [6:11] files and then you somehow started you [6:14] invoke a single command to start that [6:16] thing what happens is a single operating [6:18] system process is started just one ôs [6:20] process your own instance of being the [6:22] ER language whole machine and this is [6:24] where the entire system is running in a [6:26] single OS process in there you're going [6:27] to have a bunch of these small [6:29] sequential independent lightweight [6:31] programs this processes these things [6:32] that you spawn on your own from within [6:34] your code whenever you want to and [6:36] you're going to have a bunch of these [6:37] things running like think in terms of [6:39] thousands hundreds of thousands millions [6:41] like the maximum limit imposed by beam [6:43] being around 130 million of these [6:46] processors so these things are quite [6:48] cheap in terms of memory overhead and [6:50] startup time and we really run a lot of [6:52] them now internally what happens [6:54] beam employs a couple of our threads [6:56] called schedulers by default we're going [6:57] to have one scheduler per available CPU [6:59] core and then the schedulers are [7:01] responsible for executing these [7:03] processors so a naive version how you [7:06] can think of it is imagine that there is [7:07] a single view of processors pending for [7:10] execution and then each scheduler [7:12] somehow pulls one process from the queue [7:14] runs it for a little while [7:15] puts it back to the end of the queue [7:17] takes the next one rinse and repeat the [7:19] real deal is of course way more [7:20] sophisticated but this simple [7:22] approximation is going to work for our [7:23] needs and that's it it's you know in my [7:27] opinion not really rocket science it's [7:29] actually incredibly simple in its nature [7:32] but now it begs the question you know [7:33] like what do we do with this how do we [7:35] use this thing how do we split things [7:37] into processors and to what purpose and [7:38] to demonstrate this I have prepared a [7:41] series of demos so for that purpose I [7:45] have written a small example demo system [7:46] in Aleks here which is already started [7:48] on this machine as a background service [7:50] as a headless demon if you will running [7:52] or started pretty much in exactly the [7:54] same way as I would have started it in [7:56] real production so it's a single beam [7:57] instance it's a single lowest process [7:59] and everything that you're going to see [8:00] is powered by that single lowest process [8:03] nothing on the side existing which [8:04] supports that system so the first thing [8:07] I'm going to show you is a sort of an [8:10] internal dashboard where I can generate [8:12] some synthetic load I can start up some [8:14] jobs and each job is a process that's in [8:17] simple sequential program and so let me [8:19] start up some jobs which are going to [8:21] simulate some usage on our system and [8:23] you would in a matter of a single second [8:26] we have 10,000 processes running running [8:28] around 10,000 of these independent [8:29] programs running around our system in [8:31] each of these programs each of these [8:33] processes runs in an infinite loop [8:35] within that loop it runs a short [8:38] cpu-bound loop to generate some load to [8:41] simulate some synthetic load and then it [8:43] sends a message to a well-known process [8:45] in the system like a singleton well [8:48] known well addressable process in the [8:49] system basically it indicates a success [8:52] in the current iteration then it sleeps [8:54] for a single second rinse and repeat and [8:56] then we have that other process in fact [8:58] a couple of other processes collecting [9:00] this day time working together to [9:03] propagate this to whichever user is [9:05] observing this page and so what we see [9:06] on the page are [9:07] basic like metrics if you will so most [9:10] notably the chart on the left indicates [9:12] a reported rate of successes per second [9:14] as you can tell we are roughly in the [9:16] area of expected 10 K successes per [9:18] second you may notice that the line is [9:21] not completely perfectly aligned [9:23] perfectly flat so there are going to be [9:25] some oscillations first and foremost [9:26] because the logic is not really [9:27] perfectly synchronized and then in [9:30] addition because there are other things [9:31] running on this relatively modest [9:33] machine beside the system itself right [9:35] so most notably this browser is [9:36] rendering this chart very frequently [9:39] using SVG so expect some oscillations [9:41] there going to be maybe even a bit [9:42] larger but roughly we do expect to be in [9:45] the area of 10 K successes per second [9:47] now this system here is powered by a [9:50] single scheduler thread by default it [9:53] would actually use one it would actually [9:54] four on this machine but I have [9:56] deliberately downgraded it hard-coded it [9:59] to use one to make some points more [10:01] obvious and so all of the processes [10:04] running on that single scheduler thread [10:05] it's effectively a simulation of a case [10:07] where we have deployed the system on a [10:10] machine with a single CPU core and as [10:12] you can tell here on the right that [10:14] single scheduler is currently not super [10:16] busy so we have like a moderate world [10:17] operating at around 20% of our maximum [10:20] capacity and ok at this point we have [10:24] like some synthetic users using our [10:26] system at the rate of 10 K operations [10:28] per second and now it's time to start [10:30] using it ourselves so I'm going to open [10:32] up a user facing page which as you can [10:35] tell is not really super spectacular but [10:37] it captures what I believe is the gist [10:39] of any web facing system and that's the [10:42] fact that there is some place for user [10:43] where they provide their input and then [10:45] the system choose and this input and [10:47] produces some output and in this [10:49] particular case the input is a positive [10:51] integer or a natural number and so when [10:53] I when a user submits the input the [10:56] system computes the sum of the first n [10:58] natural numbers right so 1 plus 2 plus 3 [11:00] equals 6 and 1 plus 2 Plus 3 plus 4 [11:02] equals 10 again not super spectacular [11:05] but gives us a little bit of context to [11:07] go on with now let me first explain the [11:10] process structure powering this piece of [11:12] functionality so when the user visits [11:16] this page and in fact any page that you [11:18] have seen and you will see a WebSocket [11:20] connection is this tab [11:21] between the browser and the system [11:22] itself and all the communication takes [11:24] place via that WebSocket connection so [11:26] like a browser on the behalf of user [11:28] submits a request give me the sum of the [11:30] first three national numbers the system [11:32] processes this and then returns or not [11:35] returns but sends the result [11:36] asynchronously at some later point in [11:38] time and this is of course then rendered [11:40] in terms of process structure each [11:42] WebSocket connection is managed by its [11:44] own process so here we have a situation [11:45] of exactly three people simultaneously [11:48] connected to the system if we have [11:50] million people connected to the system [11:51] we're going to have a million of these [11:52] connection processes and maybe in the [11:54] later time there's no one around and [11:56] then we're going to have zero of these [11:57] things so they are quite dynamic in [11:59] their nature that they tend to come and [12:00] go depending on the usage patterns now [12:03] the interesting thing happens within a [12:05] single connection so the calculation [12:07] itself is in fact performed in a [12:09] separate one-off process rights like I [12:12] submit a request to give me the sum of [12:13] the first three natural number numbers [12:15] the connection process spawns another [12:18] process which is the calculation process [12:20] and then the result is computed there [12:22] the calculation process sends the result [12:25] back to the connection and then the [12:26] connection of course forwards it further [12:28] to the browser and ultimately the user [12:30] interface and meanwhile the calculation [12:32] process stops terminates normally right [12:35] so these things are again very ephemeral [12:36] in their nature very dynamic and now it [12:40] begs the question you know like why do [12:41] why do we have this process structure [12:43] I've definitely have not chosen it [12:44] randomly so basically the main reason [12:47] why I have opted for this structure is [12:49] to improve fault tolerance and [12:50] ultimately high availability of the [12:52] system the ability of the system to [12:54] produce service and push forward even [12:56] when there are some problems in [12:57] production and as is the case with any [12:59] other real production out there this [13:01] system suffers from some problems some [13:03] faults some bugs and we're going to [13:05] explore some of these bugs so the first [13:09] one let me just refresh is the first one [13:11] I'm going to show you is a simulation of [13:13] what I personally would say is quite [13:16] possibly the most frequent example of a [13:17] problem that occurs in production and [13:19] this is a situation where we haven't [13:21] consider some special cases some edge [13:23] cases and then we end up with a runtime [13:25] exception unhandled exception on the [13:27] server side like square root of -1 [13:29] division by zero undefined is not a [13:32] function and so on and so forth [13:34] here the particular problematic input [13:36] which simulates this situation is the [13:38] lucky number 13 and so when I submit [13:41] this number I get an error which doesn't [13:43] really look super exciting at this point [13:45] but let me show you the log right so [13:48] here I'm tailing the log of my system [13:50] again the system is running as a [13:51] background service so no console running [13:53] in front or something like that [13:55] so what we see in the log we see some [13:57] runtime error and we see that something [13:59] called task has terminated and in terms [14:02] that you understand given what I said [14:04] previously this essentially means that [14:06] the calculation process has crashed and [14:08] burned [14:09] there was some unhandled exception in [14:10] the calculation process and it crashed [14:12] now an important thing about beam is [14:15] that when a process crashes everything [14:17] else progresses right so most of the [14:18] system still works and so like when I [14:21] when we have a bunch of these crashes [14:23] I'm just going to generate them here [14:25] what we see on the dashboard is still a [14:27] stable progress of the system at the [14:29] expected rate of 10 K successes per [14:31] second so most of the service is still [14:33] up and running and most of the users [14:34] don't really see this problem and this [14:36] is a big important point we are [14:37] separating failures by separating things [14:39] into different processes now another [14:41] side of the story is that a failure [14:43] while not an abrupt event is also not a [14:45] silent event so when a process crashes [14:48] are much more generally when it [14:49] terminates whether normally or [14:50] abnormally any other process in the [14:52] system can get a notification about it [14:54] in the shape of a message and that's in [14:56] fact what happens here right so remember [14:57] we have the connection process and the [15:00] calculation process the calculation [15:02] process terminates or pressures [15:04] connection process gets a message about [15:06] it stating that process foo has died [15:09] because of the reason bar it figures out [15:10] the role of foo and then it wires up [15:12] this information to the user interface [15:14] and this is why we are able to report [15:16] the error correctly right we have not [15:18] lost the connection and we were able to [15:20] report the error even when we have the [15:22] unhandled exception and so well not [15:25] immediately obvious this is what many [15:26] people from the wind community would say [15:28] is the most important reason to split [15:30] things into processes to separate [15:32] failures right to get better fault [15:34] tolerance another very important reason [15:36] in my opinion is to separate latencies [15:38] and to demonstrate this I will show you [15:40] another bug another problem that exists [15:43] with this system and this is the fact [15:44] that the implementation of this [15:46] calculation itself is not really [15:48] done in a super efficient way it's in [15:50] fact quite naive very brute-force kind [15:52] of implementation that just runs a [15:54] cpu-bound loop going from 1 to N and [15:56] computes the Sun along the way like one [15:58] plus two plus three plus four and so on [15:59] and so forth and thus if a user submits [16:02] large enough input then we're going to [16:04] have a lot of CPU bound processing but [16:06] so like I'm going to try here with the [16:08] magical number of nine nines so it's one [16:11] two three four five six seven eight nine [16:12] and this thing is now calculating right [16:15] it takes some time now what we can see [16:17] on the dashboard here that that single [16:19] scheduler thread that we have is maxing [16:21] out at 100% and this is going to take [16:23] for like some ten seconds or so let's [16:26] wait for it there it is it's done and [16:28] here we see the result but the very [16:30] interesting thing here is that we still [16:33] have a stable success rate of 10 K [16:35] successes per second despite the fact [16:37] that this is a single threaded system [16:38] right remember all of these processes [16:40] are running on the same thread and there [16:41] is no yielding or any sort of trickery [16:43] than in the implementation itself right [16:45] so how does that even work the thing is [16:48] that Bheem scheduler performs very [16:50] frequent context switching like in less [16:53] than 1 millisecond intervals and it does [16:55] it with proper preemption so no matter [16:57] what the process does be it IO bound or [16:59] CPU bound it gets preempted or it gets [17:02] switched out very very quickly and by [17:04] doing this beam essentially favors our [17:06] fair distribution of CPU time for every [17:08] single activity in the system or in [17:10] other words it promotes the progress of [17:12] the system as a whole at the expense of [17:14] the maximum efficiency of any single [17:16] activity in the system and this makes [17:18] perfect sense in a software system where [17:20] you're running a bunch of different most [17:22] mutually unrelated activities and it's a [17:24] great example in my mind in my opinion [17:26] of how beam implementation is really [17:28] geared it's really fine-tuned toward [17:30] software systems at the expense of any [17:32] other kind any other type of software [17:34] out there and so to put the tie it all [17:37] together right we use processes in my [17:39] opinion are two separate failures and [17:41] two separate latencies these are the [17:42] most important reasons why split things [17:45] into processes and tying it all together [17:47] as a single user I'm going to try four [17:49] five six seven eight nine I'm going to [17:50] try this CPU intensive input and the [17:53] connection is still available so I can [17:56] provide some other input [17:57] can generate a bunch of errors and what [18:00] happens is we still have a stables to [18:01] progress for the rest of the people here [18:03] and we still have a stable progress for [18:05] this particular user here and we got our [18:07] long intensive computation computed this [18:10] is the system which progresses which [18:11] stays on top of the game even when there [18:13] are some problems happening in [18:14] production so like despite a couple of [18:17] holes that we have our ship sails [18:18] smoothly it doesn't sink and it provides [18:20] most of the service to all of its users [18:22] and this in my mind is what high [18:24] availability is about it's not about [18:26] chasing some mythical amount of nines of [18:28] uptime it's about having a system which [18:30] is there for its users which is [18:31] ultimately its primary if not the only [18:34] purpose so ok we have a nice highly [18:38] available system but of course [18:39] ultimately we still need to pick these [18:40] problems because until we do there will [18:42] be some amount of unavailability and [18:44] then in particular there are some quite [18:47] there are some kind of problems which [18:48] are very very which can be possible [18:50] threatening to the very existence of the [18:52] system itself and so here I'm going to [18:53] show you yet another problem which is [18:55] quite insidious and this is the case [18:57] where a user submits a negative number [18:59] as an input so I'm going to try with [19:00] minus 1 and what happens here is we see [19:03] that it hasn't really returned right [19:05] it's calculating so we shouldn't really [19:07] have accepted this input in the first [19:09] place it's not a valid domain value [19:11] right but once we did we should have [19:13] calculated it's already but we didn't [19:15] and in fact what happens here what you [19:17] can see on the scheduler we're maxing [19:18] out constantly we basically have a [19:20] program an activity gone rogue running [19:22] an infinite CPU bound loop like it an [19:25] equivalent of wild through semicolon if [19:26] you will and that's obviously not quite [19:29] good now again good news owing to our [19:31] judicious usage of our processors and [19:33] the way foul beam deals with them we [19:36] have a stable system which still [19:38] progresses at the expected rate of 10 K [19:40] successes per second so it works for [19:42] other users it works for this user and [19:43] this is all well and fine [19:44] but of course ultimately we need to fix [19:48] pick this problem because unless we do [19:51] the system is eventually going to grind [19:52] to a halt right if a user retries this [19:55] input a couple of more times an other [19:57] user stumble into the same problem we [19:59] end up in a situation where the system [20:00] is spinning a lot but really produces [20:02] nothing useful in some reasonable amount [20:04] of time so it's imperative for us to [20:05] quickly figure out what the problem is [20:07] here and to fix it before things you [20:09] know completely blow up [20:11] that's what we're gonna do I'm gonna put [20:12] my operator hat on I'm gonna pretend [20:14] that I don't know what happened here all [20:15] I know is that I got some alert [20:17] notification informing me that my CPU [20:19] usage is at hundred percent and I need [20:21] to figure out the problem right so what [20:23] I'm gonna do I'm gonna SSH to the server [20:25] there I am and I'm taking a look at the [20:27] logs what do I see in the logs I see [20:29] nothing right the log is completely [20:31] statical i see some previous errors [20:33] these are the number thirteen problem so [20:35] that's all well and fine but it doesn't [20:37] really help me with this particular [20:38] problem and so that doesn't really look [20:41] promising but not all is lost because [20:43] beam is a runtime which is highly [20:45] debuggable in respectable observable if [20:47] you will right so beam allows us to hook [20:49] into the running system and peek and [20:51] poke inside it and get a lot of useful [20:53] information and I don't need to set some [20:54] special Flags restart the system and [20:56] whatnot I can do this you're by default [20:58] and that's what we're going to do I'm [21:00] going to invoke the particular command [21:02] to do this thing it's remote console and [21:05] at this point I have a so called IEX [21:07] shell session or interactive elixir [21:09] basically here I can provide elixir [21:11] expressions which are running in the [21:14] context of the running system right so [21:15] like if I ask for this sum it's [21:18] calculated on that single scheduler [21:20] thread and always bear in mind we have a [21:22] single threaded system and we have this [21:24] 10k operations per second running and we [21:26] have this single activity gone rogue and [21:28] yet everything works and here in the [21:30] shell I'm also the user of this system [21:32] right so going to beams scheduling I am [21:36] actually able to even do this to debug [21:38] the system itself so okay the question [21:42] is who is burning our CPU right and it [21:44] has to be one or more processes and so [21:46] the first thing I'm going to do is I'm [21:47] going to list those processes so process [21:50] dot list and I get a list of bits that [21:52] what you asked about earlier these are [21:54] the process IDs right so each bit is [21:56] uniquely identifying a process in the [21:58] system and once I get the list of [22:01] processes then I can get so I ask my run [22:04] time ask the beam about the properties [22:05] of each process so let me take the head [22:09] of this process list and I'm going to [22:13] feed it to the function called process [22:14] info and I get a bunch of properties for [22:17] that particular process as reported by [22:19] the beam itself and for our particular [22:21] scenario we're interested in a property [22:23] which is called [22:24] actions or reduction count and this is a [22:26] non negative integer which is roughly [22:29] proportional to the amount of [22:30] instructions executed in that process [22:32] since the dawn of time rises it was born [22:35] and so to figure out the which of the [22:38] busiest processes I need to take [22:40] reduction counts now do the same thing [22:42] in some future moments with the [22:43] difference sort descending and I have a [22:45] top like view and this is like pretty [22:47] mechanical to speed things up and reduce [22:51] the error surface in this demo I wrote [22:53] myself a helper function for this but [22:55] even without the function it would be [22:57] like quite trivial to do this thing [22:59] and so I invoked run time dot top and it [23:01] gives me the top 10 CPU spenders in the [23:04] past second so I get a list of maps map [23:07] is a key value structure under the keep [23:09] it we have the process ID and then under [23:12] the key CPU we have the percentage of [23:14] CPU usage in the past second and as we [23:17] can see the busiest process is by far [23:19] the busiest it's the process with a pin [23:21] 0 1 to 605 0 and so I'm just going to [23:24] run this thing this is our primary [23:25] suspect now and I'm gonna run this thing [23:27] one more time to make sure that it's the [23:28] same process and let's check it out sure [23:32] enough it's the same process and so this [23:33] is our culprit button within a min [23:35] within a matter of few seconds I was [23:37] able to quickly sift through all these [23:38] tens of thousands of things running [23:39] around the system and find the [23:41] problematic piece and now we're going to [23:42] drill into that problematic piece so I'm [23:45] going to run this runtime top one more [23:46] time take the head of it the top busiest [23:48] process take its big property and take [23:51] it into a bit variable and there we have [23:53] it and now I can ask my run time [23:56] something about this particular process [23:57] and always bear in mind that this is the [23:59] program gone rogue it runs an infinite [24:01] CPU bound loop doesn't heal doesn't talk [24:03] to anyone doesn't answer to anyone but [24:04] we can still get a lot of information if [24:06] we ask the runtime the beam itself [24:08] directly about it so I'll invoke a [24:11] function called process that info again [24:13] and I'm going to ask for the current [24:15] stack trace which I believe is pretty [24:17] self-explanatory and so we get a stack [24:19] trace of this particular process and [24:20] we're going to analyze it a little bit [24:21] later but first I'm going to progress [24:23] forward and gather a bit more data I'm [24:25] going to trace the dynamic activity in [24:27] this process we're going to take a look [24:28] at which functions it's executing so for [24:31] this purpose I also wrote a helper [24:33] function called run time dot trace which [24:35] traces the function locations in this [24:37] process for at most one second or fifty [24:39] function invocations whichever takes [24:41] place first and as we can see it's 50 [24:45] function invocations and we're going to [24:46] take a look at this a bit later too [24:48] but at this point I have gathered all [24:49] the information that I need to properly [24:52] analyze and understand the problem and [24:53] before I do that I'm going to terminate [24:55] this process I'm going to ask the [24:57] runtime to stop this process by sending [24:59] it an exit signal so an woke standard [25:01] library function process dot exit the [25:04] process ID and the reason I'm using a [25:06] special reason called kill which is [25:08] effectively kill minus nine at the beam [25:10] level at the level of this nano Pico [25:12] activity and as soon as I invoke this [25:14] thing what we can see on the dashboard [25:15] is that the scheduler user drops to a [25:18] expected level and of course nothing [25:20] else was disturbed in the process so we [25:22] still have stable progress of 10 K [25:23] successes per second meanwhile for the [25:25] user the error has been reported again [25:28] courtesy of how how we split things in [25:31] two processes the calculation process [25:32] itself was brutally killed and you can [25:35] truly try catch your way out of that one [25:36] but because we have another process [25:38] which gets the information about it this [25:40] is the connection process we are able to [25:42] consistently report error information [25:44] back to the user interface so this [25:48] process exiting it's like a seemingly [25:50] simple thing but one which is as far as [25:52] I know exclusive to beam in terms of [25:54] guarantees this is essentially a first [25:55] class cancellation implemented at the [25:57] runtime layer right and so super simple [25:59] to use with very strong guarantees and [26:01] here I'm invoking it from the shell but [26:03] typically we in fact invoke it from the [26:05] code whether directly or through some [26:06] higher level abstractions to perform [26:08] cancellation like a time-out base [26:10] cancellation or user induced [26:11] cancellation no single line of code and [26:13] does wonders but anyway let's go back to [26:17] the data that we've collected so the [26:19] current stack trace essentially gives me [26:21] a context where that particular program [26:23] was in right and so I see the function [26:25] that it was executing and I see a module [26:27] and module is just a namespace just a [26:29] bunch of functions bundled under the [26:31] same name [26:31] and then I see the file and I see the [26:33] line number and this puts me into the [26:34] proper place into the code and then from [26:36] the dynamic view I see a bit boring [26:39] interesting information so I can see [26:41] that this thing here was actually [26:42] constantly recursing it was running a [26:44] constant recursion which is an FP [26:46] equivalent of a constant loop and [26:48] furthermore I see the [26:50] take of each step of the loop because [26:52] see the parameters of this function [26:53] right so I can see that the first [26:54] parameter was always increased by number [26:57] one the second parameter is always minus [26:59] one and then the third parameter [27:00] increased by some not immediately [27:02] apparent amount but you know given this [27:04] data and the context of the current [27:06] stack trace which puts me to the proper [27:08] place in code I am able to figure out [27:09] the problem and the gist of the story is [27:12] that so this is the module and this is [27:14] the function right here the gist of the [27:16] story that this code really assumes that [27:18] the input is a positive integer and it [27:20] doesn't work for any other kind of input [27:22] but that's actually fine because this [27:24] our business domain are in positive [27:27] integers natural numbers and this piece [27:28] of code is running relatively deeply in [27:30] our system and so this is where we have [27:32] the symptom but the cause in my view [27:34] happens at the edge of the system right [27:37] where we're accepting user input and we [27:39] shouldn't really be allowing all these [27:40] invalid inputs to propagate throughout [27:42] our system we have to discard them there [27:44] and in fact so this is the edge of the [27:46] system this is where I'm accepting users [27:48] input and in fact when we take a look at [27:50] the code [27:50] I'm already discarding some inputs right [27:53] so it returns invalid input for some [27:54] particular cases but obviously I haven't [27:57] considered all the edge cases it's a [27:58] classical kind of bug you know I haven't [28:00] thought of all possible scenarios and so [28:02] now I have to fix this scenario which [28:04] I'm going to elegantly do by just an [28:05] commenting this line of code again for [28:08] demo purposes right all is fair in demos [28:10] and while I'm at it I'm going to fix the [28:12] remaining previous two problems you have [28:14] seen right so we had a problem of [28:15] problematic number thirteen the edge [28:17] case there it is I fixed it and then we [28:20] had the problem of suboptimal [28:22] calculation and to fix that one I need [28:24] to reach for a little bit of high school [28:25] level math so the sum of first n natural [28:27] numbers is n times n plus 1 and then I [28:30] need to divide all of this by 2 and I [28:34] have actually tests which are verifying [28:36] all of this just to make sure that I [28:38] didn't make any mistake there it is [28:41] everything is passing and so now I'm [28:43] going to invoke an admittedly hacky task [28:45] which is going to deploy these changes [28:47] to production so it's going to recompile [28:48] these two modules and bring into [28:50] production without restarting the [28:51] production itself how do we know the [28:53] production has not been restarted [28:54] because we can see here on the dashboard [28:56] that we still have a stable progress of [28:58] 10 K successes per second but has it [29:00] been fixed so without refreshing the [29:02] page minus one invalid M [29:04] put zero invalid input minus one two [29:06] three invalid input number three still [29:08] works for works 13 now works and then a [29:11] bunch of nines I'm not even going to [29:12] count them at this point because this [29:13] thing is fast as a lightning ball now [29:15] so essentially what happened here we had [29:18] a system with problems and it was [29:20] staying on top of the game and it [29:21] allowed me to approach it the system and [29:23] look from inside it to figure out what [29:25] the problems are but I was able to [29:27] quickly fix those problems and deployed [29:29] into production without disturbing [29:31] anything in the system itself right the [29:33] other activities and this is like as a [29:35] writer of systems and ultimately one of [29:37] people who is responsible for managing [29:39] those systems in production this is what [29:41] I want from my tool but in this kind of [29:42] level of utility functions service [29:44] that's what I want from a tool this is [29:46] obviously what beam gives me I am not [29:48] aware of anything else that comes even [29:49] remotely close to that level of service [29:51] so anyway the last demo I'm going to [29:55] show you is related to beams inherent [29:59] support for distributed systems right so [30:01] it is in fact possible to start multiple [30:03] beam instances and typically you want to [30:06] start one for each separate machine but [30:08] you can start of course multiple on a [30:09] single machine and then with a single [30:12] function invocation you can establish a [30:13] TCP connection between two instances and [30:16] at this point you have something which [30:17] is called a beam cluster and then these [30:20] instances are called nodes and once you [30:23] have a cluster you can programmatically [30:24] do some very interesting things so using [30:27] like the main language of your choice or [30:28] long elixir you can enumerate the nodes [30:30] in the cluster you can subscribe to join [30:32] and leave events you can spawn a process [30:35] anywhere in the cluster on any node you [30:37] can send a message to any process [30:39] anywhere in the cluster and you can [30:40] receive messages from other processes [30:42] anywhere in the cluster and for the most [30:44] part the code in fact stays exactly the [30:46] same because in fact in beam [30:48] concurrency and distribution are one and [30:50] the same thing and so let me show you [30:53] another page this is a dashboard where I [30:56] can start up some named services and the [30:58] service here really means a process [31:00] nothing more than that and we're going [31:02] to start a process somewhere in the [31:04] cluster and we're going to give it a [31:06] cluster wide name now here I can see the [31:08] list of nodes in the cluster and [31:10] currently we have just one node so this [31:11] is not really super exciting and [31:13] therefore I'm going to start [31:15] another node this one is going to be [31:17] started in foreground so system node 2 [31:20] and immediately we see that node 2 is [31:22] running is the member of our cluster now [31:25] and so now I provide some name and [31:28] submit and then what happens is system [31:30] somehow picks a node starts a process on [31:32] that node and gives it a cluster wide [31:34] name foo right and so now when I click [31:36] on this thing on the server side the [31:38] system will discover that process using [31:40] the name foo and then send it a message [31:42] and then the process is going to send [31:44] that message back and we are going to [31:46] report it to on the user interface right [31:48] so when I click I get this entire string [31:50] everything here is shaped within the [31:53] process which is globally known as foo [31:55] ok and so we can see that the process in [31:57] fact knows its own name so it has some [31:59] kind of a state and it also of course [32:01] can deduce the node where it's running [32:03] at and now if I start another service [32:05] let's name it bar and it just so [32:07] happened that bar has been started on [32:08] another node but you know for us it [32:11] still works exactly the same way so I [32:13] have like kind of a load spread you know [32:14] and spreading these services across my [32:16] cluster and what happens if the node [32:18] dies right so I'm going to stop this [32:20] node 2 and what we see here is that a [32:23] bar has been resurrected on node 1 so [32:25] this is like fault tolerance I'm dealing [32:27] with even with crashes of individual [32:29] machines and when I click on bar [32:30] now of course bar response from node 1 [32:33] okay [32:34] so full disclosure complete honesty and [32:36] this all looks well and fine but I would [32:39] say that beam distribution is sort of [32:42] plagued with a bunch of issues serious [32:45] issues you know so like well many people [32:47] do use Beam distribution distributed [32:49] beam in production there are also many [32:50] people who would tell you like maybe not [32:52] use that thing I'm not going to go into [32:53] those issues you can ask me about this [32:55] later but in my view they are serious [32:57] and they exist on all levels starting [32:59] from the runtime level all the way to [33:00] the high level libraries that we use [33:02] that said I would also say that these [33:05] issues are purely mechanical which means [33:07] that they can be fixed in the [33:08] implementation without significantly [33:10] changing or without changing at all the [33:12] interface the contract the way we use [33:14] this thing and this is where I see a lot [33:15] of potential and that's exactly or [33:18] that's actually what I want to explain [33:20] so I will briefly give you a very quick [33:22] code walkthrough so here we have the [33:25] module which is powering this [33:26] distributor named service [33:28] and within that module again the module [33:30] is just a bunch of functions we have [33:32] another sub module called local now this [33:35] piece of code here is completely [33:37] agnostic of the fact that distributed [33:39] beam is used doesn't care you know you [33:41] can use it you don't have to use it [33:42] works exactly the same this is just [33:43] basic concurrent elixir and if you want [33:45] to do some concurrent elixir sooner or [33:47] later you will have to learn a little [33:48] bit of this which is not really rocket [33:50] science and now the question is how do [33:52] we turn this thing to be distributed and [33:54] there are exactly three things I've done [33:56] I think number one in another file which [33:58] is called mix exs which is the part of [34:00] the same project project this is where [34:02] I'm configuring the project itself and [34:04] within that file I have a section where [34:06] I'm defining dependencies of the project [34:09] like the libraries on the framework [34:10] which I'm using and one of my [34:12] dependencies is called swarm and swarm [34:15] is basically like a higher level [34:17] distributed service Orchestrator it [34:18] gives me a high level interface which [34:20] allows me to start the process somewhere [34:22] in the cluster and give it a cluster [34:23] wide name quite easily right and swarm [34:27] is a dependency and in elixir like in [34:30] any other language beam or not being a [34:32] dependency means bringing in some code [34:34] write some functions which I can invoke [34:36] however in beam languages a dependency [34:39] may also bring activities processors [34:41] right and so courtesy of this single [34:43] line of code when I start my systems [34:46] form is going to start some of its own [34:48] processes which are related to the sporm [34:50] functionalities so these processes are [34:51] going to watch for node joining and [34:54] leaving and they are going to manage the [34:55] cluster wide state the Association of [34:57] names to process and all this happens [35:00] courtesy of this single line of code [35:01] essentially I am running this [35:03] distributed service Orchestrator [35:05] directly in my system and because in my [35:07] mind this thing is a part of my system [35:09] it's the best place to run it like [35:11] what's the better place to run a part of [35:13] the system if not the same place for the [35:14] rest of the system is running itself and [35:16] anyway once I have swarm running then [35:19] starting a named service somewhere in [35:22] the cluster boils down to invoking a [35:24] particular swarm function which does [35:26] exactly that so it's going to somehow [35:28] pick a node and start the process there [35:30] and it's going to manage the association [35:31] between the given name and the process [35:33] and then once I want to discover and [35:35] invoke this service given its name and [35:37] woke another swarm function I give it a [35:39] name it gives back [35:41] bid which is again a unique piece of [35:43] information identifying the process in [35:45] the system and in this case the system [35:47] is a cluster not just a single node and [35:49] so kid is locational a transparent type [35:52] right it can work for local process and [35:54] it could work for remote processes we [35:56] don't care the vast majority of the [35:57] functions which are working with big [35:59] data type don't care too and so I get a [36:01] paid and I just pass it to the [36:02] distributed beam agnostic function and [36:04] the thing just works but three lines of [36:07] code to make this thing distributed [36:08] nothing else running on the side no [36:10] extra OS process no container no virtual [36:12] machine nothing as a services employed [36:15] here in fact I mean completely [36:16] disconnected from the network and so [36:18] this thing is super integrated right [36:20] everything is in fact implemented in [36:22] Aleks here so there is like given the [36:24] user interface itself is driven by the [36:26] elixir code there is no JavaScript here [36:28] this is all very technically uniform and [36:30] this technical uniformity is the last [36:32] thing that I want to discuss in my [36:34] current talk so this table here [36:39] originally published in my book elixir [36:40] in action tells a true story about two [36:43] systems I was working on side by side [36:44] around the beginning of this decade and [36:46] what happened in system a was that our [36:49] first chosen technology our cornerstone [36:51] our foundation was a technology that [36:53] didn't really have proper support if any [36:55] for the challenges inherent to software [36:58] systems and we were building a software [36:59] system we had those challenges and in [37:01] order to solve them we had to fall back [37:03] to the level of the operating system [37:04] right so we had to run a bunch of always [37:06] processes and reach for other languages [37:07] where we needed a better concurrency [37:09] story and we also of course had to reach [37:11] for third-party products and we [37:13] fulfilled our needs [37:14] however the technical complexity [37:16] footprint the barrier if you will of [37:18] this solution is really really high like [37:20] imagine a junior lending to this thing [37:22] how much of the things that the [37:23] technical concept technical things do [37:25] they need to learn before they can [37:27] confidently work on any part of the [37:28] system system be implemented in Erlang [37:31] groaning on top of being the runtime [37:32] which as you have seen has a rich [37:34] support for software systems it is in [37:36] fact sometimes said that beam is an [37:38] operating system for our code and if I [37:40] have the operating system directly in my [37:41] runtime and I have the language as the [37:43] main interface then I don't need to fall [37:44] back to the OS level and indeed we [37:46] didn't write so we were able to fulfill [37:48] all our needs in Erlang in the same [37:50] language in the same project have it [37:52] runs [37:52] you'll always process at runtime now it [37:55] goes without saying that these solutions [37:56] cannot fully compete with full-blown [37:58] third party products so we don't have [38:00] all the bells and whistles and fancy [38:02] performance but this they worked for our [38:03] needs just fine and what we got in [38:05] return is this technical total technical [38:08] uniformity which of course is not a goal [38:10] in itself right we should not strive for [38:12] the for uniformity for the sake of [38:13] uniformity but I do believe that it can [38:15] bring a lot of important benefits that [38:17] happen take place in all the phases of a [38:19] software development cycle so like if my [38:21] whole system is implemented in a single [38:23] piece of technology let's say a league [38:24] here right so one language one project [38:26] one always process per each machine in [38:27] the cluster then it's clearly easier to [38:29] work with it on a daily basis right so [38:31] like starting from setting up a [38:32] development machine and then you know [38:34] writing some code writing test setting [38:36] up CI deploying a single thing has to be [38:38] way easier than orchestrating a [38:40] deployment of five to ten different [38:41] moving pieces and then managing the [38:43] system in production there is like one [38:44] thing we hook into there is one thing [38:47] where we collect metrics and logs there [38:49] is one thing we debug which as you have [38:50] seen of courses were being particularly [38:52] shines and then on top of it all and I [38:55] would say by far the most important [38:56] benefit is the fact that the teamwork in [38:58] general is improved because the [39:00] technical bar is significantly lower [39:02] there are less technical things to learn [39:04] and that's a good thing but a new [39:05] developer comes on board they learn [39:07] themselves a bit of a leak serum that [39:08] can immediately contribute to any part [39:10] of the system and so we don't need to [39:11] have all these narrow specialists we [39:13] don't they feel like a ready specialist [39:15] engine expecially stated especially [39:17] zookeeper specialist Kafka specialist [39:19] kubernetes specialist and so on and so [39:20] forth and we don't need to wrestle with [39:22] all these Yama's and in ease and Jason's [39:24] and tunnels and xml's and other kind of [39:26] ad hoc be yourselves and they're quite [39:28] substandard dueling it is in fact [39:30] possible it is perfectly viable to [39:33] express to describe the entire system [39:35] using a single language which is [39:37] steering complete gives us full [39:39] integration to the underlying operating [39:40] system and it gives us full flexibility [39:43] compile-time and run-time to make [39:45] whichever variations we want on a [39:47] case-by-case basis and these are in my [39:49] opinion some terrific benefits to be [39:51] read and the reason why they can be [39:54] reaped is because we have something like [39:55] being as our foundation right and so [39:58] this is where I see the biggest [39:59] potential of being languages which is [40:01] admittedly not still fully realized [40:04] we're maybe like halfway there [40:06] but I do hope we'll get there and like [40:07] imagine the following story you want to [40:09] build a small to medium web facing crowd [40:12] and what you do is create a new elixir [40:13] project bring in a couple of [40:15] dependencies and five minutes later you [40:17] have everything you have like proper [40:19] support for distribution you have SQL [40:22] based persistence with replication [40:23] everything running directly in that same [40:25] project in the single system now this is [40:28] what I call a simple start right because [40:30] it moves a lot of technicalities out of [40:32] the way and it allows me to focus on the [40:33] core challenges that I'm supposed to [40:35] solve yet at the same time the solution [40:37] is implemented in a full-blown curing [40:39] complete [40:39] language and it's running on top of beam [40:41] this wonderful runtime that has been [40:43] around for like what three decades has [40:45] been extensively battle-tested in [40:47] different projects of different shapes [40:49] and sizes and so I can really be [40:51] confident that down the line if I need [40:53] to I can deal with highly complex [40:55] challenges at a very large scale without [40:57] having to rewrite everything from [40:58] scratch and in my view this is a winning [41:00] combination [41:01] right so the artificial trade-off of [41:03] having to choose between a quick start [41:04] technology and the long run one [41:06] disappears flies out the window and I [41:08] get both in the same piece of technology [41:09] and this is all in my opinion possible [41:11] not realized yet but possible going to [41:14] beam this wonderful runtime which is in [41:16] my view indeed the soul of Erlang and [41:18] elixir so before a part a couple of [41:21] parting gifts I'm the author of the book [41:23] elixir in action only today it's 50% off [41:26] with the following code up there and [41:28] then for the rest of the days you also [41:30] have the 40% of code I'm also giving [41:34] away a couple of copies for free so I [41:36] have to hard book versions and a couple [41:37] of more of eBook versions and if you [41:40] wanna grab that just approach me after [41:41] the talk and ask for it you don't have [41:43] to pretend that you're interested in [41:44] anything else please rate this session [41:48] let me know what worked what didn't I'm [41:52] going to return back to this fight in [41:53] case you want to grab those coats and [41:54] thank you [41:58] you