Given supervisors are also processes, they may also become bottlenecks. While this is unlikely to happen to a Supervisor, since it is mostly static, it can happen to a DynamicSupervisor.
We can address this by partitioning the dynamic supervisor. Imagine the following dynamic supervisor:
defmodule MyApp.DynamicSupervisor do
use DynamicSupervisor
def start_link(opts) do
DynamicSupervisor.start_link(__MODULE__, arg, opts)
end
def init(_arg) do
DynamicSupervisor.init(strategy: :one_for_one)
end
end
In order to partition it, we can start 8 instances of said supervisor inside a regular Supervisor, and then pick one partition at random when starting a child. For example:
defmodule MyApp.Supervisor do
use Supervisor
@partitions 8
@name __MODULE__
def start_child(module, arg) do
i = :erlang.phash2(self(), @partitions) + 1
DynamicSupervisor.start_child(:"#{__MODULE__}#{i}", {module, arg})
end
def start_link do
Supervisor.start_link(__MODULE__, arg, name: @name)
end
def init(arg) do
children =
for i <- 1..@partitions do
name = :"#{__MODULE__}#{i}"
Supervisor.child_spec({MyApp.DynamicSupervisor, name: name}, id: name)
end
Supervisor.init(children, strategy: :one_for_one)
end
end
I would like to make the above more convenient by introducing a :partitions option to DynamicSupervisor.start_link. When given, the new option will automatically start N dynamic supervisors under a supervisor, like above:
DynamicSupervisor.start_link(__MODULE__, :ok, partitions: 8, name: @name)
For now, the :name option will be required.
Now, when spawning child processes, you will use via tuples:
DynamicSupervisor.start_child({:via, DynamicSupervisor, {@name, self()}}, {module, arg})
The via tuple has the format {:via, DynamicSupervisor, {supervisor_name, value_to_partition_on}}. Once invoked, it will take care of partitioning based on the current process and dispatching it.
Overall, encapsulating the partitioning of DynamicSupervisor (and consequently of Task.Supervisor) into an easy to use API can help to vertically scale up applications that use those constructs.
## Open questions
One of the confusing aspects of the above is that the :name option no longer reflects the name of the DynamicSupervisor but of the parent Supervisor. One alternative is to *not* accept the :name option when :partitions is given, instead we could have a :root_name option instead (or something more appropriately named).
Implementation wise, we will store the available processes in ETS or using a Registry (to be started alongside the Elixir application).
Feedback?