Yegair

Yegair

How do Ecto Repo.checkout timeouts work in detail?

Hello everyone,

I am having trouble to fully understand how Repo.checkout works regarding DB connection timeouts. I did not find an explanation in the Docs (but maybe there is one, in that case I would be very thankful if someone could point me to it).

My code looks roughly like this:

# code that loads data into `very_large_table` ...

Repo.checkout(fn ->
    # use minimal number of CPU cores for index creation to not overwhelm the database
    Repo.query!("SET max_parallel_maintenance_workers = 0")

    Repo.query!(
      "CREATE INDEX very_large_index ON very_large_table (...)",
      [],
      timeout: :timer.minutes(5)
    )

    Repo.query!(
      "CREATE INDEX another_large_index ON very_large_table (...)",
      [],
      timeout: :timer.minutes(5)
    )

    # ... more heavy operations
end)

It runs as part of a job that loads a huge amount of data into a fresh table and then sets up indexes, constraints, etc.


I verified that the CREATE INDEX ... operations easily finish within the specified timeout, but I still get a DBConnection.ConnectionError tcp recv: closed (the connection was closed by the pool, possibly due to a timeout or because the pool has been terminated) from the Repo.checkout call.

I assume I have to change my code so the Repo.checkout call has a timeout that allows all the nested calls to finish.

Repo.checkout(
  fn ->
    # ... long running calls, see example above
  end,
  timeout: :timer.minutes(30)
)

But now I am not sure what exactly that does to my nested Repo.query!("...", [], timeout: ...) calls.

  • Do they still have their default timeout of whatever is specified in the config, unless a specific timeout is given?
  • Do they inherit the timeout from the Repo.checkout call?
  • Do they override the timeout from the Repo.checkout call if a specific timeout is given?

Any help or links to some docs that clarify the matter would be highly appreciated :blush:

Marked As Solved

ruslandoga

ruslandoga

:wave: @Yegair

This is probably not very helpful, but I think all answers are “no”. Or “it depends” on the Ecto adapter. Here’re some examples for Postgrex which is used for Ecto.Adapters.Postgres repos.

Do they still have their default timeout of whatever is specified in the config, unless a specific timeout is given?

iex> Mix.install [:postgrex]

iex> defmodule Demo do
  def run do
    {:ok, pool} =
      Postgrex.start_link(database: "plausible_dev", username: "postgres", password: "postgres")

    try do
      DBConnection.run(
        pool,
        fn conn ->
          Postgrex.query!(conn, "select pg_sleep(30)", [], [])
        end,
        timeout: :timer.seconds(60)
      )
    after
      Process.exit(pool, :normal)
    end
  end
end

iex> Demo.run()
{:ok,
 %Postgrex.Result{
   command: :select,
   columns: ["pg_sleep"],
   rows: [[:void]],
   num_rows: 1,
   connection_id: 91,
   messages: []
 }}

A 30s query succeeds with no custom timeout provided. Default timeout in Postgrex is 15s.

Do they inherit the timeout from the Repo.checkout call?

Instead of inheriting, it seems more like individual timeout no longer applies to queries inside a checkout block.

defmodule Demo do
  def run do
    {:ok, pool} =
      Postgrex.start_link(database: "plausible_dev", username: "postgres", password: "postgres")

    try do
      DBConnection.run(
        pool,
        fn conn ->
          Postgrex.query!(conn, "select pg_sleep(30)", [], [])
          Postgrex.query!(conn, "select pg_sleep(30)", [], [])
          Postgrex.query!(conn, "select pg_sleep(30)", [], [])
        end,
        timeout: :timer.seconds(60)
      )
    after
      Process.exit(pool, :normal)
    end
  end
end

Demo.run
15:49:22.723 [error] Postgrex.Protocol (#PID<0.309.0>) disconnected:
  ** (DBConnection.ConnectionError) client #PID<0.215.0> timed out because it queued and
     checked out the connection for longer than 60000ms

#PID<0.215.0> was at location:

    :prim_inet.recv0/3
    (postgrex 0.18.0) lib/postgrex/protocol.ex:3287: Postgrex.Protocol.msg_recv/4
    (postgrex 0.18.0) lib/postgrex/protocol.ex:2281: Postgrex.Protocol.recv_bind/3
    (postgrex 0.18.0) lib/postgrex/protocol.ex:2136: Postgrex.Protocol.bind_execute_close/4
    (db_connection 2.6.0) lib/db_connection/holder.ex:354: DBConnection.Holder.holder_apply/4
    (db_connection 2.6.0) lib/db_connection.ex:1512: DBConnection.run_execute/5
    (db_connection 2.6.0) lib/db_connection.ex:743: DBConnection.parsed_prepare_execute/5
    (db_connection 2.6.0) lib/db_connection.ex:735: DBConnection.prepare_execute/4

** (Postgrex.Error) ERROR 57014 (query_canceled) canceling statement due to user request
    (postgrex 0.18.0) lib/postgrex.ex:330: Postgrex.query!/4
    iex:27: anonymous fn/1 in Demo.run/0
    (db_connection 2.6.0) lib/db_connection.ex:930: DBConnection.run/3
    iex:23: Demo.run/0
    iex:18: (file)

Do they override the timeout from the Repo.checkout call if a specific timeout is given?

defmodule Demo do
  def run do
    {:ok, pool} =
      Postgrex.start_link(database: "plausible_dev", username: "postgres", password: "postgres")

    try do
      DBConnection.run(
        pool,
        fn conn ->
          Postgrex.query!(conn, "select pg_sleep(30)", [], timeout: :timer.seconds(60))
          Postgrex.query!(conn, "select pg_sleep(30)", [], timeout: :timer.seconds(60))
          Postgrex.query!(conn, "select pg_sleep(30)", [], timeout: :timer.seconds(60))
        end,
        timeout: :timer.seconds(10)
      )
    after
      Process.exit(pool, :normal)
    end
  end
end

Demo.run
15:49:51.534 [error] Postgrex.Protocol (#PID<0.318.0>) disconnected: 
  ** (DBConnection.ConnectionError) client #PID<0.215.0> timed out because it queued and
     checked out the connection for longer than 10000ms

#PID<0.215.0> was at location:

    :prim_inet.recv0/3
    (postgrex 0.18.0) lib/postgrex/protocol.ex:3287: Postgrex.Protocol.msg_recv/4
    (postgrex 0.18.0) lib/postgrex/protocol.ex:2281: Postgrex.Protocol.recv_bind/3
    (postgrex 0.18.0) lib/postgrex/protocol.ex:2136: Postgrex.Protocol.bind_execute_close/4
    (db_connection 2.6.0) lib/db_connection/holder.ex:354: DBConnection.Holder.holder_apply/4
    (db_connection 2.6.0) lib/db_connection.ex:1512: DBConnection.run_execute/5
    (db_connection 2.6.0) lib/db_connection.ex:743: DBConnection.parsed_prepare_execute/5
    (db_connection 2.6.0) lib/db_connection.ex:735: DBConnection.prepare_execute/4

** (Postgrex.Error) ERROR 57014 (query_canceled) canceling statement due to user request
    (postgrex 0.18.0) lib/postgrex.ex:330: Postgrex.query!/4
    iex:27: anonymous fn/1 in Demo.run/0
    (db_connection 2.6.0) lib/db_connection.ex:930: DBConnection.run/3
    iex:24: Demo.run/0
    iex:19: (file)

Where Next?

Popular in Questions Top

sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
Werner
Hi, I’m using Ubuntu 18.04 and after updating to OTP-24.0 yesterday i have this warning when I run “mix local.hex”: 14:57:30.512 [warn] ...
New
Kagamiiiii
Student &amp; New to elixir. Nice language. I want to convert a english character, e.g. “a”, which is stored in a variable, to it’s asci...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
vac
Hi, I'm quite new in Elixir and I'm trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and ...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
Phillipp
Hey, I have a NanoPi-M3 and try to install Elixir on their Ubuntu image. I followed the Raspberry Pi installation instructions from the ...
New
Fl4m3Ph03n1x
Background Let’s assume I have a typical GenServer that receives messages as requests, does some operation in a DB and returns responses....
New

Other popular topics Top

JDanielMartinez
Hi! May someone helps me, please! I have two apps into an umbrella project: the first one is Database, which manages queries, and the se...
New
JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1140 51847 244
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
yawaramin
In the Dialyzer docs ( http://erlang.org/doc/man/dialyzer.html#requesting-or-suppressing-warnings-in-source-files ), there is a way to tu...
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
polypush135
As many of you may have realized by now (sorry for all the posts here) I’ve been working on a db problem where I’m trying to aggregate a ...
New
lk-geimfari
What is most correct way to open, read and parse JSON file with poison? For example if we have example.json file in root of some projec...
New
vac
Hi, I'm quite new in Elixir and I'm trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and ...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New

We're in Beta

About us Mission Statement