You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Heroku部署Django应用PostgreSQL连接不释放问题求助

PostgreSQL连接泄漏问题排查与解决

我当前使用的技术栈:Django、Heroku、WebSocket、AJAX、Celery、PostgreSQL、Consumer、ASGI(uvicorn,也试过Daphne)。

核心问题:仅刷新页面就会创建新的PostgreSQL连接,且长时间闲置也不会释放,很快触发Heroku PostgreSQL的连接数上限(20/20)。本地环境无此问题,仅在Heroku生产环境出现。


已尝试的操作

  • 查看pg_stat_activity,发现泄漏的连接来自特定视图代码的查询,多数处于空闲状态:
    for post in posts:
            temp_dict = defaultdict(list)
            recommendations_ordered = post.recommendations.all().order_by('created_at')
            ...and so on, including request.user. 
    
  • 数据库配置如下:
    if ENVIRONMENT == 'development':
        DATABASES = {
            'default': {
            'ENGINE': 'django.db.backends.postgresql_psycopg2',
            'NAME': 'redacted',
            'USER': 'redacted',
            'PASSWORD': 'redacted',
            'HOST': 'localhost',
            'PORT': '5432',
            }
        }
    else:
        DATABASES = {
            'default': dj_database_url.config(default=os.environ.get('DATABASE_URL'))
        }
        DATABASES['default']['CONN_MAX_AGE'] = 0  
        DATABASES['default']['ATOMIC_REQUESTS'] = True  
    
  • 在涉及的视图和Celery任务中手动添加了close.connection操作
  • Procfile配置:
    web: gunicorn -w 1 -k uvicorn.workers.UvicornWorker decide.asgi:application
    worker: celery -A decide worker -l info --pool=threads
    

可能相关的其他配置

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": os.environ.get('REDIS_URL', 'redis://localhost:6379/0'),
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    }
}

if ENVIRONMENT == 'production':
    CACHES['default']['OPTIONS']['CONNECTION_POOL_KWARGS'] = {"ssl": True}


# Cache settings
CACHE_TTL = 300 

# Use Django's cache API
CACHE_MIDDLEWARE_ALIAS = "default"
CACHE_MIDDLEWARE_SECONDS = CACHE_TTL
CACHE_MIDDLEWARE_KEY_PREFIX = ""


# Celery Configuration
if ENVIRONMENT == 'development':
    CELERY_BROKER_URL = 'redis://localhost:6379/0'
    CELERY_RESULT_BACKEND = 'redis://localhost:6379/0'
else:  # production
    CELERY_BROKER_URL = os.getenv('REDIS_URL', 'redis://localhost:6379/0')  
    CELERY_RESULT_BACKEND = os.getenv('REDIS_URL', 'redis://localhost:6379/0')  

CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TIMEZONE = 'UTC'
CELERY_TASK_DEFAULT_QUEUE = 'default'
CELERY_QUEUES = (
    Queue('default', Exchange('default'), routing_key='default'),
)

django_heroku.settings(locals())

问题原因分析与解决方案

1. CONN_MAX_AGE=0的反向作用

CONN_MAX_AGE=0意味着每次请求都会创建新连接且不复用,但ASGI是异步/长连接模型,请求上下文销毁时机与WSGI不同,导致连接无法被正确回收。

解决:将生产环境的CONN_MAX_AGE设为合理值(如60秒),让连接池复用连接:

DATABASES['default']['CONN_MAX_AGE'] = 60  # 替换原来的0

2. ATOMIC_REQUESTS=True的隐式连接持有

开启全局事务后,若请求处理中出现异常(比如WebSocket连接异常断开),事务可能无法正确回滚,导致连接被长期占用。

解决:关闭ATOMIC_REQUESTS,在需要事务的视图/函数中手动用transaction.atomic()装饰器或上下文管理器。

3. ASGI Worker的连接回收问题

gunicorn + UvicornWorker的组合可能导致异步环境下连接无法自动回收。

解决:

  • 改用纯Uvicorn启动:
    web: uvicorn decide.asgi:application --host 0.0.0.0 --port ${PORT:-8000}
    
  • 若保留Gunicorn,增加--max-requests参数让Worker处理一定请求后重启:
    web: gunicorn -w 1 -k uvicorn.workers.UvicornWorker --max-requests 100 decide.asgi:application
    

4. 循环查询的N+1问题

代码中的循环查询会触发多次数据库交互,积累闲置连接。

解决:用prefetch_related优化查询,批量获取关联数据:

from django.db.models import Prefetch

posts = Post.objects.prefetch_related(
    Prefetch('recommendations', queryset=Recommendation.objects.order_by('created_at'))
).all()

5. Celery Worker的连接管理

线程池模式下,每个线程可能持有独立连接,任务结束后未释放会导致泄漏。

解决:

  • 在Celery任务中主动关闭旧连接:
    from django.db import close_old_connections
    from celery import shared_task
    
    @shared_task
    def your_task():
        try:
            # 任务逻辑
        finally:
            close_old_connections()
    
  • 增加--max-tasks-per-child参数让Worker进程处理一定任务后重启:
    worker: celery -A decide worker -l info --pool=threads --max-tasks-per-child 50
    

6. django_heroku.settings()的覆盖问题

该调用可能会覆盖你手动设置的数据库参数,导致配置不生效。

解决:将其放在配置最后,并重新设置数据库参数:

django_heroku.settings(locals())
# 重新设置参数避免被覆盖
if ENVIRONMENT == 'production':
    DATABASES['default']['CONN_MAX_AGE'] = 60
    DATABASES['default']['ATOMIC_REQUESTS'] = False

内容的提问来源于stack exchange,提问作者superbot

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 08:52:02