Создание сигнала после сохранения вручную замедляет работу приложения, Django

У нас есть приложение Django, которое использует Django-river для управления рабочим процессом. Для повышения производительности нам пришлось использовать bulk_create. Нам нужно вставить данные в пару таблиц по несколько строк в каждой. Изначально мы использовали обычный метод .save(), и рабочий процесс работал должным образом (поскольку сигналы сохранения создавались правильно). Но как только мы перешли на bulk_create, производительность увеличилась с минут до секунд. Но Django_river перестал работать, и сигналов сохранения сообщений по умолчанию не было. Нам пришлось реализовать сигналы на основе имеющейся документации.

      class CustomManager(models.Manager):
    def bulk_create(items,....):
         super().bulk_create(...)
         for i in items:
              [......] # code to send signal

А также

      class Task(models.Model):
    objects = CustomManager()
    ....

Благодаря этому рабочий процесс снова заработал, но генерация сигналов требует времени, и это сводит на нет все улучшения производительности, полученные с помощью bulk_create. Так есть ли способ улучшить создание сигнала?

Подробнее

      def post_save_fn(obj):
    post_save.send(obj.__class__, instance=obj, created=True) 

class CustomManager(models.Manager):
    def bulk_create(self, objs, **kwargs):
        #Your code here
        data_obj = super(CustomManager, self).bulk_create(objs,**kwargs)
        for i in data_obj:
            # t1 = threading.Thread(target=post_save_fn, args=(i,))
            # t1.start()
            post_save.send(i.__class__, instance=i, created=True) 
        return data_obj
        
        
class Test(Base): 
    test_name = models.CharField(max_length=100)
    test_code = models.CharField(max_length=50)
    objects = CustomManager()
    class Meta:
        db_table = "test_db"

2 ответа

В чем проблема?

Как уже упоминалось в комментариях, проблема заключается в том, что функции, которые вызываются через метод, занимают много времени. (Помните, что сигналы не асинхронны!! — это распространенное заблуждение).

Я не знаком с ними, но бегло взглянув на функции, которые будут вызываться после сохранения (см. здесь и здесь), мы увидим, что они включают дополнительные обращения к базе данных.

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

Что можно с этим сделать?

Короче говоря. Немного!! Для подавляющего большинства запросов django медленная часть будет вызывать базу данных. Вот почему мы пытаемся свести к минимуму количество обращений к базе данных (используя такие вещи, как bulk_create).

Прочитав первые несколько абзацев всей идеи, вы перенесете то, что обычно находится в коде, в базу данных. Большим преимуществом здесь является то, что вам не нужно так часто переписывать код и повторно развертывать. Но недостатком является то, что вам неизбежно придется больше обращаться к базе данных, что замедлит работу. Это будет хорошо для некоторых случаев использования, но не для всех.

Я могу придумать две вещи, которые могут помочь:

  • Происходит ли все это в настоящее время как часть цикла запрос/ответ. А если есть, то нужно ли? Если ответы на эти два вопроса «да» и «нет» соответственно, вы можете переместить эту работу в отдельную очередь задач. Это по-прежнему будет медленным, но, по крайней мере, не замедлит работу вашего сайта.
  • В зависимости от того, каковы ваши рабочие процессы и характер данных, которые вы создаете, может случиться так, что вы сможете делать все, что post_saveсигналы выполняют свою собственную функцию и делают это более эффективно. Но это определенно будет зависеть от ваших данных и вашего приложения и отойдет от философии django-river.

Используйте отдельного работника, если логика «сигнала» позволяет вам выполняться после массового сохранения.

Вы можете создать дополнительную таблицу очереди и поместить метаданные о том, что делать для вашего будущего работника.

Создайте отдельный воркер (модуль Django) с необходимой логикой и данными из таблицы очереди. Вы можете сделать это как команду управления, это позволит вам запускать воркер в основном потоке (вы можете запускать команды управления из обычного кода Django) или вы можете запускать его с помощью crontab по расписанию.

Как запустить такого воркера?

Если вам нужно что-то сделать так же близко, как вы создали записи - запустите это в отдельном потоке с помощью threadingмодуль. Таким образом, ваш жизненный цикл запроса-ответа будет выполнен сразу после того, как вы запустите новый поток.

В противном случае, если вы можете сделать это позже - составьте расписание и запустите его с помощью crontab, используя структуру команд управления.

Другие вопросы по тегам