Ужасная Производительность Джанго Админа - PullRequest
0 голосов
/ 28 августа 2018

Я занимаюсь разработкой проекта Django, в котором уже собрано много реальных данных, чтобы я мог увидеть его производительность.

Производительность нескольких списков DjangoAdmin просто ужасна.

У меня есть один список администраторов, давайте назовем его devices. В этом списке я получаю дополнительную информацию для каждой строки, эти поля связаны с другими таблицами и связаны через FK / PK / M2N.

Список содержит около 500 записей, и загрузка этого экрана занимает, согласно django-debug-toolbar, около 6,5 секунд, что невыносимо.

Этот админ класс

@admin.register(Device)
class DeviceAdmin(admin.ModelAdmin):
    list_select_related = True
    list_display = ('id', 'name', 'project', 'location', 'machine', 'type', 'last_maintenance_log')
    inlines = [CommentInline, TestLogInline]

    def project(self, obj):
        try:
            return Device.objects.get(pk=obj.pk).machine.location.project.project_name
        except AttributeError:
            return '-'

    def location(self, obj):
        try:
            return Device.objects.get(pk=obj.pk).machine.location.name
        except AttributeError:
            return '-'

    def last_maintenance_log(self, obj):
        try:
            log = AdminLog.objects.filter(object_id=obj.pk).latest('time')
            return '{} | {}'.format(log.time.strftime("%d/%m/%Y, %-I:%M %p"), log.title)
        except AttributeError:
            return '-' 

Все Machine, Location и Project являются таблицами в базе данных.

Изучив запросы в django-debug-toolbar, я обнаружил нечто ужасное. Этот экран требует 287 sql запросов! Да, более двух сотен!

Что я могу сделать, чтобы сократить это время до чего-то разумного?

EDIT: Благодаря Бруно я удалил Device.objects.get(pk=obj.id) (поскольку это было действительно избыточно, я полностью упустил это из виду.

Так везде я ставлю obj. например obj.machine.location.project.project_name

Только это само по себе снижает скорость и количество запросов до половины, пока все хорошо.

У меня нет проблем со слиянием obj подхода и select_related подхода. Это мой текущий код, который хуже, чем только obj подход.

def project(self, obj):
        try:
            Device.objects.select_related('machine__location__project').get(id=obj.pk).machine.location.project.project_name
        except AttributeError:
            return '-'

Что создает хорошие ВНУТРЕННИЕ СОЕДИНЕНИЯ в запросах, но производительность примерно на 15% ниже, чем без него, и используется только obj.machine.location.project.project_name

Что я делаю не так?

EDIT2:

Лучшая производительность, которую я получил, с этим кодом:

@admin.register(Device)
class DeviceAdmin(admin.ModelAdmin):
    list_select_related = True
    save_as = True
    form = DeviceForm
    list_display = ('id', 'name', 'project', 'location', 'machine', 'type', 'last_maintenance_log')
    inlines = [CommentInline, TestLogInline]

    def project(self, obj):
        try:
            return obj.machine.location.project.project_name
        except AttributeError:
            return '-'

    def location(self, obj):
        try:
            return obj.machine.location.name
        except AttributeError:
            return '-'

    def last_maintenance_log(self, obj):
        try:
            log = AdminLog.objects.filter(object_id=obj.pk).latest('time')
            return '{} | {}'.format(log.time.strftime("%d/%m/%Y, %-I:%M %p"), log.title)
        except AttributeError:
            return '-'

    def get_queryset(self, request):
        return Device.objects.select_related('machine__location__project').all()

Это привело к уменьшению количества запросов до 104 (с почти 300) и сокращению времени более чем на 50%. Можно ли это еще улучшить?

1 Ответ

0 голосов
/ 28 августа 2018

Во-первых, избегайте совершенно бесполезных запросов - это:

Device.objects.get(pk=obj.pk)

настолько бесполезен, насколько это возможно, поскольку obj уже экземпляр Device, который вы ищете.

Затем переопределите ваш DeviceAdmin.get_queryset метод , чтобы правильно использовать select_related и prefetch_related, чтобы у вас было только минимально необходимое количество запросов ,

...