Recompile_buster - find and fix the files that cause the most recompilation

Hey everyone!

I just released recompile_buster, a Mix task that analyzes your project’s xref graph to surface the modules whose changes trigger the most transitive recompilation.

If you’ve ever changed one file and watched half your project recompile, this tool helps you figure out why and where to fix it.

I was working on a large Elixir codebase where incremental compilation times kept growing. mix xref graph gives you the raw data, but it’s hard to know where to start when you have hundreds of files. I wanted a tool that would rank the worst offenders and give actionable next steps.

It’s also useful as a CI guard, the --fail-above flag lets you fail the build if the number of problematic files exceeds a threshold, so you can prevent compile-time coupling from silently creeping back in.

Here is an example using it in the plausible analytics codebase:

> mix recompile_buster

Files causing excessive recompilation (sorted by transitive deps, 3 levels):

  # | Trans. deps | Recompiles | File
----+-------------+------------+---------------------------------------------
  1 |         308 |          1 | lib/plausible/prom_ex.ex
  2 |         130 |          1 | lib/plausible_web/tracker.ex
  3 |         123 |          2 | lib/plausible/imported.ex
  4 |         114 |          1 | lib/plausible/auth/api_key.ex
  5 |         112 |          2 | lib/plausible/billing/plan.ex
  6 |         109 |          1 | lib/plausible/ingestion/event.ex
  7 |          95 |          1 | lib/plausible/props.ex
  8 |          93 |         32 | lib/plausible_web/live/auth_context.ex
  9 |          90 |          1 | lib/plausible/billing/plans.ex
 10 |          88 |          2 | lib/plausible/billing/feature.ex
 11 |          88 |          7 | lib/plausible/imported/google_analytics4.ex
 12 |          83 |          1 | lib/plausible/stats/imported/base.ex
 13 |          77 |         11 | lib/plausible/imported/importer.ex
 14 |          71 |          1 | extra/lib/plausible/site/tracker_script_id_cache.ex
 15 |          65 |          1 | lib/plausible/stats/sql/expression.ex
 16 |          63 |          7 | lib/plausible/imported/csv_importer.ex
 17 |          55 |          5 | lib/plausible/imported/site_import.ex
 18 |          52 |          4 | extra/lib/plausible/stats/goal/revenue.ex
 19 |          50 |          2 | lib/plausible/sites/index.ex
 20 |          49 |          7 | lib/plausible/imported/universal_analytics.ex

Problematic files: 36 | Total transitive deps: 2357

To start investigating the most problematic file, run:
  mix recompile_buster --explain lib/plausible/prom_ex.ex
> mix recompile_buster --explain lib/plausible/imported/csv_importer.ex

Explain: lib/plausible/imported/csv_importer.ex
===================================
Unique transitive deps (3 levels): 63 | Direct deps: 7 | Recompiles: 7

A) Files that recompile when csv_importer.ex changes (7):
  lib/plausible/imported.ex
  lib/plausible/imported/import_sources.ex
  lib/plausible/imported/site_import.ex
  lib/plausible/stats/imported/base.ex
  lib/plausible/stats/imported/imported.ex
  lib/plausible_web/live/imports_exports_settings.ex
  lib/workers/local_import_analytics_cleaner.ex

B) Direct dependencies of csv_importer.ex (63 unique transitive deps):
  lib/plausible/imported/site_import.ex (27 transitive deps)
  lib/plausible/imported/importer.ex (24 transitive deps)
  lib/plausible/s3.ex (14 transitive deps)
  lib/workers/local_import_analytics_cleaner.ex (7 transitive deps)
  lib/plausible/clickhouse_repo.ex (4 transitive deps)
  lib/plausible/repo.ex (4 transitive deps)
  lib/plausible/ingest_repo.ex

Every file in (A) will recompile if any of the 63 files in (B) changes.
To fix this, break the compile dependency (A) or reduce the transitive deps (B) by removing direct dependencies with the most nested deps.

To investigate why a specific file is in (A), run:
  mix xref graph --source <file> --sink lib/plausible/imported/csv_importer.ex --label compile
17 Likes

This should be part of every Elixir app: From 7 problematic files / 386 transitive deps to zero. in 5 minutes using Claude Code! Incredible work!

1 Like

Wow, this looks really cool!

What is it that makes a module cause lots of recompilation? Is it that it gets used in other modules in some particular way?

The most common problem is: when a module A depends “at compile time” on module B, and module B depends (in any way, even just at run-time) on module C, then A will recompile when C changes. So it’s good practice to move macros, constants etc to their own separated modules that don’t depend on anything else, to avoid unnecessary recompilations

2 Likes