Today I upgraded to Elixir 1.19.5 (latest stable) and one thing lead to another, I eventually upgraded most of the dependencies of my app. I got everything working in a couple of hours, but then I noticed a slowness in handling HTTP requests. My controller finishes in a few ms but the browser waits for a couple hundred ms before getting the first byte, and this is on LAN. Further troubleshooting isolate this slowness to:
plug Phoenix.CodeReloader
Does anyone know anything that may have caused this slowness in Phoenix CodeReloader? I noticed that compiling in general for Elixir 1.19 is slower than say, 1,15. Are they related? As a developer, I am willing to spend couple more minutes to compile my app (the type checking and extra warnings are useful) but I am hesitate to spend 200ms for every request served in the dev environment though.
You can try running the server with MIX_DEBUG=1 to get an estimate of where the slowdown is or try one of the Elixir v1.20 release candidates to see if it has been addressed. Compilation has generally gotten faster since v1.15, so it may be something in particular at hand.
Please double check that slowness by comparing older and latest Elixir versions (of the same version of application) - it should be the same/similar.
I mean that I experience that slowness too on older versions of Elixir - this is probably because our app has a lot of cycle dependencies and change of one file entails recompilation of another 200 files completely unnecessary.
quick fix for it - set locally code_reloader to false in config/dev
``config :your_app, BetStackWeb.Endpoint,code_reloader: false,```
Running with MIX_DEBUG=1 shows for every http request:
-> Running mix compile.elixir --purge-consolidation-path-if-stale /home/derek/projects/roastidious/_build/dev/lib/roastidious/consolidated --no-all-warnings (inside Roastidious.MixProject)
<- Ran mix compile.elixir in 274ms
-> Running mix compile.app --purge-consolidation-path-if-stale /home/derek/projects/roastidious/_build/dev/lib/roastidious/consolidated --no-all-warnings (inside Roastidious.MixProject)
<- Ran mix compile.app in 1ms
So a null run of mix compile.elixir is slow. By the way, I was under the impression that code reload shall be triggered by inotify, but in my case nothing was changed, but mix compile.elixir is still ran, for every request. Seems excessive?
-> Running mix compile.elixir --purge-consolidation-path-if-stale /home/derek/projects/roastidious/_build/dev/lib/roastidious/consolidated --no-all-warnings (inside Roastidious.MixProject)
<- Ran mix compile.elixir in 7ms
-> Running mix compile.app --purge-consolidation-path-if-stale /home/derek/projects/roastidious/_build/dev/lib/roastidious/consolidated --no-all-warnings (inside Roastidious.MixProject)
<- Ran mix compile.app in 2ms
So a null build of mix compile.elixir has got a lot slower. I wanted to try 1.20rc but then I have to upgrade my erlang version, which is a lot more complicated.
This performance regression happened between 1.19.0-rc.0 and 1.19.0-rc.1. All packages version pinned down; nothing else was changing. The change log of 1.19.0-rc.1 shows quite a few changes related to mix compile, does anything ring a bell? I am a bit lost on how to debug further though.
My suggestion would be to change Mix.Compilers.Elixir to add some timestamps so you can see the code that slows down. FWIW, I just checked both Tidewave and Livebook and here is what I got:
-> Running mix compile.phoenix_live_view --purge-consolidation-path-if-stale /Users/jose/Work/ts/_build/dev/lib/ts/consolidated --no-all-warnings (inside TS.MixProject)
<- Ran mix compile.phoenix_live_view in 0ms
-> Running mix compile.elixir --purge-consolidation-path-if-stale /Users/jose/Work/ts/_build/dev/lib/ts/consolidated --no-all-warnings (inside TS.MixProject)
<- Ran mix compile.elixir in 7ms
-> Running mix compile.app --purge-consolidation-path-if-stale /Users/jose/Work/ts/_build/dev/lib/ts/consolidated --no-all-warnings (inside TS.MixProject)
<- Ran mix compile.app in 0ms
Also, do you have path dependencies? We fixed recent regressions on the Elixir compiler where some path deps would be marked as stale, leading to additional checks. So don’t forget to try the latest RC.
Yes, I have path dependencies, for both elixir packages and erlang packages. Maybe that’s the culprit? Anyway, I update to OTP28 and Elixir 1.20.0-rc.4, the result seems to be somewhat better:
-> Running mix compile.elixir --purge-consolidation-path-if-stale /home/derek/projects/roastidious/_build/dev/lib/roastidious/consolidated --no-all-warnings (inside Roastidious.MixProject)
<- Ran mix compile.elixir in 163ms
-> Running mix compile.app --purge-consolidation-path-if-stale /home/derek/projects/roastidious/_build/dev/lib/roastidious/consolidated --no-all-warnings (inside Roastidious.MixProject)
<- Ran mix compile.app in 0ms
I will try to use git dependency instead of path dependency and see what happens.
I kind of sense that the compiler was going through all my source files that still have outstanding warnings. Is what the CodeReloader doing equivalent to run:
Mix.Task.rerun("compile.elixir", ...)
from the iex prompt? It returns quite a few warnings consistently; most of them are warnings from expanded macros defined in 3rd party libraries called from inside my files. The same can be observed in running mix compile repeatedly. I’d assume I will see warning only once, because subsequent compilation should be noop; however, I am seeing all warnings again and again. Does the compiler rely on mtime only, or does it somehow cache warnings from the previous invocation and revisit those files?