【问题标题】:Django+Postgres FATAL: sorry, too many clients alreadyDjango+Postgres 致命:抱歉,已经有太多客户了
【发布时间】:2023-11-20 14:46:01
【问题描述】:

我不时收到“致命:抱歉,已经有太多客户”,因为我在 Postgres 中有很多空闲连接,我不明白它们来自哪里或如何防止它们。

一开始我尝试了Django中的CONN_MAX_AGE设置,但似乎没有效果。

我还在 Postgres 中将 idle_in_transaction_session_timeout 设置为 5 分钟,但我一直看到很多空闲事务:

postgres=# select client_addr, state, count(*) from pg_stat_activity group by client_addr, state;
  client_addr  | state  | count 
---------------+--------+-------
               |        |     5
               | active |     1
               | idle   |     1
 172.30.12.148 | idle   |     2
 172.30.12.74  | idle   |    89
(5 rows)
postgres=# select client_addr, state, backend_start, query_start from pg_stat_activity order by query_start ;
  client_addr  | state  |         backend_start         |          query_start          
---------------+--------+-------------------------------+-------------------------------
               | idle   | 2020-03-24 20:03:16.060707+00 | 2020-03-24 20:55:17.020962+00
 172.30.12.74  | idle   | 2020-03-25 02:05:32.567976+00 | 2020-03-25 02:05:32.613112+00
 172.30.12.74  | idle   | 2020-03-25 02:05:34.926656+00 | 2020-03-25 02:05:34.945405+00
 172.30.12.74  | idle   | 2020-03-25 02:05:49.700201+00 | 2020-03-25 02:05:49.717165+00
[...]
 172.30.12.74  | idle   | 2020-03-25 04:00:51.019892+00 | 2020-03-25 04:01:22.627659+00
 172.30.12.74  | idle   | 2020-03-25 04:04:18.333413+00 | 2020-03-25 04:04:18.350539+00
 172.30.12.74  | idle   | 2020-03-25 04:04:35.157547+00 | 2020-03-25 04:05:16.746978+00
 172.30.12.74  | idle   | 2020-03-25 04:05:08.241291+00 | 2020-03-25 04:05:39.367247+00
 172.30.12.148 | idle   | 2020-03-25 04:07:02.717151+00 | 2020-03-25 04:07:02.726822+00
 172.30.12.74  | idle   | 2020-03-25 04:07:48.07922+00  | 2020-03-25 04:07:48.112819+00
               | active | 2020-03-25 04:00:10.608213+00 | 2020-03-25 04:07:57.336091+00
               |        | 2020-03-24 19:40:38.624442+00 | 
               |        | 2020-03-24 19:40:38.624876+00 | 
               |        | 2020-03-24 19:40:38.624003+00 | 
               |        | 2020-03-24 19:40:38.623479+00 | 
               |        | 2020-03-24 19:40:38.62598+00  | 
(99 rows)

我知道 Django 为每个线程维护一个连接,但是(如果我相信 sn-p)我只有一个:

root@omaha-server-public-565447b47c-c2nqh:/usr/src/app# python manage.py shell        
Python 3.7.1 (default, Nov 16 2018, 22:26:09) 
[GCC 6.3.0 20170516] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> import threading
>>> for thread in threading.enumerate(): print(thread.name)
... 
MainThread
>>> 

那么,为什么当我列出到我的数据库的连接(在 10.100.59.225 运行)时,我看到这么多 ESTABLISHED 连接?

root@omaha-server-public-565447b47c-c2nqh:/usr/src/app# netstat -natup | grep  10.100.59.225 | wc -l
89

我是 Django、Python 和 Postgres 的新手,所以我想我忽略了一些明显的东西,但是我的搜索还没有带来任何有用的东西,所以我在这里尝试 :-)

版本信息:

  • Django 2.2
    • django-cacheops 4.1
    • psycopg2 2.7.3.2
  • Postgres 12.2
  • Python 3.7.1

【问题讨论】:

  • shell 管理命令无法提供有关 Django 服务器中线程的任何信息,因为它是具有自己线程的自己的进程。
  • 查看 pg_stat_activity.query(他们在空闲之前运行的最后一个查询)可能会帮助您了解他们来自哪里。
  • 感谢@KlausD.,我将使用gdb 重试并使用该信息编辑我的帖子。
  • 根据@jjanes 的建议,我分析了查询,结果始终是+/-。我想我把它缩小到a cached QuerySet,但我仍然不明白为什么保持连接。如果我理解正确Django's auto close mechanism 这不应该发生。从我收集的信息来看,应用程序本身没有与数据库创建直接连接,这一切都依赖于其他库(cacheops,rest_framework,...)
  • 我刚刚在一个快速的 CentOS 机器上遇到了同样的问题,这是一个直接进入 PostgreSQL 10 的 Ruby gem。多个客户端运行相同的容量。我用 12 个连接(4 个核心)对其进行了测试,这是一篇关于优化主题的 PostgreSQL 文本推荐的(3x 核心数)。我的查询很简单,实际上是返回的,然后保持空闲,然后重复该过程。我有 5 个查询要求上次登录。该表是测试表的大小。这听起来像是 PostgreSQL 问题。

标签: python django postgresql


【解决方案1】:

这个问题已经存在一段时间了,但如果有人来这里,这就是我面临的问题。

开发服务器(当您运行 manage.py runserver 时)默认是多线程的,这意味着每个请求都在创建自己的连接,并且我有一个带有池端点的服务器。我不知道这是否会对任何人有所帮助,但请记住检查这种可能性,运行将--nothreading 传递给runsever 命令的服务器。

https://docs.djangoproject.com/en/2.1/ref/django-admin/#cmdoption-runserver-nothreading

【讨论】:

    【解决方案2】:

    实际上,空闲会话中的空闲会话多于空闲事务:这看起来可能是应用程序端的连接泄漏。对于不运行任何事务的空闲会话,PostgreSQL 没有超时。 PostgreSQL 方面可能的解决方法是安排一个作业来终止这些空闲会话:请参阅Is there a timeout for idle PostgreSQL connections?

    【讨论】:

    • 谢谢!我误解了 tx 和 session 之间的区别。我本来希望解决泄漏问题而不是引入另一个依赖项。看来我必须这样做,我尝试了触发器但无法使其工作:``` postgres=# CREATE OR REPLACE FUNCTION terminate_idle_sessions() RETURNS trigger LANGUAGE plpgsql AS $function$ BEGIN SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE pid pg_backend_pid() AND state = 'idle' AND state_change is not null AND current_timestamp - state_change > INTERVAL '5' MINUTE;结尾; $函数$; ```
    【解决方案3】:
    • 用两种方法手动关闭那些idle pg 连接,这对我有用。
    # core codes
    from django.db import close_old_connections
    from django.db import connection
    close_old_connections()
    with connection.cursor() as cursor:
        sql = "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE state = 'idle'"
        print(sql)
        cursor.execute(sql)
        row = cursor.fetchall()
        print(row)
    
    • 我在一个api装饰器中使用这些代码,如果错误信息中有too many client,我会调用这些代码。
    # pip install bddjango
    
    # use the api_decorator for views that you often use. 
    from bddjango import api_decorator
    
    class Task(APIView):
    
        @api_decorator
        def get(self, request):
            pass
    
    

    【讨论】:

    • 如果它不是一个完整的网络应用程序,可能需要它; Django 在正常使用时会关闭旧连接,但并非总是在您仅使用某些部分(例如仅 ORM)时关闭