Upgrades and Downgrades
A word of caution
In general, unless you have a strong reason for using hot upgrades, it’s almost always simpler to just do rolling releases. Hot upgrades are an amazing capability, but they are deceptively simple to use due to the automatic appups. If you get to the point where you need to write your own appups, you will need to become intimately familiar with them, and there is a lot of complexity hidden there. However, there is nothing preventing you from using upgrades until you hit that point, doing a rolling release, then continuing with upgrades from there - it ultimately depends on your needs. I would caution anyone thinking of using them to evaluate whether they are truly necessary for their use case.
IMPORTANT: It is not possible to use hot upgrades and include_erts: false
in conjunction with one
another. This is due to how release_handler
manages information about releases as they are unpacked
and installed. ERTS must be packaged in the release in order for hot upgrades/downgrades to properly work.
A bit about upgrades
When building upgrade releases, Distillery will ensure that all modified
applications have appups generated for them. Appups are what tell :systools
how
to generate the relup
file, which are low-level instructions for the release handler,
which tell it how to load/remove/change modules in the system during a hot upgrade.
Without an appup, an upgrade cannot succeed, because the release handler will not know
how to upgrade that application. Distillery is very intelligent about ordering instructions
based on additions/deletions/changes to modules, based on whether they are special processes
(gen_server
, supervisor
, proc_lib
-started apps), and their dependencies to each other.
However, while Distillery’s appup generator is quite good, it can’t be perfect for all applications,
or all situations. There will be times when you need to modify these appups, or provide your own.
For instance, you may need to upgrade state of a gen_server
between one version and another based
on some external state. Appups provide facilities for passing extra data to the code change handler
for these situations. Distillery cannot know what data to provide, or when it’s needed, and that’s when
you’ll need to step in.
Appups
NOTE: This is a copy of my wiki article on appups in the relx
repository.
Ok, so you’ve generated a release, deployed it to your target system, made some code changes in development, and now you want to generate a release package that will allow you to do a hot upgrade. In order to do this, you must create a project.appup file, which will go in the ebin directory of your production build.
There is no real clear example of how appups are supposed to be built, so the following example is intended to help you get started. For more complicated application upgrades, you’ll want to check out the Appup Cookbook.
Given a sample application called test
, with a supervisor (TestSup
), and a gen_server
(TestServ
),
you should have a test.app
file in _build/<env>/lib/test/ebin
that looks something like the following:
{application,test,
[{registered,[]},
{description,"test app"},
{mod,{test,[]}},
{applications,[stdlib,kernel]},
{vsn,"0.0.1"},
{modules,['Elixir.Test','Elixir.TestServ',
'Elixir.TestSup']}]}.
If you make code changes to, test_server
for instance, the following is a simple appup file that will
load your project’s application, and call code_change/3
on test_server
. The first
"0.1.0"
block is the order of operations for the upgrade, the second one is the order of operations for the
downgrade (note that they should be in reverse order of the upgrade instructions).
{"0.2.0",
[{"0.1.0",[{update,'Elixir.TestServ',{advanced,[]},[]}]}],
[{"0.1.0",[{update,'Elixir.TestServ',{advanced,[]},[]}]}]}.
Note that the {advanced, []}
tuple in each of the blocks is where you would pass additional arguments to code_change
, if needed.
It is important that you order the instructions such that processes which depend on each other are upgraded
in an order compatible with the dependencies between them. If you have proc_a
and proc_b
, and proc_a
calls
proc_b
for something, upgrade proc_b
first, then proc_a
. When processes are upgraded, they are suspended
during the upgrade, but in-flight requests will be handled by the old version, until the upgrade is complete and
the new version is un-suspended.
This is the simplest case for an appup, but it covers the gist of the process. The Appup Cookbook should be your reference when authoring appups, as it goes into great detail all of the steps, what each instruction type does, and more.
To generate an upgrade release, you’ll need to pass --upgrade
to mix release
. To generate an upgrade from an arbitrary
version you’ve previously built, pass --upfrom=<version>
. Distillery will look for the appup in the ebin of your current build.