Don't want to make data skipped by Pagination with sorted data

So there are two ways that I know of


# this probably won't work

# in the genserver
def init(your_paginated_query) do
  {:ok, Repo.stream(your_paginated_query)}
end

def handle_call({:page_after, cursor}, _from, stream) do
  Repo.transaction fn ->
    # do something with the `stream`
  end
end

Or maybe just create a snapshot with Repo.query at the beginning of the user session and then release it in the end, note that creating too many transactions on a database might significantly hurt its performance due to locks. Not sure if it applies here, though. Or you might run out of the checked out ecto(postgrex) connections/processes to the database if you use long running streams …

Also, I’ve found Using Ecto to run a long-running multi-process transaction, might be somewhat relevant, although I think in this case what’s needed is a long-running single process transaction.


  • a “snapshot” in your application (I used this one for a paginated chatbot’s search feature) which would load some arbitrary number of posts into memory (can be genserver per http session) and serve “pagination” requests from there. For the chatbot I loaded about 10 pages (look at your users’ access patterns, you might need to load more) of content into the genserver which had a data structure like
@type page :: [SearchResult.t]
@type paged_snapshot :: %{
  current_page: page,
  pages_before: [page],
  pages_after: [page]
}

@spec next(paged_snapshot) :: paged_snapshot
defp next(%{pages_after: []} = snapshot), do: snapshot # or request a new snapshot
defp next(%{pages_after: [new_current_page | pages_after], pages_before: pages_before, current_page: old_current_page} = snapshot) do
  %{snapshot | pages_after: pages_after, pages_before: [old_current_page | pages_before], current_page: new_current_page}
end

# similar for prev(snapshot)

as its state and was alive only for the duration of the user’s interaction with it (each interaction prolonged its “life” up to a certain limit). You can also create these snapshots only around the time when the “total_post_views” is about to change.


Another way would be to create materialized views for the posts sorted by total_post_views. And keep two of these views in the database when total_post_views is being updated.

2 Likes