Работа с потоками в Django и Gunicorn

Я пытаюсь сделать бота, который каждые несколько минут делает API запрос, выполняет некоторые вычисления и некоторые действия на их основе. Я сделал так: сначала создал модель Bot, затем модель BotGroup, которая запускает ботов по расписанию с помощью модуля sched.

import sched
import threading
from django.db import models


class Bot(models.Model):
   on_status = models.BooleanField()

   def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.busy = False

   def run(self):
       self.busy = do_stuff()


class BotGroup(models.Model):
    name = models.CharField(max_length=16)
    bots = models.ManyToManyField(Bot)    
    sch = sched.scheduler(time.time, time.sleep)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.on_status = False
        self.events = {}
        self.th = None

    def start(self, thread):
        self.th = thread
        self.on_status = True
        self._init_bots()
        self._run()

    def stop(self):
        self.on_status = False
        for bot in self.bot_set:
            if not bot.busy:
                if bot.id in self.events.keys():
                    for event in self.events[bot.id]:
                        if event in self.sch.queue:
                            self.sch.cancel(event)

    def _init_bots(self):
        self.bot_set = self.bots.all()
        for bot in self.bot_set:
            bot.bg = self

    def _check_bots(self):

        delay = self._get_delay() # Returns delay

        for bot in self.bot_set:
            if ((bot.busy or self.on_status and bot.on_status)
                and (bot.id not in self.events.keys()
                     or self.events[bot.id] not in self.sch.queue)):
                self.events[bot.id] = self.sch.enterabs(
                    delay, bot.id, bot.run)

    def _run(self):
        while self.on_status or self.sch.queue:
            self._check_bots()
            next_ev = self.sch.run(False)
            if next_ev is not None:
                time.sleep(min(5, next_ev))
            else:
                time.sleep(5)


class BotsThread(threading.Thread):

    def __init__(self, bg):
        super().__init__()
        self.bg = bg
        self.name = bg.name

    def run(self):
        self.bg.start(self)

    def stop(self):
        self.bg.stop()

Затем у меня есть представление 'app:bot', в котором есть кнопка запуска/остановки, которая :

{% if bg.on_status %}
  <a href="{% url 'app:bot_stop' bg.id %}">STOP</a>
{% else %}
  <a href="{% url 'app:bot_start' bg.id %}">START</a>
{% endif %}

Кнопка "Пуск" переходит к просмотру:

def bot_start(request, pk):
    if request.method == 'GET':
        bg = BotGroup.objects.get(id=pk)
        th = BotsThread(bg)
        th.start()
        th_ready = False
        while not th_ready:
            time.sleep(1)
            th_ready = th.is_alive()
        return redirect('app:bot')
    else:
        return HttpResponseBadRequest('Invalid request')

Проблема начинается в представлении для рендеринга 'app:bot', когда я пытаюсь получить экземпляр BotGroup. Сначала я попробовал сделать это так:

def bot(request):
    if request.method == 'GET':        
        context = {
            'bg': BotGroup.objects.get(id=1),
        }
        return render(request, 'bot.html', context)
    else:
        return HttpResponseBadRequest('Invalid request')

Но когда я использовал кнопку "Старт", она запускала тему, но на странице по-прежнему было написано START. Затем я заметил, что если перезагрузить страницу несколько раз, то в конце концов кнопка меняется на "STOP". Но если вы перезагрузите страницу снова через некоторое время, она снова изменится на "START", хотя поток продолжает работать в фоновом режиме. Это приводит к первому вопросу:

  1. Почему атрибут bg.on_status читается как False, хотя поток запущен?

Вторая и гораздо более серьезная проблема проявилась при развертывании на AWS (1 vCPU, 1Gb RAM). Сначала я использовал стандартный конфиг gunicorn:

[Unit]
Description=gunicorn daemon
Requires=app.socket
After=network.target

[Service]
User=app
Group=www-data
WorkingDirectory=/opt/app/src
ExecStart=/opt/app/ve/bin/gunicorn \
          --log-level=debug \
          --capture-output \
          --log-file /var/log/app.log \
          --access-logfile - \
          --workers 3 \
          --bind unix:/run/app.sock \
          app.wsgi:application

[Install]
WantedBy=multi-user.target

Когда я попытался запустить бота, сначала он на некоторое время завис, а затем выдал 502. После нескольких попыток он в конце концов загрузил страницу. И так продолжается до сих пор. Я попробовал изменить рабочие gunicorn на gthread, немного поиграл с числом, но, похоже, это только ухудшило ситуацию по сравнению с дефолтной. Поэтому мой второй вопрос:

  1. Для этой установки, где приложение использует несколько потоков (пока я запускаю только один экземпляр BotGroup, но планирую запустить несколько), какой лучший способ настроить gunicorn для 1vCPU экземпляра AWS ec2?

И поскольку я полный профан в многопоточности:

  1. Делаю ли я что-то неправильно в целом? Могу ли я улучшить всю настройку?
Вернуться на верх