perf: optimize load_user_counts with DB-side aggregation
All checks were successful
continuous-integration/drone/push Build is passing
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:
parent
08182300b9
commit
5998fb643d
1 changed files with 15 additions and 20 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue