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

Django ASGI应用内存泄漏问题排查及临时解决方法

Django ASGI应用内存泄漏问题排查与临时解决方案

2024年4月25日更新

malloc_trim(0) 初步验证有效,仍需长期观察服务器内存消耗。以下是修改后的asgi.py,通过启动守护线程,在内存达到阈值时调用malloc_trim(0):

import ctypes
import random
import time
from threading import Thread
import psutil
import os
import django
from django.core.handlers.asgi import ASGIHandler

MEMORY_THRESHOLD = 1024 * 1024 * 256  # 256MB


def trim_memory() -> int:
    libc = ctypes.CDLL("libc.so.6")
    return libc.malloc_trim(0)


def should_trim_memory() -> bool:
    # 检查是否接近内存耗尽阈值
    process = psutil.Process(os.getpid())
    return process.memory_info().rss > MEMORY_THRESHOLD


def trim_loop() -> None:
    while True:
        time.sleep(random.randint(30, 60))  # 随机延迟30-60秒,避免固定频率
        if not should_trim_memory():
            continue

        ret = trim_memory()
        print("trim memory result: ", ret)


def get_asgi_application():
    """
    The public interface to Django's ASGI support. Return an ASGI 3 callable.

    Avoids making django.core.handlers.ASGIHandler a public API, in case the
    internal implementation changes or moves in the future.
    """

    django.setup(set_prefix=False)

    thread = Thread(name="TrimThread", target=trim_loop)
    thread.daemon = True
    thread.start()

    return ASGIHandler()


os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'v45.settings')

application = get_asgi_application()

2024年4月24日更新

全新Django ASGI应用存在内存泄漏问题,关联CPython官方bug:TLS/SSL asyncio内存泄漏。报告称Python 3.9不使用uvloop时泄漏极小或无泄漏,但测试后该配置无效。由于Railway的nixpacks最高支持Python 3.11,无法验证Python 3.12是否修复该问题,欢迎分享有效配置方案。

复现步骤

  • 克隆测试仓库fifdee/djangofresh,需根据Railway生成的域名修改settings.py中的ALLOWED_HOSTS和CSRF_TRUSTED_ORIGINS;
  • 在Railway部署,自定义启动命令:hypercorn --bind 0.0.0.0:$PORT --workers 1 --reload djangofresh.asgi:application;
  • 访问应用确认显示“Hello, world!”;
  • 本地发起大量并发请求(代码如下),观察服务器内存消耗:每次请求循环结束后仅部分内存释放,内存会随时间持续增长。
from concurrent.futures import ThreadPoolExecutor
import requests as requests
    
def get_server_response(params):
        url = 'https://djangofresh-production.up.railway.app/'
        with requests.Session() as s:
            r = s.get(url,
                      headers={

                      },
                      json=params)

            print(r.text[:100])
            return r.text
    

if __name__ == '__main__':
    with ThreadPoolExecutor(max_workers=100) as executor:
        for _ in range(100):
            params = {
                'text': ' Very long text ' * 1000000,
            }
            executor.submit(get_server_response, params)

2024年4月18日更新

问题并非特定于django-storages和图片上传,而是所有请求都会引发内存泄漏:

  • 通过预签名URL让客户端直接上传图片至S3,仅在数据库存储文件名(无图片数据经过Django服务器),内存仍缓慢增长;
  • 移除AWS Lambda返回结果中的base64缩略图后,内存增长速度进一步变慢。
    结论:Django服务器处理的每个请求都会引发内存泄漏,请求/响应体积越大,泄漏速度越快。

初始问题描述

花费数日排查,尝试多种方案未解决:通过django-storages将图片保存至绑定S3的Django模型ImageField时,托管Web应用内存持续增长。

测试与观察

  • 无论使用哪种ASGI服务器(Daphne、Uvicorn、Hypercorn、Granian),结果一致;
  • 用tracemalloc逐行检查内存分配,发现调用uploaded_image.image.save或uploaded_image.thumbnail.save时内存占用上升,指向json/decoder.py文件;
  • 本地及服务器tracemalloc日志显示:单张图片保存后内存从约45MB升至55MB,短时间大量保存时内存飙升至约400MB,但之后会回落,垃圾回收看似正常;
  • 托管容器内存统计完全不同:单worker启动内存约250MB,每次上传图片后内存增长50-100MB,仅约10%能释放,压力测试2分钟即可耗尽8GB内存;
  • 测试railway.app和render.com均出现该问题:使用hypercorn --workers 5 --max-requests 50 --max-requests-jitter 25时,render.com可通过重启worker释放内存,但railway.app无法释放,且重启worker会导致请求丢失,并非可行方案。

疑问

是否有人遇到过此类问题并已解决?能否强制分块保存图片至S3?或是托管容器的外部层在请求处理后无法释放内存?


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 17:14:57