Part 5. Adding Supervisors to the key-value store. — Elixir/OTP

Arpit Dubey
Gamezop Tech
Published in
5 min readFeb 27, 2020
Photo by Jase Bloor on Unsplash

This is part 5 of the 7 part series. Please read the previous articles to catch up on the topic.

Till now we have discussed GenServers and how it handles tasks in an async manner in an isolation. But now we are going to discuss something which is very crucial in Elixir programming language and most of the time when developing any Elixir application you would be making use of it. The topic which we are going to discuss in this post is FAULT TOLERANCE.

Fault tolerance is one of the things for which people vouch for Elixir. Now suppose in the key-value store app there some runtime error occurs and an exception is raised which causes the system to crash. In this scenario ideally, it would be much better to have a utility that restarts the process when it exits unexpectedly. This can be achieved by using Supervisors.

A supervisor is a generic process that manages the lifecycle of other processes in a system. A supervisor process can start other processes, which are then considered to be its children. Using links, monitors, and exit traps, a supervisor detects possible terminations of any child and can restart it if needed. Processes that aren’t supervisors are called workers. These are the processes that provide the actual services of the system.

If any of the worker processes crashes, perhaps due to a bug, some part of your system will be gone forever. This is where supervisors can help. By running workers under a supervisor, you can ensure that a failing process is restarted, and the service of your system is restored.

Let’s get started 🏎️

In order to start a supervisor Supervisor.start_link/2 can be called which does the following thing:

  1. The supervisor starts and starts the child which are registered under it.
  2. When a child crashes the supervisor will trap exit and restart the child process.
  3. If a supervisor crashed all the child processes under it will crash.
High-Level Design of our process under a Supervisor

Before beginning make sure you have Elixir and Mix installed in your system.

Now create a mix project by typing

mix new ex5_supervised_key_val_with_db_workers

This command should create your project structure like this

folder structure

Delete all the files in the lib folder and create a file with name system.ex. Also, copy the other files from the 3rd exercise into the lib folder i.e. manager.ex, server.ex, database.ex, db_worker.ex and store.ex.

Now as shown in the diagram our system would be started by a supervisor which would then start the KeyVal.Manager and KeyVal.DB. At any point of time if the manager or the db crashes it will be restarted as it is being supervised by the system.

Show me some code 👨‍💻

Since we will now manage the manager and database via a supervisor there is some extra code that we need to write around them in order for them to be compatible with the supervisor.

In order to manage a child process, a supervisor needs some information, such as

  1. How should the child be started?
  2. What should be done if the child terminates?
  3. What term should be used to uniquely distinguish each child?

These pieces of information are collectively called the child specification. For example, here’s what the specification for the KeyVal.Manager looked like:

%{
id: KeyVal.Manager,
start: { KeyVal.Manager, :start_link, [nil]}
}

The :id field is an arbitrary term that’s used to distinguish this child from any other child of the same supervisor. The :start field is a triplet in the shape of {module, start_function, list_of_arguments}. When starting the child, the supervisor process will invoke module.start_function, passing it the given list of arguments. This function must start and link the process.

Here, Supervisor.start_link/2 will call init function which would further invoke the start function of the children defined in the array. The second argument is the strategy by which we want to start the supervisor.

system.ex

Now in both manager and db we need to replace GenServer.start with GenServer.start_link because we want to link the children processes with the supervisor. When a child is specified in Supervisor initialization the child specification of the child is checked. If it returns the child specification the start function for the child is called in order to start it.

database.ex

Since both the GenServer are now linked to the Supervisor if an error occurs in the system the resulting in crashed in any child. The supervisor will restart the failed process.

manager.ex

Rest everything in the code is same as that of the previous exercise.

The final run 📟

In order to start the whole system, we first initiate an elixir session
by typing

iex -S mix
session

After starting the system you can see that both the manager and database with the workers are started under a supervisor.

Now as you can see that as soon as Manager or Database is killed both are automatically restarted and you get the output saying that the service has been started. This means that the supervisor is working fine and is doing its job.

We have barely scratched the surface of Supervisors and I would encourage you to read more about it here as it has many other things like restart frequency and starting strategies.

I hope this post has helped you get a little bit better understanding of the whole process.

The complete source code of all the parts are here.

References 📝

  1. Elixir in Action. 2nd Edition.
  2. GenServer behaviour docs.

--

--

Arpit Dubey
Gamezop Tech

Fullstack developer. React ● Node ● Go ● Elixir. I make awesome stuff with my bare hands 👐🏻