Skip to content

perf(session): reuse backend connections when possible#837

Merged
v0idpwn merged 16 commits intomainfrom
reuse-sessions-when-possible
Feb 10, 2026
Merged

perf(session): reuse backend connections when possible#837
v0idpwn merged 16 commits intomainfrom
reuse-sessions-when-possible

Conversation

@v0idpwn
Copy link
Member

@v0idpwn v0idpwn commented Jan 19, 2026

When terminating gracefully and not in transaction, the ClientHandler can run a cleanup in the DbHandler and put it back to the pool. This reduces the overhead of opening new connections in the session mode, making supavisor a more performant session pooler, akin to PgBouncer.

When terminating gracefully and not in transaction, the ClientHandler
can run a cleanup in the DbHandler and put it back to the pool. This
reduces the overhead of opening new connections in the session mode.
@v0idpwn v0idpwn requested a review from a team as a code owner January 19, 2026 06:50
Copy link
Contributor

@mentels mentels left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

Leaving some comments.

Logger.debug("ClientHandler: Performing session cleanup before termination")
{pool, db_pid, _} = data.db_connection

case DbHandler.attempt_cleanup(db_pid) do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it wasn't be safer to have a timeout here as well.

Can we rule out a scenario in wich an "unrelated" event arrives to the DbHandler while in :waiting_cleanup and cancels the DbHandler :cleanup_timeout?

Copy link
Member Author

@v0idpwn v0idpwn Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we rule out a scenario in wich an "unrelated" event arrives to the DbHandler while in :waiting_cleanup and cancels the DbHandler :cleanup_timeout?

Great point!!

I should've used :state_timeout instead of :timeout which ensures this does not happen.

:state_timeout refers to the time spent in a state while :timeout refers to the time without processing messages (like in GenServer)

Will push a fix.

Other than that, I think we do a regular timeout in the gen:call just for sanity.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 4e39cba and 0d252af

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, there's yet another issue!

If the call fails - be it b/c of the :timeout or just b/c the gen_statem processes exists which is actually expected exec path here - the gen_statem:call exists the caller! And this will crash the client_handler.

So I guess to make it graceful we need to wrap it in the try catch OR make it async? (No, the latter sounds like a bad idea but "thinking out loud").

Copy link
Member Author

@v0idpwn v0idpwn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: make sure we free the client before terminate so we reduce the time we impact the connection limit

Edit: done

@v0idpwn v0idpwn requested a review from mentels February 10, 2026 04:49
Copy link
Contributor

@mentels mentels left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes looks good but as I pointed out in the comment, I believe there's an issue.


defp maybe_cleanup_db_handler(state, data) do
if state == :idle and data.mode == :session and data.db_connection != nil and
!Supavisor.Helpers.no_warm_pool_user?(data.user) do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤯 takes a while to reason about this condition

Logger.debug("ClientHandler: Performing session cleanup before termination")
{pool, db_pid, _} = data.db_connection

case DbHandler.attempt_cleanup(db_pid) do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, there's yet another issue!

If the call fails - be it b/c of the :timeout or just b/c the gen_statem processes exists which is actually expected exec path here - the gen_statem:call exists the caller! And this will crash the client_handler.

So I guess to make it graceful we need to wrap it in the try catch OR make it async? (No, the latter sounds like a bad idea but "thinking out loud").

v0idpwn and others added 3 commits February 10, 2026 13:55
We are cutting the v2.8.10 release anyway and moving to v2.9.0 which
will not be soft deploy compatible.
@v0idpwn v0idpwn merged commit ebc789b into main Feb 10, 2026
14 checks passed
@v0idpwn v0idpwn deleted the reuse-sessions-when-possible branch February 10, 2026 07:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants