joaquinalcerro
What is the best approach for fetching large amount of records from postgresql with ecto
Hi there,
I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records approximately (15MB).
Each record in the table has a email message id with which I have to call Google Gmail API to get the message.
So I am thinking about memory efficient. What is the most efficient way to call all the records with the minimum impact in memory or not loading the hole 15MB at once just to make API calls?
I found Ecto Stream but it works inside a transaction and I don’t use transactions. And… is Ecto Stream the best way to go?
Thanks for any comments.
Regards.
Most Liked
josevalim
IMO stream is the way to go. You need to run inside a transaction but that should not be a problem. You should be able to do neat things such as:
Repo.stream(...)
|> Task.async_stream(&deliver/1, max_concurrency: 10)
|> Stream.run
and that will get emails in batches of 10 and invoke the google email api concurrently. It requires Elixir v1.4.
michalmuskala
You can increase the timeout for the transaction where streaming happens:
Repo.transaction(fn -> ... end, timeout: some_huge_number)
You need to remember that LIMIT + OFFSET pagination is prone to race conditions - it may happen that you’ll miss some rows or see rows that you shouldn’t. Database-level cursor gives a consistent view of data.
brightball
Detailed explanation there: http://use-the-index-luke.com/no-offset
Short version, when iterating over the records get the max range and the min range to make sure it’s used in your where clause. This trims the result set BEFORE sorting. Using LIMIT and OFFSET means the entire possible result has to be loaded and sorted to calculate which chunk of them is coming back.
josevalim
LIMIT + OFFSET is also linear. When getting the last 100 in the 1 million, you will go through the first 99900. Using an ID or a stream with a timeout of :infinity would be preferred I would say.
mbuhot
After hitting transaction timeouts with Repo.stream we switched to a pagination with Scriviner.
The interface is emulating Stream.chunk but uses a query as the source:
@doc """
Stream chunks of results from the given queryable.
Unlike Repo.stream, this function does not keep a long running transaction open.
Hence, consistency is not guarenteed in the presence of rows being deleted or sort criteria changing.
## Example
Ecto.Query.from(u in Users, order_by: [asc: :created_at])
|> Repo.chunk(100)
|> Stream.map(&process_batch_of_users)
|> Stream.run()
"""
@spec chunk(Ecto.Queryable.t, integer) :: Stream.t
def chunk(queryable, chunk_size) do
chunk_stream = Stream.unfold(1, fn page_number ->
page = Repo.paginate(queryable, page: page_number, page_size: chunk_size)
{page.entries, page_number + 1}
end)
Stream.take_while(chunk_stream, fn [] -> false; _ -> true end)
end







