Open cytim opened 6 months ago
Hello!
I had the same issue and if like me you know you can't have null values in your cursor fields, try to use the option sort_direction: :asc_nulls_first
to avoid making unnecessary queries. If this option is not passed, it defaults to :asc_nulls_last
.
from(
u in User,
order_by: [asc: :id]
)
|> Repo.paginate(
cursor_fields: [:id],
sort_direction: :asc_nulls_first,
after: "g3QAAAABdwJpZGIAAYag",
limit: 1
)
AscNullsLast
and AscNullsFirst
modulesdefmodule Paginator.Ecto.Query.AscNullsLast do
...
def build_dynamic_filter(args = %{direction: :after}) do
dynamic(
[{query, args.entity_position}],
(^field_or_expr_equal(args) and ^args.next_filters) or
^field_or_expr_greater(args) or
^field_or_expr_is_nil(args)
)
end
...
end
defmodule Paginator.Ecto.Query.AscNullsFirst do
...
def build_dynamic_filter(args = %{direction: :after}) do
dynamic(
[{query, args.entity_position}],
(^field_or_expr_equal(args) and ^args.next_filters) or
^field_or_expr_greater(args)
)
end
...
end
Problem
Hi there :) I found a problem that the pagination query is slow if
cursor_fields
contains the primary key. Or more specifically, it is slow ifcursor_fields
hits an index whichnull
values are excluded.Investigation
Take the below query as an example.
It will generate the following SQL.
This is the Query Plan for the SQL.
From the Query Plan, the
users_pkey
index is used, butFilter
is applied instead ofIndex Cond
.I customised the SQL by removing
id IS NULL
from it. Here's the new Query Plan.This time
Index Cond
is applied and theExecution Time
improves by a lot.I guess that the primary index does not index (and expect) any null values. Therefore, the null-checking will not hit the index condition and have to be done by filtering, which is slow.
Suggestion
One possible fix is to allow skipping the null-checking conditionally. For example, we can add a
nullable?
option tocursor_fields
.