From 40ca69eb5eeada6e7c9b8a691d0274e719864a4f Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Mon, 28 Sep 2026 12:13:44 -0400 Subject: [PATCH 1/3] feat(config): allow tuning pool pings, checkout retries and connection recycling Signed-off-by: Yordis Prieto --- lib/event_store/config.ex | 18 ++++++++++++++---- test/config_test.exs | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/lib/event_store/config.ex b/lib/event_store/config.ex index f32ec2a5..e057730d 100644 --- a/lib/event_store/config.ex +++ b/lib/event_store/config.ex @@ -115,6 +115,7 @@ defmodule EventStore.Config do :backoff_max, :backoff_min, :backoff_type, + :checkout_retries, :configure, :connect_timeout, :connection_listeners, @@ -124,6 +125,8 @@ defmodule EventStore.Config do :handshake_timeout, :hostname, :idle_interval, + :idle_limit, + :max_lifetime, :max_restarts, :max_seconds, :parameters, @@ -166,8 +169,7 @@ defmodule EventStore.Config do def postgrex_notifications_opts(config, name) do config - |> session_mode_pool_config() - |> default_postgrex_opts() + |> session_mode_postgrex_opts() |> Keyword.put(:auto_reconnect, true) |> Keyword.put(:backoff_type, :exp) |> Keyword.put(:pool_size, 1) @@ -183,12 +185,20 @@ defmodule EventStore.Config do """ def advisory_locks_postgrex_opts(config) do config - |> session_mode_pool_config() - |> default_postgrex_opts() + |> session_mode_postgrex_opts() |> Keyword.put(:backoff_type, :stop) |> Keyword.put(:pool_size, 1) end + # Session mode connections hold state tied to their lifetime (advisory locks, + # LISTEN registrations), so they must never be recycled. + defp session_mode_postgrex_opts(config) do + config + |> session_mode_pool_config() + |> default_postgrex_opts() + |> Keyword.delete(:max_lifetime) + end + # Get the optional session mode pool to be used for persistent Postgres # connections (advisory locks and notifications). Uses the default Postgres # configuration if not specified. diff --git a/test/config_test.exs b/test/config_test.exs index 57025883..3cc1d298 100644 --- a/test/config_test.exs +++ b/test/config_test.exs @@ -190,6 +190,39 @@ defmodule EventStore.ConfigTest do ] end + describe "connection pool options" do + setup do + config = + Config.parse( + url: "postgres://username:password@localhost/database", + checkout_retries: 5, + idle_limit: 2, + max_lifetime: 480_000..540_000 + ) + + [config: config] + end + + test "main pool accepts every option", %{config: config} do + opts = Config.postgrex_opts(config, :name) + + assert opts[:checkout_retries] == 5 + assert opts[:idle_limit] == 2 + assert opts[:max_lifetime] == 480_000..540_000 + end + + test "session mode pools are never recycled", %{config: config} do + for opts <- [ + Config.advisory_locks_postgrex_opts(config), + Config.postgrex_notifications_opts(config, :name) + ] do + assert opts[:checkout_retries] == 5 + assert opts[:idle_limit] == 2 + refute Keyword.has_key?(opts, :max_lifetime) + end + end + end + test "parse url with query parameters" do config = [ url: "postgres://username:password@localhost/database?ssl=true&pool_size=5&timeout=120000" From 4a41bd40f2eed89b682f6e802c08007a2775ce14 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Mon, 28 Sep 2026 12:17:33 -0400 Subject: [PATCH 2/3] fix(deps): require a DBConnection version that honors max_lifetime Signed-off-by: Yordis Prieto --- mix.exs | 1 + 1 file changed, 1 insertion(+) diff --git a/mix.exs b/mix.exs index 2eec4bf4..d390d60a 100644 --- a/mix.exs +++ b/mix.exs @@ -38,6 +38,7 @@ defmodule EventStore.Mixfile do defp deps do [ + {:db_connection, "~> 2.10"}, {:gen_stage, "~> 1.2"}, {:postgrex, "~> 0.22"}, {:telemetry, "~> 1.0"}, From 58bde437b59f7962457d084c1eb325af4ed61884 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Mon, 28 Sep 2026 12:27:52 -0400 Subject: [PATCH 3/3] fix(config): keep the migration lock connection from being recycled Signed-off-by: Yordis Prieto --- lib/event_store/config.ex | 21 +++++++++------------ test/config_test.exs | 3 ++- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/lib/event_store/config.ex b/lib/event_store/config.ex index e057730d..b8b28c60 100644 --- a/lib/event_store/config.ex +++ b/lib/event_store/config.ex @@ -151,8 +151,12 @@ defmodule EventStore.Config do :username ] + # Only the main pool may recycle connections, every other connection holds + # state tied to its lifetime (advisory locks, LISTEN registrations). def default_postgrex_opts(config) do - Keyword.take(config, @postgrex_connection_opts) + config + |> Keyword.take(@postgrex_connection_opts) + |> Keyword.delete(:max_lifetime) end def postgrex_opts(config, name) do @@ -169,7 +173,8 @@ defmodule EventStore.Config do def postgrex_notifications_opts(config, name) do config - |> session_mode_postgrex_opts() + |> session_mode_pool_config() + |> default_postgrex_opts() |> Keyword.put(:auto_reconnect, true) |> Keyword.put(:backoff_type, :exp) |> Keyword.put(:pool_size, 1) @@ -184,19 +189,11 @@ defmodule EventStore.Config do advisory lock. """ def advisory_locks_postgrex_opts(config) do - config - |> session_mode_postgrex_opts() - |> Keyword.put(:backoff_type, :stop) - |> Keyword.put(:pool_size, 1) - end - - # Session mode connections hold state tied to their lifetime (advisory locks, - # LISTEN registrations), so they must never be recycled. - defp session_mode_postgrex_opts(config) do config |> session_mode_pool_config() |> default_postgrex_opts() - |> Keyword.delete(:max_lifetime) + |> Keyword.put(:backoff_type, :stop) + |> Keyword.put(:pool_size, 1) end # Get the optional session mode pool to be used for persistent Postgres diff --git a/test/config_test.exs b/test/config_test.exs index 3cc1d298..3aa83222 100644 --- a/test/config_test.exs +++ b/test/config_test.exs @@ -211,8 +211,9 @@ defmodule EventStore.ConfigTest do assert opts[:max_lifetime] == 480_000..540_000 end - test "session mode pools are never recycled", %{config: config} do + test "connections other than the main pool are never recycled", %{config: config} do for opts <- [ + Config.default_postgrex_opts(config), Config.advisory_locks_postgrex_opts(config), Config.postgrex_notifications_opts(config, :name) ] do