100

Some clients connect to our postgresql database but leave the connections opened. Is it possible to tell Postgresql to close those connection after a certain amount of inactivity ?

TL;DR

IF you're using a Postgresql version >= 9.2
THEN use the solution I came up with

IF you don't want to write any code
THEN use arqnid's solution

IF you don't want to write any code
AND you're using a Postgresql version >= 14
THEN use Laurenz Albe's solution

Stephan
  • 41,764
  • 65
  • 238
  • 329
  • 3
    You could use a cron job to look at when the connection was last active (see `pg_stat_activity`) and use `pg_terminate_backend` to kill old ones. Easily expressed in a simple query. I'm not sure if `pg_terminate_backend` was available in the pretty-ancient 8.3, though. – Craig Ringer Sep 13 '12 at 08:38
  • I have a similar problem with my service using C3P0 pooling -- all the connections were closed (finally block) but after i did a load test, the number of idle connections didn't drop after the load test finished. In `pg_stat_activity` table i have lots of `idle` connections that's running `SELECT 1` or `SHOW TRANSACTION ISOLATION LEVEL`. Then when I run a second load test, the number kept growing, does this mean that the idle connections were not re-used during second test? Can i use the same solution here to fix it? – Zip Aug 04 '18 at 19:25
  • @Zip Can you please turn your comment into a new question and put a link to this new question here? I'll be easier to anwser you. – Stephan Aug 05 '18 at 12:28
  • @Stephan the question is here: https://stackoverflow.com/questions/51682584/idle-not-idle-in-transaction-connections-are-not-released-closed-in-postgresql . Thanks! – Zip Aug 05 '18 at 19:23

8 Answers8

90

For those who are interested, here is the solution I came up with, inspired from Craig Ringer's comment:

(...) use a cron job to look at when the connection was last active (see pg_stat_activity) and use pg_terminate_backend to kill old ones.(...)

The chosen solution comes down like this:

  • First, we upgrade to Postgresql 9.2.
  • Then, we schedule a thread to run every second.
  • When the thread runs, it looks for any old inactive connections.
    • A connection is considered inactive if its state is either idle, idle in transaction, idle in transaction (aborted) or disabled.
    • A connection is considered old if its state stayed the same during more than 5 minutes.
  • There are additional threads that do the same as above. However, those threads connect to the database with different user.
  • We leave at least one connection open for any application connected to our database. (rank() function)

This is the SQL query run by the thread:

WITH inactive_connections AS (
    SELECT
        pid,
        rank() over (partition by client_addr order by backend_start ASC) as rank
    FROM 
        pg_stat_activity
    WHERE
        -- Exclude the thread owned connection (ie no auto-kill)
        pid <> pg_backend_pid( )
    AND
        -- Exclude known applications connections
        application_name !~ '(?:psql)|(?:pgAdmin.+)'
    AND
        -- Include connections to the same database the thread is connected to
        datname = current_database() 
    AND
        -- Include connections using the same thread username connection
        usename = current_user 
    AND
        -- Include inactive connections only
        state in ('idle', 'idle in transaction', 'idle in transaction (aborted)', 'disabled') 
    AND
        -- Include old connections (found with the state_change field)
        current_timestamp - state_change > interval '5 minutes' 
)
SELECT
    pg_terminate_backend(pid)
FROM
    inactive_connections 
WHERE
    rank > 1 -- Leave one connection for each application connected to the database
Stephan
  • 41,764
  • 65
  • 238
  • 329
  • 1
    Finally, we moved to Postgresql 9.2 to get advantage of `pg_terminate_backend`. We used a cron like job to periodically invoke `pg_terminate_backend`. – Stephan Jun 11 '15 at 00:18
  • 2
    Can you post a script which you use? – Andrus Jun 29 '15 at 16:45
53

If you are using PostgreSQL >= 9.6 there is an even easier solution. Let's suppose you want to delete all idle connections every 5 minutes, just run the following:

alter system set idle_in_transaction_session_timeout='5min';

In case you don't have access as superuser (example on Azure cloud), try:

SET SESSION idle_in_transaction_session_timeout = '5min';

But this latter will work only for the current session, that most likely is not what you want.

To disable the feature,

alter system set idle_in_transaction_session_timeout=0;

or

SET SESSION idle_in_transaction_session_timeout = 0;

(by the way, 0 is the default value).

If you use alter system, you must reload configuration to start the change and the change is persistent, you won't have to re-run the query anymore if, for example, you will restart the server.

To check the feature status:

show idle_in_transaction_session_timeout;
fresko
  • 1,890
  • 2
  • 24
  • 25
  • Interesting feature. However, it doesn't allow fine connections selection (keeping one connection alive, whitelisting some applications connections ...) – Stephan Oct 08 '18 at 15:45
  • Maybe it has the possibility to write a whitelist, but i am not sure about. The command is new also for me. – fresko Oct 09 '18 at 11:25
  • 50
    This will only close connections that opened a transaction and failed to close (commit or rollback) it within the given timeout (as the name "idle_in_transaction_session_timeout" suggests). It won't close connections that are just "idle". – Maximilian Tyrtania Dec 06 '18 at 21:13
  • 1
    "idle_in_transaction_session_timeout" can also be set in postgresql.conf – cdalxndr Oct 04 '19 at 17:23
  • if I put session time out = 0 , it will impact my running quries? – Syed Haseeb Jul 17 '21 at 11:42
  • @SyedHaseeb I think it won't impact running queries – fresko Jul 20 '21 at 09:04
  • see also https://stackoverflow.com/a/69528572/363573 – Stephan Oct 11 '21 at 16:22
24

Connect through a proxy like PgBouncer which will close connections after server_idle_timeout seconds.

araqnid
  • 127,052
  • 24
  • 157
  • 134
17

From PostgreSQL v14 on, you can set the idle_session_timeout parameter to automatically disconnect client sessions that are idle.

Laurenz Albe
  • 209,280
  • 17
  • 206
  • 263
7

If you use AWS with PostgreSQL >= 9.6, you have to do the following:

Create custom parameter group

go to RDS > Parameter groups > Create parameter group Select the version of PSQL that you use, name it 'customParameters' or whatever and add description 'handle idle connections'.

Change the idle_in_transaction_session_timeout value

Fortunately it will create a copy of the default AWS group so you only have to tweak the things that you deem not suitable for your use-case.

Now click on the newly created parameter group and search 'idle'.
The default value for 'idle_in_transaction_session_timeout' is set to 24 hours (86400000 milliseconds). Divide this number by 24 to have hours (3600000) and then you have to again divide 3600000 by 4, 6 or 12 depending on whether you want the timeout to be respectively 15, 10 or 5 minutes (or equivalently multiply the number of minutes x 60000, so value 300 000 for 5 minutes).

Assign the group

Last, but not least, change the group:

go to RDS, select your DB and click on 'Modify'.

Now under 'Database options' you will find 'DB parameter group', change it to the newly created group.

You can then decide if you want to apply the modifications immediately (beware of downtime).

Antonin GAVREL
  • 9,682
  • 8
  • 54
  • 81
3

I have the problem of denied connections as there are too much clients connected on Postgresql 12 server (but not on similar projects using earlier 9.6 and 10 versions) and Ubuntu 18.

I wonder if those settings

tcp_keepalives_idle 
tcp_keepalives_interval 

could be more relevant than

idle_in_transaction_session_timeout

idle_in_transaction_session_timeout indeed closes only the idle connections from failed transactions, not the inactive connections whose statements terminate correctly... the documentation reads that these socket-level settings have no impact with Unix-domain sockets but it could work on Ubuntu.

  • 1
    Wanna try it and share your results with us here ? ;) – Stephan Aug 26 '20 at 23:55
  • Hi, @Stefan it seems to work. I see no connections from yesterday with pg_stat_activity. I used the following settings : `tcp_keepalives_interval=300 tcp_keepalives_interval=30` . If I understand correctly, the server should wait 5 minutes to check if the client is still connected, and then wait 30 further seconds for an ACK from the client... – Franck Theeten Aug 27 '20 at 12:31
  • 1
    I also must say that I replaced persistent PDO connections by transient ones in an application. They were causing the problem of client slots not being freed. – Franck Theeten Aug 28 '20 at 07:15
  • Hi, @FranckTheeten can you state exact settings. Here in comments you mentioned `tcp_keepalives_interval` twice... – xneg Jul 13 '21 at 14:42
  • Hi, sorry for the typo, I enabled ```tcp_keepalives_idle=300``` , ```tcp_keepalives_interval=30```, ```tcp_user_timeout = 300``` (on separate lines). The server waits 5 minutes before sending the first ACK request to the client, waitx 30 seconds before retrying if it doesn't reply, and closes the connection slot after 5 minutes. ```tcp_keepalives_count``` also allows to specify the number of keepalive queries (default system setting TCP_KEEPCNT if not mentionned). See also this unofficial site : https://postgresqlco.nf/doc/en/param/tcp_keepalives_count/ – Franck Theeten Jul 14 '21 at 09:56
  • see also https://stackoverflow.com/a/69528572/363573 – Stephan Oct 11 '21 at 16:21
1

Up to PostgreSQL 13, you can use my extension pg_timeout.

pifor
  • 7,419
  • 2
  • 8
  • 16
  • For Pg13 and superior versions, what happen to your extension ? – Stephan Dec 05 '21 at 14:05
  • 1
    I had already tested my extension with PG13 and I have just successfully tested it with PG 14: according to my tests it works as expected. – pifor Dec 08 '21 at 16:55
0

Here is a solution if you using docker containers: Run following commands against your database container to find out the number of idle connections you have:

echo "SELECT datname, count(*) FROM pg_stat_activity WHERE state = 'idle' GROUP BY datname;" >> query.sql
cat ./query.sql | docker exec -i $name_of_db_container psql -U $pg_user

To kill idle connections, Run the following commands against your database container:

echo "select pg_terminate_backend(pid) from pg_stat_activity where state = 'idle';" >> query.sql
cat ./query.sql | docker exec -i $name_of_db_container psql -U $pg_user
helvete
  • 2,455
  • 13
  • 33
  • 37