From d9755b4fc21eaff24b6711955147d9be8c65c688 Mon Sep 17 00:00:00 2001 From: ZePedroResende Date: Sat, 25 Jul 2026 23:16:37 +0100 Subject: [PATCH 1/3] fix: use the default pool in prod, not the test SQL sandbox MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit prod.exs configured Ethui.Repo with pool: Ecto.Adapters.SQL.Sandbox — a verbatim copy of config/test.exs. The sandbox pool is DBConnection.Ownership; in its default :auto mode it assigns a connection to each process on first use and only releases it when that process terminates (ownership manager handle_info(:DOWN), db_connection manager.ex:203). It does NOT check the connection back in after each query, unlike DBConnection.ConnectionPool. So every long-lived process that touches the DB (telemetry poller, per-stack servers, ...) permanently holds one of the pool_size connections. After ~10 such processes the pool is exhausted for good: even a single manual Repo query from a fresh iex session times out with a checkout error, which is what we observed in production. Removing the pool: line falls back to the default DBConnection.ConnectionPool, which returns connections after each operation. Co-Authored-By: Claude Opus 4.8 (1M context) --- server/config/prod.exs | 1 - 1 file changed, 1 deletion(-) diff --git a/server/config/prod.exs b/server/config/prod.exs index 89898ec..45883f9 100644 --- a/server/config/prod.exs +++ b/server/config/prod.exs @@ -18,5 +18,4 @@ config :logger, level: :info config :ethui, Ethui.Repo, default_transaction_mode: :immediate, - pool: Ecto.Adapters.SQL.Sandbox, pool_size: System.schedulers_online() * 2 From 15365c634fbec2f17c4e948e1f890e5e9f0a107d Mon Sep 17 00:00:00 2001 From: ZePedroResende Date: Sat, 25 Jul 2026 23:27:11 +0100 Subject: [PATCH 2/3] consolidate Repo pool config into runtime.exs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The prod.exs pool_size (schedulers*2) was dead — runtime.exs always overrides it (prod ran pool_size: 10). Drop it so runtime.exs is the single source; keep default_transaction_mode: :immediate (recommended for SQLite). Verified with a reproduction: 2 long-lived processes that each run one query exhaust the sandbox pool permanently (connections held per-process until the process dies), while the default DBConnection pool returns them after each query. Matches the Phoenix --database sqlite3 template, which uses the default pool in prod and the sandbox only in test. Co-Authored-By: Claude Opus 4.8 (1M context) --- server/config/prod.exs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/config/prod.exs b/server/config/prod.exs index 45883f9..44c2e19 100644 --- a/server/config/prod.exs +++ b/server/config/prod.exs @@ -16,6 +16,6 @@ config :swoosh, local: false # Do not print debug messages in production config :logger, level: :info -config :ethui, Ethui.Repo, - default_transaction_mode: :immediate, - pool_size: System.schedulers_online() * 2 +# Pool + pool_size are set in runtime.exs (the default DBConnection pool, not the +# test SQL sandbox). :immediate transactions are recommended for SQLite web apps. +config :ethui, Ethui.Repo, default_transaction_mode: :immediate From 90a4435dbf8392396e5eb14027de28d425926d30 Mon Sep 17 00:00:00 2001 From: ZePedroResende Date: Sat, 25 Jul 2026 23:28:58 +0100 Subject: [PATCH 3/3] drop comment block in prod.exs --- server/config/prod.exs | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/config/prod.exs b/server/config/prod.exs index 44c2e19..550b0a8 100644 --- a/server/config/prod.exs +++ b/server/config/prod.exs @@ -16,6 +16,4 @@ config :swoosh, local: false # Do not print debug messages in production config :logger, level: :info -# Pool + pool_size are set in runtime.exs (the default DBConnection pool, not the -# test SQL sandbox). :immediate transactions are recommended for SQLite web apps. config :ethui, Ethui.Repo, default_transaction_mode: :immediate