DynamicSupervisor — 동적으로 자식만 시작하는 슈퍼바이저

DynamicSupervisor — 동적으로 자식만 시작하는 슈퍼바이저

DynamicSupervisor자식 프로세스를 실행 중(런타임)에 동적으로 띄우는 데 특화된 슈퍼바이저 behaviour예요. 기존 Supervisor는 슈퍼바이저가 시작할 때 주어진 순서대로 정적인 자식들을 시작하도록 설계되어 있어요. 반면 DynamicSupervisor는 자식 없이 시작하고, 대신 start_child/2필요할 때마다 자식을 시작해요. 자식 사이에 순서가 없죠. 덕분에 효율적인 자료구조를 써서 수백만 개의 자식까지 담을 수 있고, 종료 같은 일부 연산을 동시에(concurrently) 수행할 수도 있어요.

출처: DynamicSupervisor

본문

시작과 예시

다이나믹 슈퍼바이저는 자식 없이, 그리고 보통 이름을 붙여서 시작해요.

children = [
  {DynamicSupervisor, name: MyApp.DynamicSupervisor, strategy: :one_for_one}
]

Supervisor.start_link(children, strategy: :one_for_one)

child specification에 주는 옵션들은 start_link/1 문서에 설명되어 있어요. 슈퍼바이저가 떠 있으면 그것을 이용해 자식을 원할 때 시작할 수 있어요. 이런 간단한 GenServer가 있다고 해볼게요.

defmodule Counter do
  use GenServer

  def start_link(initial) do
    GenServer.start_link(__MODULE__, initial)
  end

  def inc(pid) do
    GenServer.call(pid, :inc)
  end

  def init(initial) do
    {:ok, initial}
  end

  def handle_call(:inc, _, count) do
    {:reply, count, count + 1}
  end
end

여기에 start_child/2와 child specification을 주면 Counter 서버를 시작할 수 있어요.

{:ok, counter1} = DynamicSupervisor.start_child(MyApp.DynamicSupervisor, {Counter, 0})
Counter.inc(counter1)
#=> 0

{:ok, counter2} = DynamicSupervisor.start_child(MyApp.DynamicSupervisor, {Counter, 10})
Counter.inc(counter2)
#=> 10

DynamicSupervisor.count_children(MyApp.DynamicSupervisor)
#=> %{active: 2, specs: 2, supervisors: 0, workers: 2}

확장성과 파티셔닝

DynamicSupervisor는 다른 프로세스를 시작하는 단일 프로세스예요. 어떤 애플리케이션에서는 이것이 병목이 되기도 해요. 이럴 땐 다이나믹 슈퍼바이저를 여러 개 띄우고, 자식을 시작할 때 "임의의" 한 인스턴스를 고르면 돼요. 다음처럼 쓰는 대신:

children = [
  {DynamicSupervisor, name: MyApp.DynamicSupervisor}
]

그리고:

DynamicSupervisor.start_child(MyApp.DynamicSupervisor, {Counter, 0})

이렇게 바꿀 수 있어요:

children = [
  {PartitionSupervisor,
   child_spec: DynamicSupervisor,
   name: MyApp.DynamicSupervisors}
]

그리고 나서:

DynamicSupervisor.start_child(
  {:via, PartitionSupervisor, {MyApp.DynamicSupervisors, self()}},
  {Counter, 0}
)

위 코드는 파티션 슈퍼바이저를 시작하는데, 기본적으로 머신의 코어마다 다이나믹 슈퍼바이저 하나씩을 띄워줘요. 그리고 이름으로 부르는 대신 self()를 라우팅 키로 써서 파티션 슈퍼바이저를 통해 호출해요. 이렇게 하면 각 프로세스가 기존 다이나믹 슈퍼바이저 중 하나에 배정돼요. 자세한 내용은 PartitionSupervisor 문서를 읽어보세요.

모듈 기반 슈퍼바이저

Supervisor와 마찬가지로 다이나믹 슈퍼바이저도 모듈 기반으로 만들 수 있어요.

defmodule MyApp.DynamicSupervisor do
  # 자동으로 child_spec/1 을 정의
  use DynamicSupervisor

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

  @impl true
  def init(_init_arg) do
    DynamicSupervisor.init(strategy: :one_for_one)
  end
end

use DynamicSupervisor 바로 앞에 붙인 @doc 주석은 생성된 child_spec/1 함수에 붙어요.

이름 등록

슈퍼바이저는 GenServer와 같은 이름 등록 규칙을 따라요. 자세한 규칙은 GenServer 문서를 참고해요.

더 알아보기

  • Supervisor 문서에서 정적 슈퍼바이저와의 차이, 전략(strategy), 재시작 옵션(max_restarts, max_seconds)을 자세히 볼 수 있어요.
  • 자식을 여러 프로세스로 나눠 시작해야 한다면 PartitionSupervisor 문서를 확인해요.
  • start_child/2start_link/1의 반환값(on_start_child/0 타입), count_children/1, terminate_child/2 같은 함수 시그니처는 원문 문서의 Types·Functions 항목을 참고하세요.