Last active
September 25, 2022 19:57
-
-
Save mgwidmann/7162c6da59e027a3f0617575fb147897 to your computer and use it in GitHub Desktop.
Hot code swapping
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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
Slides on exrm:
http://slides.com/paulschoenfelder/elixirconf2015-release-management#/