Do you want to pick up from where you left of?
Take me there

OTPスーパバイザ

スーパバイザは、他のプロセスを監視するという一つの目的に特化したプロセスです。 子プロセスが失敗した際に自動的に再起動させることによって、耐障害性の高いアプリケーションを作ることを可能にします。

設定

スーパバイザの魔術は Supervisor.start_link/2 関数の中にあります。 スーパーバイザプロセスと子プロセスを開始するのに加えて、子を管理するためにスーパーバイザが使用する戦略を定義することができます。

OTPの並行性レッスンで実装したSimpleQueueを用いて、始めていきましょう。

新しいプロジェクトを mix new simple_queue --sup を使って作成することで、スーパバイザツリーも一緒に作成することができます。 SimpleQueue モジュール用のコードは lib/simple_queue.ex へ、スーパバイザを起動するコードは lib/simple_queue/application.ex に記述することになります。

スーパバイザの子プロセスはリストで定義します。それは以下のように単純にモジュール名のリストにするか…

defmodule SimpleQueue.Application do
  use Application

  def start(_type, _args) do
    children = [
      SimpleQueue
    ]

    opts = [strategy: :one_for_one, name: SimpleQueue.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

あるいは子プロセスの起動時に初期値を渡すべくタプルのリストにするか、のどちらかが使えます。

defmodule SimpleQueue.Application do
  use Application

  def start(_type, _args) do
    children = [
      {SimpleQueue, [1, 2, 3]}
    ]

    opts = [strategy: :one_for_one, name: SimpleQueue.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

iex -S mix で実行すると SimpleQueue が自動的に実行されるのがわかります。

iex> SimpleQueue.queue
[1, 2, 3]

もし SimpleQueue プロセスが異常終了や通常終了すれば、スーパバイザは何事もなかったかのように自動的にプロセスを再起動するでしょう。

戦略

今のところ、スーパバイザが利用可能な3つの異なる再起動の戦略があります:

子プロセスの定義

起動されたスーパバイザは、子プロセスをどのように起動/停止/再起動させるのかを知ってなくてはなりません。 各々の子モジュールにはこれらの振る舞いを定義する child_spec/1 関数が必要です。 use GenServeruse Supervisoruse Agent のマクロを使う場合は、この定義が自動的に行われます。例えば SimpleQueueuse GenServer してるので、モジュールの記述を変更する必要はありません。自分で定義する必要がある場合は child_spec/1 関数が以下のようにキーのマップを返すようにします。

def child_spec(opts) do
  %{
    id: SimpleQueue,
    start: {__MODULE__, :start_link, [opts]},
    shutdown: 5_000,
    restart: :permanent,
    type: :worker
  }
end

動的スーパバイザ

アプリケーションが開始されたときに、通常スーパバイザは子プロセスのリストと共に起動します。 しかしながら、アプリケーションの開始時には監視すべき子プロセスが決まっていない場合もあります。例えば、webサイトに接続にくるユーザ処理のプロセスを起動するようなアプリケーションなどでありえます。 このような状況に対しては、子プロセスを要求に応じて開始できるようなスーパバイザが欲しくなるでしょう。 動的スーパバイザはこのような状況で利用できます。

子プロセスを定義しないので、スーパバイザの定義はランタイムオプションのみです。 動的スーパバイザは監視の戦略として :one_for_one のみが使えます。

options = [
  name: SimpleQueue.Supervisor,
  strategy: :one_for_one
]

DynamicSupervisor.start_link(options)

そして、スーパバイザと子プロセスの定義を引数として start_child/2 を用いることで動的にSimpleQueueを起動します。ただ SimpleQueueuse GenServer を使っているので、すでに子プロセスは定義済みです。

{:ok, pid} = DynamicSupervisor.start_child(SimpleQueue.Supervisor, SimpleQueue)

Taskスーパバイザ

Taskはそれ専用のスーパバイザ Task.Supervisor を持っています。 動的にタスクを生成するように設計されており、Taskスーパバイザは暗黙的に DynamicSupervisor を用いています。

セットアップ

Task.Supervisor の使い方は他のスーパバイザと同様です:

children = [
  {Task.Supervisor, name: ExampleApp.TaskSupervisor, restart: :transient}
]

{:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one)

Supervisor との Task.Supervisor の最も大きな違いは再起動の戦略のデフォルトが :temporary である、すなわちタスクは再起動されない設定になっているということです。

Taskの監視

スーパバイザが開始された状態において start_child/2 関数によりスーパバイザに監視されるタスクを作ることができます:

{:ok, pid} = Task.Supervisor.start_child(ExampleApp.TaskSupervisor, fn -> background_work end)

タスクが途中で異常終了する場合、再起動が行われます。 これは次々と入ってくる接続やバックグラウンドで行う処理には特に役に立つでしょう。

間違いを報告したい、あるいはこのレッスンに貢献したい? このレッスンをGitHubで編集しよう!