hudsonbay
I’m in a situation where I have to insert 9000 registries in bulk into the database with Ecto in PostgreSQL.
I already have a dedicated function for inserting an array of elements BulkOperations.bulk_create(MyStruct, list). It’s basically an Ecto.Multi, so everything in the list must be inserted because it’s a transaction.
No problem with that. If I insert a list of 10 elements everything’s OK. But if the list has 9000 registries we have a problem, because I receive a timeout when the connection stays open for more than 15000 ms doing the operations. I mean, 9000 registries
, it takes a lot.
Now, I decided to separate everything by chunks. Like this:
total_rows_affected =
list
|> Enum.chunk_every(20)
|> Enum.map(&BulkOperations.bulk_create(MyStruct, &1))
|> Enum.reduce(0, fn %{rows_affected: rows_affected}, acc ->
rows_affected + acc
end)
%{errors: [], rows_affected: total_rows_affected}
It works for for 9000 registries.
But I feel it’s not secure because if I insert a chunk of elements with &BulkOperations.bulk_create(MyStruct, &1) and that operation is not succesful (because of a database key conflict maybe) then I have a problem because some operations will fail and some others don’t.
My solution was to create a transaction, so everything has to be successful. Like this:
{:ok, result} =
Repo.transaction(fn ->
total_rows_affected =
list
|> Enum.chunk_every(10)
|> Enum.map(&BulkOperations.bulk_create(MyStruct, &1))
|> Enum.reduce(0, fn %{rows_affected: rows_affected}, acc ->
rows_affected + acc
end)
%{errors: [], rows_affected: total_rows_affected}
end)
result
And it works but I receive a timeout when I try to insert 9000 registries because I believe everything is using the same connection opened by Repo.transaction.
So, my question is, how can I insert all of this 9000 registries but making sure that everything is inserted?
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #blog-post
- #ai
- #phoenix_html
- #elixirconf-us
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming










Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
RudManusachi
Have you tried to pass
timeout: :infinityoption toRepo.insert_all/3orRepo.transaction/2?hudsonbay
No, I haven’t. Because I’m worried that if I do that I could overload the server’s memory were the database is hosted. Because it’s too much to insert. Too much work
axelson
Are you using insert_all? If you’re not then you could get a speed up that way.
dimitarvp
Why don’t you try before it so you know for sure?
odix67
I think Jason’s proposal is the best option, btw. honestly, I don’t know of any database that can span a transaction across multiple connections, but I can be wrong.
hudsonbay
Actually, no. I’m not using an
insert_allfunction. It’s aEcto.Multi.insertone when callingBulkOperations.bulk_create/2This is what I did following your advices
But I received :
I don’t know why the client(Phoenix) is closing the connection because I set this to
:infinityI also did it this way:
timeout: :infinityand Phoenix is closing the connection after some timedimitarvp
What does
bulk_createdo exactly?hudsonbay
@dimitarvp it was inserting one by one. I changed it with the help of a friend and it worked.
insert_alldoes the job as @axelson was saying. Now it looks like this:But the problem is that I had to take care of timestamps because
insert_alldoesn’t insert them. So a migration to set a default value ofnow()solves the issue.So, issue solved but I still won’t know why it was closing the connection even when I set the timeout to
:infinitywith the previous function. BTW, now with theinsert_allsolution I can stick to the default timeout of15_000msdimitarvp
If memory serves, the second element in the
:oktuple should already contain the number of items affected.hudsonbay
Yes, but as it’s a transaction, all of the initial items in the list should be affected. That’s why I used
itemsparam withlength(items). Makes sense to you now? Thanks for noticing that, BTW