0Pricing
Elixir & Phoenix: Scalable Backend Development · レッスン

スーパーバイザーとアプリケーション構造

スーパーバイザーでプロセスを監視・再起動し、障害耐性のある堅牢なアプリケーションを設計します。

「スーパーバイザーとアプリケーション構造」はCoddyKit上の無料Elixir & Phoenix: Scalable Backend Developmentレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはElixir & Phoenix: Scalable Backend Development学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Elixir & Phoenix: Scalable Backend Developmentコースには全4レッスンが含まれています。

このレッスンの一部はまだ翻訳されておらず、英語で表示されています。

Building Resilient Systems

In concurrent applications, things can go wrong. Processes might crash due to unexpected errors or external issues.

Fault tolerance is the ability of a system to continue operating even when components fail. Elixir embraces a "let it crash" philosophy, meaning instead of trying to prevent every single crash, it focuses on gracefully recovering from them.

Guardians of Processes

This is where Supervisors come in! A supervisor is a special kind of process designed to monitor other processes (its "children").

  • If a child process crashes, the supervisor automatically restarts it.
  • This ensures your application remains stable and available.
  • Supervisors form the backbone of fault-tolerant Elixir applications.

Your First Supervisor

Let's define a simple supervisor module. It uses the Supervisor behavior, similar to how GenServer uses the GenServer behavior.

The init/1 callback is where you define the children it will supervise and its restart strategy.

defmodule MySupervisor do
  use Supervisor

  def start_link(init_arg) do
    Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
  end

  @impl true
  def init(_init_arg) do
    # No children yet, just the supervisor itself
    children = []
    Supervisor.init(children, strategy: :one_for_one)
  end
end

# --- Main execution part ---
# This simulates starting the supervisor and checking its status.
# In a real app, this would be part of an Application's start/2.
IO.puts("Attempting to start MySupervisor...")
{:ok, supervisor_pid} = MySupervisor.start_link([])
IO.puts("MySupervisor started with PID: #{inspect(supervisor_pid)}")

# Check if the supervisor process is alive
if Process.alive?(supervisor_pid) do
  IO.puts("Supervisor is alive!")
else
  IO.puts("Supervisor is NOT alive!")
end

Understanding Supervision Strategies

Supervisors have different strategies for handling child failures:

  • :one_for_one: Restarts only the child that crashed. This is the default and most common.
  • :one_for_all: If any child crashes, all other children are terminated and then all children are restarted.
  • :rest_for_one: If a child crashes, it and all children started *after* it are terminated and then restarted.

Choosing the right strategy depends on the dependencies between your processes.

Adding Supervised Children

To make a supervisor useful, it needs children! You define children using Supervisor.child_spec/2, which tells the supervisor how to start and manage a process.

Here, we define a simple MyWorker GenServer and add it as a child to our supervisor.

defmodule MyWorker do
  use GenServer

  def start_link(_opts) do
    GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
  end

  @impl true
  def init(:ok) do
    IO.puts("MyWorker started!")
    {:ok, %{}}
  end

  @impl true
  def handle_call(:crash, _from, state) do
    IO.puts("MyWorker is crashing!")
    exit(:bad_state) # Simulate a crash
    {:reply, :ok, state} # This line won't be reached
  end
end

defmodule MySupervisorWithWorker do
  use Supervisor

  def start_link(init_arg) do
    Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
  end

  @impl true
  def init(_init_arg) do
    children = [
      # Define our worker as a child process
      Supervisor.child_spec(MyWorker, id: MyWorker)
    ]
    Supervisor.init(children, strategy: :one_for_one)
  end
end

# --- Main execution part ---
IO.puts("Starting supervisor with worker...")
{:ok, supervisor_pid} = MySupervisorWithWorker.start_link([])
IO.puts("Supervisor PID: #{inspect(supervisor_pid)}")

# Get the worker's PID
worker_pid = Process.whereis(MyWorker)
IO.puts("Initial MyWorker PID: #{inspect(worker_pid)}")

if Process.alive?(worker_pid) do
  IO.puts("Worker is alive and supervised.")
else
  IO.puts("Worker did not start correctly.")
end

Fault Tolerance in Action

Now, let's see the supervisor in action! We'll deliberately crash our MyWorker process, and the supervisor will automatically restart it.

Notice how the worker's Process ID (PID) changes, indicating a new process was spawned.

defmodule MyWorker do
  use GenServer

  def start_link(_opts) do
    GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
  end

  @impl true
  def init(:ok) do
    IO.puts("MyWorker started!")
    {:ok, %{}}
  end

  @impl true
  def handle_call(:crash, _from, state) do
    IO.puts("MyWorker is crashing!")
    exit(:bad_state) # Simulate a crash
    {:reply, :ok, state} # This line won't be reached
  end

  def crash_it do
    GenServer.call(__MODULE__, :crash)
  end
end

defmodule MySupervisorWithWorker do
  use Supervisor

  def start_link(init_arg) do
    Supervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
  end

  @impl true
  def init(_init_arg) do
    children = [
      Supervisor.child_spec(MyWorker, id: MyWorker)
    ]
    Supervisor.init(children, strategy: :one_for_one)
  end
end

# --- Main execution part ---
IO.puts("Starting supervisor with worker...")
{:ok, _supervisor_pid} = MySupervisorWithWorker.start_link([])

worker_pid_before_crash = Process.whereis(MyWorker)
IO.puts("Worker PID before crash: #{inspect(worker_pid_before_crash)}")

# Crash the worker
IO.puts("Attempting to crash the worker...")
MyWorker.crash_it()
:timer.sleep(100) # Give supervisor a moment to restart

worker_pid_after_crash = Process.whereis(MyWorker)
IO.puts("Worker PID after crash: #{inspect(worker_pid_after_crash)}")

if worker_pid_before_crash != worker_pid_after_crash && Process.alive?(worker_pid_after_crash) do
  IO.puts("Worker was restarted by the supervisor! New PID detected.")
else
  IO.puts("Worker was NOT restarted, or PID remained the same (unexpected).")
end

Elixir Applications: The Top Level

While supervisors manage individual processes, an Elixir Application is the top-level unit of code and processes in an Elixir system.

It provides a structured way to:

  • Group related modules and processes.
  • Define how your system starts up and shuts down.
  • Manage configuration and dependencies.

The `Application` Behavior

Every Elixir application typically has a main application module that use Application.

The most important callback is start/2, which is invoked when your application starts. This is where you typically start your top-level supervisor, which then recursively starts all other processes in your system.

defmodule MyApp.Application do
  use Application

  # This is the entry point for your application.
  # It starts the top-level supervisor.
  @impl true
  def start(_type, _args) do
    children = [
      # In a real app, you'd start your main supervisor here.
      # For example: Supervisor.child_spec(MySupervisorWithWorker, id: MySupervisorWithWorker)
    ]

    # Start a supervisor that will supervise other processes/supervisors
    opts = [strategy: :one_for_one, name: MyApp.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

# --- Main execution part ---
# This part simulates how an application would be started.
# In a real Mix project, 'mix run --no-halt' would call MyApp.Application.start/2
IO.puts("Simulating application start...")
{:ok, pid} = MyApp.Application.start(:normal, [])
IO.puts("Application top-level supervisor started with PID: #{inspect(pid)}")

if Process.alive?(pid) do
  IO.puts("Application supervisor is active.")
else
  IO.puts("Application supervisor failed to start.")
end

Hierarchical Supervision Trees

For complex applications, you often don't have just one supervisor. You create a supervision tree, where supervisors can supervise other supervisors.

  • This allows you to organize your application into logical units.
  • Different parts of your system can have different restart strategies.
  • If a major component fails, its supervisor can restart it without affecting unrelated parts of the system.

Supervisor Check

Time for a quick check on your understanding of supervisor strategies!

Lesson Summary

Well done! You've learned how Elixir builds resilient applications:

  • Supervisors monitor processes and restart them upon failure.
  • Different supervision strategies (:one_for_one, :one_for_all, :rest_for_one) dictate how failures are handled.
  • Elixir Applications provide the top-level structure, starting supervisors and forming supervision trees.

These concepts are fundamental to building robust, fault-tolerant systems in Elixir.

よくある質問

「スーパーバイザーとアプリケーション構造」レッスンは無料ですか?

はい。「スーパーバイザーとアプリケーション構造」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Elixir & Phoenix: Scalable Backend Developmentコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Elixir & Phoenix: Scalable Backend Developmentコースには全4レッスンが含まれています。

「スーパーバイザーとアプリケーション構造」で何を学びますか?

スーパーバイザーでプロセスを監視・再起動し、障害耐性のある堅牢なアプリケーションを設計します。 ブラウザで直接実行するハンズオンコードでElixir & Phoenix: Scalable Backend Developmentを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Elixir & Phoenix: Scalable Backend Developmentを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのElixir & Phoenix: Scalable Backend Developmentは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。

「スーパーバイザーとアプリケーション構造」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このElixir & Phoenix: Scalable Backend Developmentレッスンでコードを書いて実行できますか?

はい。すべてのElixir & Phoenix: Scalable Backend Developmentレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. Elixirのプロセスとメッセージパッシング
  2. GenServerビヘイビアの実装
  3. スーパーバイザーとアプリケーション構造
  4. TaskとAgentによる並行処理
← Elixir & Phoenix: Scalable Backend Developmentに戻る