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 5ac9ab7ff9
commit 68c09b761e
Signed by: moritz
GPG key ID: 1020A035E5DD0824

View file

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