perf: optimize load_user_counts with DB-side aggregation
All checks were successful
continuous-integration/drone/push Build is passing

Replace Elixir-side counting with Ecto GROUP BY COUNT query for
better performance. This avoids loading all users into memory and
performs the aggregation directly in the database.
This commit is contained in:
Moritz 2026-01-08 15:58:53 +01:00
parent 08182300b9
commit 5998fb643d
Signed by: moritz
GPG key ID: 1020A035E5DD0824

View file

@ -126,33 +126,28 @@ defmodule MvWeb.RoleLive.Index do
end end
end end
# Loads all user counts for roles in a single query to avoid N+1 queries # Loads all user counts for roles using DB-side aggregation for better performance
# TODO: Optimize to use DB-side aggregation instead of loading all users
@spec load_user_counts([Mv.Authorization.Role.t()], map() | nil) :: %{ @spec load_user_counts([Mv.Authorization.Role.t()], map() | nil) :: %{
Ecto.UUID.t() => non_neg_integer() Ecto.UUID.t() => non_neg_integer()
} }
defp load_user_counts(roles, actor) do defp load_user_counts(roles, _actor) do
role_ids = Enum.map(roles, & &1.id) role_ids = Enum.map(roles, & &1.id)
# Load all users with role_id in a single query # Use Ecto directly for efficient GROUP BY COUNT query
opts = opts_with_actor([], actor, Mv.Accounts) # This is much more performant than loading all users and counting in Elixir
# Note: We bypass Ash here for performance, but this is a simple read-only query
import Ecto.Query
users = query =
case Ash.read( from u in Accounts.User,
Accounts.User where: u.role_id in ^role_ids,
|> Ash.Query.filter(role_id in ^role_ids) group_by: u.role_id,
|> Ash.Query.select([:role_id]), select: {u.role_id, count(u.id)}
opts
) do
{:ok, users_list} -> users_list
{:error, _} -> []
end
# Group by role_id and count results = Mv.Repo.all(query)
users
|> Enum.group_by(& &1.role_id) results
|> Enum.map(fn {role_id, users_list} -> {role_id, length(users_list)} end) |> Enum.into(%{}, fn {role_id, count} -> {role_id, count} end)
|> Map.new()
end end
# Gets user count from preloaded assigns map # Gets user count from preloaded assigns map