Learn Elixir | Hacky Hot Code Reloading
Blog Image

Development

Hacky Hot Code Reloading in Elixir

Elixir OTP Hot Code Reloading
Photo of Mika Kalathil

Mika Kalathil

April 13th 2022

How not to update a production system

Hot code reloading is often seen as a tricky and hard way to maintain and deploy your code into production, but what if I told you that updating our modules could be as easy as copy and pasting our updated module into the production environment. This is possible with Elixir.

What the heck are hot code reloads?

Hot code reloads allow us to modify our runtime production system. Unlike a traditional release, which must fully stop and restart the server, a hot code upgrade is capable of updating the code while the server is still online without disconnecting your users. The tradeoff however is when we have processes, like a GenServer that holds state, you need to specify in your code how to upgrade the state. You also need to generate Appups and Relups which thankfully are very well documented in both the mix release hexdocs as well as the appupp cookbook from Erlang.

Unsafe Upgrades

Now to the fun parts, using the unsafe way we can take advantage of our remote shell bundled into our elixir releases. In this environment, you’re directly apart of your production environment, this means you can execute code that is part of the production environment.

Inside the IEx shell, we have a lot of functionality available to us, including but not limited to defining modules, but what would happen if we redefine a module that’s being used in production? To figure this out we can check out this example, where we first load ourselves up into a production release of our application, in our example, we’ll just load up a simple phoenix application with an api to just add a couple numbers together.

# router.ex
get "/calculate/:a/:b", CalculationController, :run

# calculator.ex
defmodule Calculator do
  def add(a, b), do: a + b
end

# calculation_controller.ex
defmodule CalculationController do
  def run(conn, %{"a" => a, "b" => b}) do
     # Returning the results of Calculator.add(a, b) to a string
     text(conn, Calculator.add(a, b))
  end
end

We’ll boot into our release with ./bin/calculator start (for more info on releases, I recommend looking at the elixir release docs) and we’ll see the friendly Phoenix on port 4000 message from when we boot an application.

Now in a seperate shell we can query this from the terminal (I use httpie for it’s simplicity) like so:

$ http get "http://localhost:4000/calculate/2/2"
2

In this shell, let’s open up a new remote console into the production environment with ./bin/calculator remote (this is built in with releases, which again, I highly recommend you check out if you haven’t used them already!). We can then redefine our calculator.ex file by copy and pasting this module in:

defmodule Calculator do
  def add(a, b), do: (a * 2) + (b * 2)
end

We can close the shell and try our query again to calculator with the same params:

$ http get "http://localhost:4000/calculate/2/2"
8

Our result is now 8 and we haven’t restarted our calculator app at all! In essence we’ve done a hot code reload without all the complications of AppUps. Is it worth it? Probably not!!

Is this a good thing?

In all seriousness, while this is nifty, and maybe even could come in handy during a pinch, I would try and stear clear of updating your modules in production like this, and fall back to real hot code reloading with AppUps, or just use a more normal method of deployment. The good thing to know, is that our commands in remote shell from our release, can have a impact on our production system and we need to be careful of that.

Special Thanks

Special shout out to my co-worker Carter Pederson for helping discover this as well as helping to edit this