elixir-performance
Find and fix performance problems in portal/ — N+1 queries, missing preloads, unbounded lists/assigns, missing DB indexes, slow context reads. Use when a page or list is slow, the DB is hot, LiveView
SKILL.md.claude/skills/elixir-performance/SKILL.md
GitHubPerformance pass
Measure, then fix the cause. Don't micro-optimize what isn't hot.
The usual suspects (emisar-specific)
- N+1 queries. An association loaded per-row in a loop. Symptom: the same
SELECTrepeated in the log.- Fix: declare the association in the Query module's
preloads/0and pass:preloadtoRepo.fetch/list(IL-10) — neverRepo.preloadinside anEnum.map. Detect: readEnum.*callbacks and comprehensions for context calls orRepo.preload;rg -n 'Repo\.preload' apps/emisar/liblists the preload sites.
- Fix: declare the association in the Query module's
- Unbounded lists. Loading every row, or assigning a big list to the socket.
- Lists that can grow (runs, audit events, runners): page with
Repo.list/3(keyset viacursor_fields), and render withstream/3in LiveView, neverassign(socket, :items, all)(IL-18).
- Lists that can grow (runs, audit events, runners): page with
- Missing indexes. Every
Query.by_*filter, every FK, and thenot_deletedsoft-delete predicate should be backed by an index. Cross-check the Query helpers against the migration; a partial indexwhere deleted_at is nullmatchesnot_deleted/1. - Slow query / wrong plan. Confirm with
Repo.explain(:all, query)iniex -S mix(verify the call exists/shape first) — look forSeq Scanon a large table where an index should apply. - Over-preloading. The opposite problem: loading associations a screen doesn't use. Preload only what the caller renders.
How to run it
cd portal
# repeated identical SELECTs in dev = N+1 (queries log by default in dev)
# profile a specific read in iex:
echo 'Emisar.Repo.explain(:all, Emisar.Runs.ActionRun.Query.all())' | iex -S mix
Read the LiveView's mount/handle_* for list assigns; read the context read path
for preload shape; read the migration for indexes.
Output
Findings ordered by impact: issue · where · cost · fix. Fix the unambiguous ones
(add a preload to preloads/0, switch an assign to a stream, add a missing index in
a NEW migration when the original already ran — IL-11). Leave a judgment call (is
this list big enough to page?) as a flagged question, not a silent change. Re-measure
after fixing.