nathany

nathany

GitHub Action Cache Elixir always recompiles dependencies (Elixir 1.13.3)

Looking to speed up our build on GitHub Actions. Right now the slowest task is compiling dependencies, which takes over 3 minutes. This is surprising, because the _build folder should be cached, but it behaves as though it isn’t.

I’ve found that the command mix deps.compile does a full recompile locally too, even if I run it twice in a row. Not sure if this is a regression in Elixir 1.13 or expected behaviour?

I’ll walk through the relevant portions of the GitHub Actions config.

setup-beam

For starts, using erlef/setup-beam, which is fairly standard. I don’t know if we need to specify anything extra for rebar? This is a fairly standard Phoenix app.

env:
  MIX_ENV: test
steps:
  - uses: actions/checkout@v2
  - uses: erlef/setup-beam@v1
    with:
      otp-version: "24.2.1"
      elixir-version: "1.13.3"

cache

Then onto actions/cache which is using the standard example for Elixir, other than that we’ve added a v2- prefix at some point to bust an old cache.

- uses: actions/cache@v2
  id: cache
  with:
    path: |
      deps
      _build
    key: v2-${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }}
    restore-keys: |
      v2-${{ runner.os }}-mix-

download dependencies

Downloading dependencies, including some private ones, is a step that we can reliably skip if there is a cache-hit. If the lock file hasn’t changed, everything still works and these two steps take zero seconds, proving that the cache is working.

- name: Authenticating Hex
  if: steps.cache.outputs.cache-hit != 'true'
  run: mix hex.organization auth ${ORG_NAME} --key ${HEX_ORG_KEY}
  env:
    HEX_ORG_KEY: ${{ secrets.HEX_ORG_KEY }}
    ORG_NAME: ${{ secrets.ORG_NAME }}
- name: Install dependencies
  if: steps.cache.outputs.cache-hit != 'true'
  # NOTE: AppSignal has its hooks in other dependencies, so we tell it to compile first
  run: |
    mix do deps.get, deps.compile appsignal --include-children

We do have some special handling for AppSignal here (which should maybe be moved into a separate step).

code formatting

We found that we can check code formatting before compiling, which saves some cycles by “failing fast.”

- name: Check code formatting
  run: mix format --check-formatted

Compiling

The main event. I recently separated out the compiling of dependencies, just to see the time they take vs. our app. The dependencies take 3-4 minutes to compile, whereas our app takes about 30 seconds or so.

- name: Compile dependencies
  run: |
    mix deps.compile
- name: Compile
  run: |
    mix compile --warnings-as-errors

If I skip the deps.compile step then compile will compile all the deps.

If the mix.lock file hasn’t changed, and if the _build folder is cached, then I would hope that deps.compile would be very fast or could even be skipped.

But that isn’t the case right now. :cry:

Further steps

There are further steps for static code analysis and running the tests (which incidentally takes less time than compiling dependencies). I’m going to stop here because it’s the compilation that I’m interested in.

If you have any suggestions, please let me know. Also happy to open a bug with Elixir if you think it’s a regression.

Marked As Solved

nathany

nathany

After experimenting with the previously linked stand-alone repository, I made the following changes:

  • Combine Elixir & JS tests into one job. This reduces likelihood of contention on the cache, which can result in a “another job may be creating this cache” error (our JS tests need Elixir & Phoenix).
  • Remove AppSignal recompile fix :crossed_fingers:t2:
  • Don’t compile dependencies separately, as this always recompiles Erlang deps
  • Rev GitHub Actions cache to build a fresh cache

With the cache working properly, the compile step is taking around 20 seconds instead of 3.5 minutes or so.

Thanks again for the assistance and examples,
Nathan.

Also Liked

josevalim

josevalim

Creator of Elixir

There is also mix loadpaths, which is actually what is called by mix compile. It is a public task although it does not show up on mix help. It won’t compile the current app but it will check and compile missing dependencies. Another option is to provide something like mix compile --only-deps.

josevalim

josevalim

Creator of Elixir

mix deps.compile will attempt to compile all dependencies. Rebar3 logs, but the truth is that all dependencies are being checked, which does not occur on mix compile. So unless someone contributes a mix deps.compile --only-stale or something of sorts, a mix deps.compile step will always do more work than necessary.

josevalim

josevalim

Creator of Elixir

If you can isolate this error into a smaller app with reproduction steps, I will be glad to take a look at it!

Last Post!

josevalim

josevalim

Creator of Elixir

There is also mix loadpaths, which is actually what is called by mix compile. It is a public task although it does not show up on mix help. It won’t compile the current app but it will check and compile missing dependencies. Another option is to provide something like mix compile --only-deps.

Where Next?

Popular in Questions Top

JeremM34
Hello, how can I check the Phoenix version ? Thanks !
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
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
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
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New

Other popular topics Top

electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 44139 214
New
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New

We're in Beta

About us Mission Statement