【问题标题】:What is causing the memory spike in my Django file upload code?是什么导致了我的 Django 文件上传代码中的内存峰值?
【发布时间】:2015-06-12 17:13:54
【问题描述】:

当我上传一个 4.8mb 的文件时,我的系统内存使用量从 30mb 跃升至 300mb+。

基本上,用户上传(例如)4.8mb jpeg,然后上传到 webfaction,然后存储在 Amazon S3 存储桶中。

我认为让我感到沮丧的一件事是我正在使用 easy_thumbnails 生成 3 个文件,这些文件也存储在 S3 存储桶中。

更新 2: 在这一点上,我认为我的主要问题是内存峰值但从未释放。在 Photo.objects.create() 完成后,我将开始研究运行 gc.collect()。看起来 sorl-thumbnail 可能是一个更好的选择,而且听起来它更适合使用远程存储。

更新 1: 我正在使用 django-debug-toolbar 并让它拦截重定向。我现在得到了一些有用的数据。它告诉我我已经执行了 20 个 db 查询(eek),但比这更糟糕(我相信)是 boto(我用于文件存储的东西)正在记录 124 条消息。看起来它正在分别发送每个文件。也许这是正常的,也许不是?无论哪种方式,它似乎都很高。

文件上传后,除非我重置 apache,否则内存永远不会恢复。

这是我的模型:

from django.db import models
from easy_thumbnails.fields import ThumbnailerImageField
from django.conf import settings
import datetime
import os
import string
from django.core.urlresolvers import reverse

...

class Photo(models.Model):
    """
    A photo belongs to a user. A photo has a preview size and the original which
    is referenced when printing or generating the PDF for print.

    """
    def original_resolution(instance, filename):
        """
        Returns a path to upload the image to. The path is created with the
        website's slug and the current year. Using Amazon S3 for CDN storage

        """
        today = datetime.datetime.now()

        return 'uploads/{0}/{1}/{2}/{8}-{3}-{4}-{5}-{6}-{7}'.format(
            instance.owner.pk,
            today.year,
            today.month,
            today.day,
            today.hour,
            today.minute,
            today.second,
            clean_filename(filename),
            'original')

    def thumbnail_resolution(instance, filename):
        """
        Returns a path to upload the image to. The path is created with the
        website's slug and the current year. Using Amazon S3 for CDN storage

        """
        today = datetime.datetime.now()

        return 'uploads/{0}/{1}/{2}/{8}-{3}-{4}-{5}-{6}-{7}'.format(
            instance.owner.pk,
            today.year,
            today.month,
            today.day,
            today.hour,
            today.minute,
            today.second,
            clean_filename(filename),
            'thumbnail')

    def editor_resolution(instance, filename):
        """
        Returns a path to upload the image to. The path is created with the
        website's slug and the current year. Using Amazon S3 for CDN storage

        """
        today = datetime.datetime.now()
        return 'uploads/{0}/{1}/{2}/{8}-{3}-{4}-{5}-{6}-{7}'.format(
            instance.owner.pk,
            today.year,
            today.month,
            today.day,
            today.hour,
            today.minute,
            today.second,
            clean_filename(filename),
            'editor')

    height = models.PositiveIntegerField(blank=True)

    width = models.PositiveIntegerField(blank=True)

    owner = models.ForeignKey(settings.AUTH_USER_MODEL)

    original = ThumbnailerImageField(
        upload_to=original_resolution,
        resize_source=dict(size=(0, 3100), crop="scale", quality=99),
        height_field='height',
        width_field='width',
        verbose_name=u'Choose Photo')

    thumbnail = ThumbnailerImageField(
        upload_to=thumbnail_resolution,
        resize_source=dict(size=(0, 100), crop="scale"),
        blank=True,
        null=True)

    editor = ThumbnailerImageField(
        upload_to=editor_resolution,
        resize_source=dict(size=(0, 1000), crop="scale"),
        blank=True,
        null=True)

    def get_absolute_url(self):
        return reverse('photos:proxy_editor_image', kwargs={
            'pk': self.pk})

    def __unicode__(self):
        return "Photo #{}".format(self.pk)

...

这是我处理上传的视图:

@login_required
def upload_photo(request):
    """
    Creates and saves a new photo.
    """

    if request.is_ajax():
        response = {}

        form = UploadPhotoForm(data=request.POST, files=request.FILES)
        if form.is_valid():
            new_photo = Photo.objects.create(
                original=form.cleaned_data['original'],
                thumbnail=form.cleaned_data['original'],
                editor=form.cleaned_data['original'],
                owner=form.cleaned_data['owner']
            )
            response['result'] = 'success'
            response['message'] = 'Photo successfully uploaded!'
            response['new_photo_pk'] = new_photo.pk
            response['thumbnail_path'] = new_photo.thumbnail.url
            response['editor_path'] = new_photo.editor.url
            response['original_path'] = new_photo.original.url
            response['editor_path_proxy'] = new_photo.get_absolute_url()

        else:
            response['result'] = 'fail'
            response['message'] = 'The photo failed to upload.'
            response['new_photo_pk'] = False

        return HttpResponse(
            json.dumps(response),
            content_type='application/json'
        )

    if request.method == 'POST':
        form = UploadPhotoForm(request.POST, request.FILES)

        if form.is_valid():
            Photo.objects.create(
                original=form.cleaned_data['original'],
                thumbnail=form.cleaned_data['original'],
                editor=form.cleaned_data['original'],
                owner=form.cleaned_data['owner']
            )
            messages.success(request, "Photo successfully uploaded!")
        else:
            messages.error(request, "The photo failed to upload.")

    return HttpResponseRedirect(reverse('photos:list'))

我很困惑,我可能只是不明白一些事情。帮忙?

几个月来,我一直在努力解决这个内存使用问题,最终将其缩小到这个范围。

FWIW,我使用的是 django1.6 python 2.7,webfaction 托管,amazon s3 存储桶。

【问题讨论】:

  • 你解决过这个问题吗?我遇到了与 django 的 FileField 和 s3 存储桶存储完全相同的问题

标签: python django apache file-upload amazon-s3


【解决方案1】:

这很可能与您的 Apache 设置有关(worker 与 prefork)。这个过程,不管它是什么,都会咀嚼一堆内存,然后永远不会释放它。我们发生了同样的事情,最终将应用程序的各个部分放入单独的 wsgi 进程组中,这样我们将阻止内存繁重的请求导致所有 wsgi 进程消耗大量内存。

由于这个问题,我最终与 mod_wsgi 的创建者进行了深入交谈:Django, python, mod_wsgi and Apache worker

我使用 htop 来诊断一些内存繁重的进程。然后,根据我的发现,在 apache 中设置多个 WSGIDaemonProcess 指令,就像这样......

WSGIDaemonProcess article-app processes=10 threads=5 display-name=articles user=myuser group=mygroup python-path=/home/admin/.virtualenvs/django/lib/python2.7/site-packages
WSGIDaemonProcess account-app-memory-heavy processes=2 threads=5 display-name=account user=myuser group=mygroup python-path=/home/admin/.virtualenvs/django/lib/python2.7/site-packages 

您还可以使用这些选项进行调试并可能提高性能:

inactivity-timeout=300 maximum-requests=100

然后在我的 VirtualHost 指令中...

<Location /article/>
    WSGIProcessGroup articles
</Location>
<Location /account/>
    WSGIProcessGroup account
</Location>

现在您可以将大量内存上传限制为 1 或 2 个进程,并让 N 个其他进程为您的应用程序的其余部分提供服务,而不会消耗太多内存。在我们的例子中,我们的文章应用程序每个 wsgi 进程消耗 143M,我们的帐户应用程序消耗 337M。我们将帐户应用程序限制为 2 个进程,将文章限制为 10 个,这会产生非常可预测的内存占用。

【讨论】:

  • 啊,我读过这个,有些人甚至将他们的 Django 管理员移到了那个单独的组中,但我认为这样主要组不必担心在其中保存管理员 URL记忆。 ....或类似的东西。我试试看。
【解决方案2】:

这可能是因为据我所知,WebFaction 仍然使用过时的 mod_wsgi,甚至可能是 Apache 2.2。如果大文件上传非常缓慢地以小块的形式出现,则会出现问题。使用最新的 mod_wsgi 版本,最好使用 Apache 2.4,其中包含与内存使用相关的其他修复,您可能会看到更好的结果。

【讨论】:

  • 我认为你对 WF 的 mod_wsgi 是正确的,但是,我在本地(OSX 10.10.2 w'runserver)测试了这个,看看我是否可以让它发挥作用,它确实做到了。一个进程将位于〜30mb,一旦我开始上传(使用视图)它就会跳到〜300mb。然后它会回到〜80mb。永远不要回退到 30mb。如果我再次上传,它会飙升,然后回到〜80或〜90。我也一直在阅读easy_thumbnails,这似乎是一个很常见的问题。啊...话虽如此,我要确保 mod_wsgi 和 Apache 在两个环境中都是最新的。
  • 不幸的是,在 WebFaction 上升级 mod_wsgi 并不是一个简单的过程。您不能只从源代码编译最新的 mod_wsgi,因为他们的 Apache 安装缺少执行此操作所需的位。因此,您还必须自己构建 Apache。乱七八糟的。
猜你喜欢
  • 1970-01-01
  • 2020-01-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-08
  • 2019-09-17
  • 1970-01-01
相关资源
最近更新 更多