Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Hot code swapping
# To show hot code uploading, we first need to build a simple phoenix project so we can see it happen in real time.
# Start by making a new phoenix project
$ mix phoenix.new hotcode
# Go into the directory
$ cd hotcode
# Add exrm dependency to mix.exs file
{:exrm, "~> 1.0.3"}
# Fetch dependencies and start up the server
$ mix deps.get
$ iex -S mix phoenix.server
# In router.ex uncomment the API section and add the following get line so it looks like below
scope "/api", Hotcode do
pipe_through :api
get "/fib/:number", PageController, :fib
end
# In page_controller.ex add the following function to serve a request for a fibonacci number
def fib(conn, %{"number" => number}) do
{time, answer} = :timer.tc __MODULE__, :time_fib, [number]
render conn, "fib.json", answer: answer, time: time / 1_000
end
# Add this method below it (not private or :timer cannot call it)
def time_fib(number) do
number |> String.to_integer |> Hotcode.Fib.fib
end
# In views/page_view.ex add the json view
def render("fib.json", data) do
%{time: data.time, answer: data.answer}
end
# Now we need to add the function `Hotcode.Fib.fib` that we just wrote (or it won't be found)
# Create a new named lib/hotcode/fib.ex and put the following contents:
defmodule Hotcode.Fib do
def fib(0), do: 1
def fib(1), do: 1
def fib(n), do: fib(n - 2) + fib(n - 1)
end
# This implementation is simple, but very inefficient. It recomputes numbers that have already been computed.
# For example, fib(4) would be computed as:
# fib(2) + fib(3)
# fib(0) + fib(1) + fib(1) + fib(2) # Notice the recompute of 2 here
# 1 + 1 + 1 + fib(0) + fib(1)
# 1 + 1 + 1 + 1 + 1
#
# For even small numbers, like 40, it can take several seconds to compute because this creates a large tree of function calls
# This is our bug, that we want to hot code upload to fix, because we supposedly don't realize it until its in production and
# causing problems, spinning up the CPU horribly.
#
# Lets test out our endpoint
# Start up ther server
$ mix phoenix.server
# Make a request to calculate fib of 40
$ curl http://localhost:4000/api/fib/40
{"time":4566.591,"answer":165580141}
# Ouch, 4.5 seconds... Lets set up our prod deployment
# Change our config/prod.exs to have the following config
config :hotcode, Hotcode.Endpoint,
http: [port: 5000], # Dev runs on 4000, this will let us know we're hitting prod
url: [host: "127.0.0.1"],
cache_static_manifest: "priv/static/manifest.json",
server: true # Necessary because we can't run mix phoenix.server to start prod
# Now to build the prod release
$ MIX_ENV=prod mix do compile, phoenix.digest, release
# We can boot it up and daemonize it with
$ rel/hotcode/bin/hotcode start
# Confirm that its up
$ rel/hotcode/bin/hotcode ping
# Should print out "pong" if it is up
# And to test it out
$ curl http://localhost:5000/api/fib/40
# Create a script to put load on the server
$ vim punish
#!/bin/bash
while :
do
# One curl for every core, then sleep 1 second. If each of the 8 cores can't deal with one request
# in under a second, we'll begin to overload the CPU and each request will get longer
curl http://localhost:5000/api/fib/36 && echo &
curl http://localhost:5000/api/fib/36 && echo &
curl http://localhost:5000/api/fib/36 && echo &
curl http://localhost:5000/api/fib/36 && echo &
curl http://localhost:5000/api/fib/36 && echo &
curl http://localhost:5000/api/fib/36 && echo &
curl http://localhost:5000/api/fib/36 && echo &
curl http://localhost:5000/api/fib/36 && echo &
sleep 1
done
# Make it executable
$ chmod a+x punish
# Before we `punish` our server, lets set up the next release so we have control when to switch the release
# Switch mix.exs to version 0.0.2
# Change fibonacci implementation to a faster version
defmodule Hotcode.Fib do
def fib(n), do: fib(0, n, {0, 0})
defp fib(n, n, {prev, cur}), do: prev + cur
defp fib(0, n, {prev, cur}), do: fib(1, n, {0,1})
defp fib(m, n, {prev, cur}), do: fib(m + 1, n, {cur, prev + cur})
end
# Need to tell Erlang VM how to upgrade the code, need more research into what this does :/
$ vim rel/hotcode.appup
{"0.0.2",
[{"0.0.1",
[{load_module,'Elixir.Hotcode.Endpoint',brutal_purge,soft_purge,[]},
{load_module,'Elixir.Hotcode.Fib',brutal_purge,soft_purge,[]},
{load_module,'Elixir.Hotcode.ErrorHelpers'},
{load_module,'Elixir.Hotcode.Gettext'},
{load_module,'Elixir.Hotcode.Repo'},
{load_module,'Elixir.Hotcode.Router.Helpers'},
{load_module,'Elixir.Hotcode.Router',brutal_purge,soft_purge,[]}]}],
[{"0.0.1",
[{load_module,'Elixir.Hotcode.Endpoint',brutal_purge,soft_purge,[]},
{load_module,'Elixir.Hotcode.Fib',brutal_purge,soft_purge,[]},
{load_module,'Elixir.Hotcode.ErrorHelpers'},
{load_module,'Elixir.Hotcode.Gettext'},
{load_module,'Elixir.Hotcode.Repo'},
{load_module,'Elixir.Hotcode.Router.Helpers'},
{load_module,'Elixir.Hotcode.Router',brutal_purge,soft_purge,[]}]}]}.
# Before punishing, lets create the release so it doesnt make the CPU spin up too hard
$ MIX_ENV=prod mix do compile, phoenix.digest, release
# Note that letting this run for too long will cause bash to throw an exception because it can
# not fork anymore (causes resource temporarily unavailable exception), too many processes open at once...
$ ./punish
# When we feel like it hurts too much
$ rel/hotcode/bin/hotcode upgrade "0.0.2"
@mgwidmann

This comment has been minimized.

Copy link
Owner Author

commented Apr 12, 2016

@watsy0007

This comment has been minimized.

Copy link

commented Jun 14, 2018

awesome

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.