Build your mobile app more efficiently with LiveView Native. Book a free consult today to learn how we can put it to work for you.
Continuing our Building Beacon series, let’s see how each site is started with its own config and how it behaves when one of them starts to fail.
In the previous article, we’ve seen that each site starts a supervisor with some children that are responsible for the site’s operation, which includes storing the site config, loading resources, and more. We’ll discuss those inner parts in future articles, but for now, we want to share how sites are started, how they can be restarted to update the configuration at runtime, and how the entire application behaves when a site fails.
Isolated Sites
Since Beacon supports running multiple sites in the same application, one site site must not affect others when it fails, poisoning the whole system. They need to be isolated from each other, and our approach is to start separated supervision trees for each one:
Let’s suppose we have a single project with the sites :site_a
and :site_b
running,
each serving a home page at http://localhost:4000/site_a and http://localhost:4000/site_b respectively.
Let’s use ab to prove they are able to serve requests:
ab -n 1000 http://localhost:4000/site_a
Complete requests: 1000
Failed requests: 0
ab -n 1000 http://localhost:4000/site_b
Complete requests: 1000
Failed requests: 0
So far, so good. They served 1000 out of the 1000 requested. Now let’s intentionally kill the :site_b
supervisor and see what happens.
We’ll start both ab
commands simultaneously, and then kill the :site_b
before the test ends:
Supervisor.terminate_child(Beacon, :site_b)
The function Supervisor.terminate_child/2
kills the :site_b
supervisor instantly, which is the worst case scenario for a site failure,
simulating a complete outage of that site. Let’s see the results:
ab -n 1000 http://localhost:4000/site_a
Complete requests: 1000
Failed requests: 0
ab -n 1000 http://localhost:4000/site_b
Complete requests: 1000
Failed requests: 458
As expected, :site_a
is still up and running with no requests failed, and :site_b
failed
to serve half of the requests (it could be more or less depending on the timing Supervisor.terminate_child/2
was called).
That’s exactly what we wanted: isolation.
Start and Stop Sites
Now that we know how to kill a site, we should also know how to start it again, even with an update config.
Beacon provides the Beacon.boot/1 function that essentially does that for us:
- Terminates and clean up the site supervisor from the main
Beacon.Supervisor
- Build a spec child based on the new config
- Start the site again with the new config
All sites are started automatically, you don’t need to call that function manually but
let’s suppose we want to update the :mode
config of a running site from :manual
to :live
,
which requires starting some processes and sending some messages,
so to better achieve that we need to reboot the site:
current_config = Beacon.Config.fetch!(:site_a)
new_config = %{current_config | mode: :live}
Beacon.boot(new_config)
You should see in the server logs:
[info] Beacon.Boot booting site site_a
[info] Beacon.Boot finished booting site site_a
{:ok, #PID<0.982.0>}
This operation requires minimal downtime because all resources (layouts, pages, components) are lazy loaded. We’ll learn more about the Loader in an upcoming article.