Skip to content

Instantly share code, notes, and snippets.

@applideveloper
Forked from mgwidmann/hotcode.ex
Created July 14, 2016 06:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save applideveloper/dbd562969aaf4f96709126e3a25ae007 to your computer and use it in GitHub Desktop.
Save applideveloper/dbd562969aaf4f96709126e3a25ae007 to your computer and use it in GitHub Desktop.
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"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment