improvement: fix significant performance issue in lateral joins

fix: ensure source table is sorted in lateral join
This commit is contained in:
Zach Daniel 2021-07-19 13:56:36 -04:00
parent 3cbf11df36
commit 487c9f32d6
4 changed files with 114 additions and 64 deletions

View file

@ -18,7 +18,7 @@ jobs:
matrix:
otp: ["23"]
elixir: ["1.11.0"]
ash: ["master", "1.46.3"]
ash: ["master", "1.46.11"]
pg_version: ["9.6", "11"]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View file

@ -559,6 +559,16 @@ defmodule AshPostgres.DataLayer do
source_query = Ash.Query.new(source_query)
subquery =
if query.windows[:order] do
subquery(
from(destination in query,
select_merge: %{__order__: over(row_number(), :order)},
where:
field(destination, ^destination_field) ==
field(parent_as(:source_record), ^source_field)
)
)
else
subquery(
from(destination in query,
where:
@ -566,6 +576,7 @@ defmodule AshPostgres.DataLayer do
field(parent_as(:source_record), ^source_field)
)
)
end
source_query.resource
|> Ash.Query.set_context(%{:data_layer => source_query.context[:data_layer]})
@ -580,14 +591,28 @@ defmodule AshPostgres.DataLayer do
end
|> case do
{:ok, data_layer_query} ->
if query.windows[:order] do
{:ok,
from(source in data_layer_query,
as: :source_record,
where: field(source, ^source_field) in ^source_values,
inner_lateral_join: destination in ^subquery,
on: field(source, ^source_field) == field(destination, ^destination_field),
select: destination
order_by: destination.__order__,
select: destination,
distinct: true
)}
else
{:ok,
from(source in data_layer_query,
as: :source_record,
where: field(source, ^source_field) in ^source_values,
inner_lateral_join: destination in ^subquery,
on: field(source, ^source_field) == field(destination, ^destination_field),
select: destination,
distinct: true
)}
end
{:error, error} ->
{:error, error}
@ -628,7 +653,6 @@ defmodule AshPostgres.DataLayer do
|> Ash.Query.set_context(%{:data_layer => source_query.context[:data_layer]})
|> set_lateral_join_prefix(query)
|> Ash.Query.do_filter(relationship.filter)
|> Ash.Query.sort(Map.get(relationship, :sort))
|> case do
%{valid?: true} = query ->
Ash.Query.data_layer_query(query)
@ -638,6 +662,31 @@ defmodule AshPostgres.DataLayer do
end
|> case do
{:ok, data_layer_query} ->
if query.windows[:order] do
subquery =
subquery(
from(destination in query,
select_merge: %{__order__: over(row_number(), :order)},
join: through in ^through_query,
on:
field(through, ^destination_field_on_join_table) ==
field(destination, ^destination_field),
where:
field(through, ^source_field_on_join_table) ==
field(parent_as(:source_record), ^source_field)
)
)
{:ok,
from(source in data_layer_query,
as: :source_record,
where: field(source, ^source_field) in ^source_values,
inner_lateral_join: destination in ^subquery,
select: destination,
order_by: destination.__order__,
distinct: true
)}
else
subquery =
subquery(
from(destination in query,
@ -655,9 +704,11 @@ defmodule AshPostgres.DataLayer do
from(source in data_layer_query,
as: :source_record,
where: field(source, ^source_field) in ^source_values,
inner_lateral_join: through in ^subquery,
select: through
inner_lateral_join: destination in ^subquery,
select: destination,
distinct: true
)}
end
{:error, error} ->
{:error, error}
@ -957,8 +1008,8 @@ defmodule AshPostgres.DataLayer do
sort
|> sanitize_sort()
|> Enum.reduce_while({:ok, query}, fn
{order, %Ash.Query.Calculation{} = calc}, {:ok, query} ->
|> Enum.reduce_while({:ok, %Ecto.Query.QueryExpr{expr: [], params: []}}, fn
{order, %Ash.Query.Calculation{} = calc}, {:ok, query_expr} ->
type =
if calc.type do
Ash.Type.ecto_type(calc.type)
@ -976,27 +1027,17 @@ defmodule AshPostgres.DataLayer do
})
|> case do
{:ok, expr} ->
{params, expr} = do_filter_to_expr(expr, query.__ash_bindings__, [], false, type)
{params, expr} =
do_filter_to_expr(expr, query.__ash_bindings__, query_expr.params, false, type)
new_query =
Map.update!(query, :order_bys, fn order_bys ->
order_bys = order_bys || []
sort_expr = %Ecto.Query.QueryExpr{
expr: [{order, expr}],
params: params
}
order_bys ++ [sort_expr]
end)
{:cont, {:ok, new_query}}
{:cont,
{:ok, %{query_expr | expr: query_expr.expr ++ [{order, expr}], params: params}}}
{:error, error} ->
{:halt, {:error, error}}
end
{order, sort}, {:ok, query} ->
{order, sort}, {:ok, query_expr} ->
expr =
case Map.fetch(query.__ash_bindings__.aggregates, sort) do
{:ok, binding} ->
@ -1047,21 +1088,30 @@ defmodule AshPostgres.DataLayer do
{{:., [], [{:&, [], [0]}, sort]}, [], []}
end
new_query =
Map.update!(query, :order_bys, fn order_bys ->
order_bys = order_bys || []
{:cont, {:ok, %{query_expr | expr: [query_expr.expr ++ {order, expr}]}}}
end)
|> case do
{:ok, %{expr: []}} ->
{:ok, query}
sort_expr = %Ecto.Query.QueryExpr{
expr: [
{order, expr}
]
}
{:ok, sort_expr} ->
new_query =
query
|> Map.update!(:order_bys, fn order_bys ->
order_bys = order_bys || []
order_bys ++ [sort_expr]
end)
{:cont, {:ok, new_query}}
|> Map.update!(:windows, fn windows ->
order_by_expr = %{sort_expr | expr: [order_by: sort_expr.expr]}
Keyword.put(windows, :order, order_by_expr)
end)
{:ok, new_query}
{:error, error} ->
{:error, error}
end
end
@impl true

View file

@ -95,7 +95,7 @@ defmodule AshPostgres.MixProject do
{:ecto_sql, "~> 3.5"},
{:jason, "~> 1.0"},
{:postgrex, ">= 0.0.0"},
{:ash, ash_version("~> 1.46 and >= 1.46.3")},
{:ash, ash_version("~> 1.46 and >= 1.46.11")},
{:git_ops, "~> 2.4.3", only: :dev},
{:ex_doc, "~> 0.22", only: :dev, runtime: false},
{:ex_check, "~> 0.11.0", only: :dev},

View file

@ -1,5 +1,5 @@
%{
"ash": {:hex, :ash, "1.46.3", "61f4513810fa6ae401f2290ee45bc93db1fa7a289b5d5b71d70d8a2c6a83a5b6", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.5", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.1.5", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:timex, ">= 3.0.0", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "bdc4c22e4770204b712301f9f18f9b9f687931cc499a0516f1974cbee00cf48c"},
"ash": {:hex, :ash, "1.46.11", "bc404875dad27ed5a9e4fc2ad5369287342da1eeb571ec1b85b21c024515b9f7", [:mix], [{:comparable, "~> 1.0", [hex: :comparable, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8.0", [hex: :ets, repo: "hexpm", optional: false]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.3.5", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.1.5", [hex: :picosat_elixir, repo: "hexpm", optional: false]}, {:timex, ">= 3.0.0", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "685814bb094dbc2cc4c9dd460778670f6d248c006fcd02e8a75fe173350bb0b1"},
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
"certifi": {:hex, :certifi, "2.6.1", "dbab8e5e155a0763eea978c913ca280a6b544bfa115633fa20249c3d396d9493", [:rebar3], [], "hexpm", "524c97b4991b3849dd5c17a631223896272c6b0af446778ba4675a1dff53bb7e"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
@ -28,7 +28,7 @@
"makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
"nimble_options": {:hex, :nimble_options, "0.3.5", "a4f6820cdcb4ee444afd78635f323e58e8a5ddf2fbbe9b9d283a99f972034bae", [:mix], [], "hexpm", "f5507cc90033a8d12769522009c80aa9164af6bab245dbd4ad421d008455f1e1"},
"nimble_options": {:hex, :nimble_options, "0.3.6", "365d03c05d43483d3eacf820671dafce5b49d692667b3bb8cae28447fd2414ef", [:mix], [], "hexpm", "1c1d3536c4aee1be2c8f3c691bf27c62dbd88d9bb3a0b1a011913453932e8c15"},
"nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"},
"parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
"picosat_elixir": {:hex, :picosat_elixir, "0.1.5", "23673bd3080a4489401e25b4896aff1f1138d47b2f650eab724aad1506188ebb", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "b30b3c3abd1f4281902d3b5bc9b67e716509092d6243b010c29d8be4a526e8c8"},