tadasajon

tadasajon

Staging environment? How to debug out-of-memory errors in production on Fly.io?

So I have a hello-world Phoenix app deployed on Fly.io, which I achieved by following this guide: Getting Started · Fly Docs

When I say that the app is a hello-world app, I mean that I literally have not done anything other than run mix phx.new myapp and mix phx.gen.auth Accounts User users. I then added the Dockerfile, fly.toml, etc needed for deployment and successfully deployed the app to Fly.io.

It still shows the “Welcome to Phoenix! Peace of mind from prototype to production” landing page, so I really haven’t touched anything.

Once deployed, the app appears to work fine – I can register for an account, log in, log out, change the background color and redeploy, etc.

But if I come back to the app the next day, the user registration and login system does not work. If I attempt to register a user the app will just crash. This has happened to me twice now, with two separate hello-world Phoenix apps.

This is what the logs show:

app[893322ef] maa [info] [21576.418891] Out of memory: Killed process 510 (beam.smp) total-vm:1388056kB, anon-rss:169764kB, file-rss:0kB, shmem-rss:79684kB, UID:65534 pgtables:704kB oom_score_adj:0
proxy[893322ef] iad [error] error.code=2003 error.message="App connection closed before request/response completed" request.method="POST" request.url="https://quiz-bot.fly.dev/users/register" request.id="01FGW9VXXXXXXXXXX1HVCS67DC" response.status=502
app[893322ef] maa [info] Main child exited with signal (with signal 'SIGKILL', core dumped? false)
app[893322ef] maa [info] Reaped child process with pid: 570 and signal: SIGUSR1, core dumped? false
app[893322ef] maa [info] Starting clean up.
app[893322ef] maa [info] Process appears to have been OOM killed!
runner[893322ef] maa [info] Starting instance
runner[893322ef] maa [info] Configuring virtual machine
runner[893322ef] maa [info] Pulling container image
runner[893322ef] maa [info] Unpacking image
runner[893322ef] maa [info] Preparing kernel init

The error message when I try to register a user is “App connection closed before request/response completed” and also “Process appears to have been OOM killed!”

So one thing that has occurred to me is that there is some kind of crucial difference between my development environment and my production environment that I do not understand. The most obvious candidate is the amount of RAM available.

So what I would like to do is figure out some way to run my phoenix app in the exact same docker configuration that it will have in production – i.e., with the same amount of RAM available. I guess I would call this a “staging environment”.

Is it possible to run a Phoenix app inside docker on my local machine with exactly the same amount of RAM specified as I will have in production? Also, what can I do to make my phoenix app use less RAM?

I also honestly don’t understand why the app would work correctly for a few hours before ceasing to work properly – I would think that an out-of-memory error would either appear immediately or else not appear at all – because I don’t see where I could have a slow memory leak over time, especially if the app is being used by exactly no one.

Marked As Solved

dnsbty

dnsbty

I ran into this same thing, and I would highly recommend looking into the documentation that @ruslandoga posted above. I would especially recommend checking out the section on choosing parameters. The ones that have provided will work, but I think it’s good to understand why they work and make sure you’re making the best tradeoffs for your application.

The m_cost being provided refers to the amount of memory that is used for hashing and the t_cost refers to the amount of time used for hashing (specifically the number of iterations being performed to arrive at the final hash). By default the m_cost is set to 17, which means that 2^17 KiB of memory (128 MiB) will be used out of the 256 MiB provided on the lowest tier Fly.io instance. I have several different applications running on this lowest tier, and it looks like all of them use between 150-170 MiB of memory at rest, so allocating 128 MiB for hashing will cause an OOM every time. The Argon2 RFC recommends that you use the highest amount of memory you can, but you can increase the number of iterations if less memory is available.

The recommendation above to use a t_cost of 4 and an m_cost of 16 should work fine for low traffic applications. 2^16 KiB is 64 MiB of memory, so combined with the memory used at rest would take your instance to somewhere around 225 MiB of memory used. However, if you have a lot of concurrent websocket users or you’re heavily using ets for caching and storage, or if you have multiple concurrent hashing operations, this will still lead to an OOM.

For my applications I decided to go with the following parameters instead:

config :argon2_elixir, t_cost: 18, m_cost: 15

This will use only 32 MiB of memory per hash, but will use several more iterations to obtain that final hash. I arrived at those numbers by following the guide provided in the RFC: decide first on the memory cost and then test the time it takes to create a hash and change the time cost until the total time is around 500ms (where they recommend for security purposes).

That said, 500ms is what’s recommended as the most secure option, but others suggest considering timing as fast as 50ms for user experience purposes. Here are the timings run inside the lowest tier fly.io instances between 50 and 500ms. I would recommend selecting the one that makes the best tradeoffs for your application:

config :argon2_elixir, t_cost: 2, m_cost: 15 # ~60ms
config :argon2_elixir, t_cost: 4, m_cost: 15 # ~115ms
config :argon2_elixir, t_cost: 8, m_cost: 15 # ~208ms
config :argon2_elixir, t_cost: 12, m_cost: 15 # ~307ms
config :argon2_elixir, t_cost: 16, m_cost: 15 # ~415ms
config :argon2_elixir, t_cost: 18, m_cost: 15 # ~460ms
config :argon2_elixir, t_cost: 20, m_cost: 15 # ~525ms

I should add that the most secure solution will be to give more memory to your application, but even with more memory, you should still make sure that the parameters you’re using make the most sense.

24
Post #7

Also Liked

brainlid

brainlid

Yes, argon2 is a “memory hard” hashing algorithm. See: https://www.password-hashing.net/argon2-specs.pdf

This means it requires more RAM to operate. This doesn’t seem to fit in the smallest Fly.io size with the default settings. You can change the defaults like this:

  config :argon2_elixir,
      t_cost: 4,
      m_cost: 16

Or you can use different values for dev/test/prod.

If you switch to bcrypt (the default for new Phoenix applications), it uses an algorithm that is not memory hard and works just fine on the smallest Fly instances.

EDIT: I didn’t catch that the default for new Phoenix apps changed with 1.6 to use {:argon2_elixir, "~> 2.0"}! That’s going to cause problems for people creating new apps on the smallest instances of many platforms.

Morzaram

Morzaram

Hey just wanted to respond to this for anyone else who is looking for help.

I can confirm on {:argon2_elixir, "~> 2.0"} (currently at 2.4 as of this post)

Putting the following in runtime.exs and deploying to fly.io w/ lowest plan works smoothly.
config :argon2_elixir, t_cost: 4, m_cost: 16

Thank you @brainlid !

kamidev

kamidev

Yes, this is valuable info for anyone looking at upgrading to argon. Thank you!

Note that from Phoenix 1.6.1 the default for new apps has reverted to bcrypt. From the changelog:

“phx.gen.auth] No longer set argon2 as the default hash algorithm for phx.gen.auth in favor of bcrypt for performance reasons on smaller hardware”

Where Next?

Popular in Questions Top

baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
johnnyicon
Hi all, I’ve just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I’m trying to use Postgres...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
JulienCorb
I am trying to implement my new.html.eex file to create new posts on my website. new.html.eex: <h1>Create Post</h1> <%= ...
New
Emily
I have VueJS GUIs with the project generated using Webpack. I have Elixir modules that will need to be used by the VueJS GUIs. I forese...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
dblack
I’ve got an issue with an app and I’ve no idea of how to troubleshoot it. I’m hoping someone here might have seen something similar. I p...
New
PeterCarter
There are pre-rolled solutions for other frameworks that do work. However, Phoenix does not seem to have these. Have people had good expe...
New

Other popular topics Top

aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
marius95
Hello everyone, I try to use an Javascript Event Handler in my root.html.leex file. Therefore I created a function in the app.js file: ...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New
Emily
I have VueJS GUIs with the project generated using Webpack. I have Elixir modules that will need to be used by the VueJS GUIs. I forese...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New

Latest on Elixir Forum

Elixir Forum

We're in Beta

About us Mission Statement