Разработка функциональности автоматического обновления статуса задачи в ЦАП
Демонстрация
https://github.com/user-attachments/assets/8b53fc13-e3ee-4f71-a501-400eed4902fb
Как оно работает?
Фрагмент HTML со статусом задачи был выделен в отдельный шаблон student/task_status.jinja, student/task.jinja безусловно его включает в себя. Кроме того, этот статус теперь находится в div с id равным task-status. POST запрос на /group/<int:gid>/variant/<int:vid>/task/<int:tid>/ws при успешном отправлении задачи перенаправляет на себя же.
Была добавлена ручка /group/<int:gid>/variant/<int:vid>/task/<int:tid>/ws с WebSocket. При подключении он ждёт пока данная задача не будет проверена, после чего отправляет HTML с новым статусом задачи и закрывает сокет.
JS на /group/<int:gid>/variant/<int:vid>/task/<int:tid>, который добавляется на уровне шаблона только если задача проверяется, подключается к выше упомянутому WebSocket и заменяет содержимое #task-status на каждое пришедшее сообщение.
Почему оно так работает?
Было 3 варианта решить эту задачу:
- Поллинг на стороне клиента - просто, чуть меньше нагружает сервер, возможно чуть больше тратит трафика, и мне вроде говорили так не делать
- SSE - не нужно подключать сторонние библиотеки, старше чем сами вебсокеты (вроде), проще тестировать из pytest, но данный функционал будет сложнее вынести в отдельный процесс, о чём будет ниже
- WebSocket - текущая реализация
Так как для последних двух способов необходимо держать "живой" запрос, которому необходимо занимать один из потоков у WSGI-сервера, придётся увеличить пул воркеров и потоков у каждого воркера, иначе эти долгоживущие запросы имеют вероятность повесить сервер. Для избежания этого в текущей реализации сервер закрывает сокет либо если задача не отправлена, либо как только она проверена.
В реализации с WS это проще вынести в отдельный процесс, а reverse-proxy вроде nginx будет распознавать запросы к этому WS и перенаправлять их на этот процесс. Это снизит нагрузку на основное приложение и избавит от необходимости увеличивать количество потоков.
Что стоит изменить в будущем?
Добавить PubSub
На данный момент сервер проверяет статус задачи простым поллингом в цикле без каких либо задержек. Можно либо добавить Redis/Valkey как ещё один сервис, либо поддерживать только PostgreSQL и использовать его аналогичные способности. SQLAlchemy в теории поддерживает похожий функционал, но он не очень хорошо работает с multiprocessing, поэтому им не воспользовался.
Тесты
Я также не нашёл способ тестировать WS из pytest, поэтому тесты не добавил, но make coverage мне говорит, что /group/<int:gid>/variant/<int:vid>/task/<int:tid> в целом не покрыт тестами, так что считаю своё решение чуточку более обоснованным (:
Минимизация static/js/autoreload.js
Руками это не очень хорошо делать, поэтому надо придумать способ внедрить это в сборочную систему, который видимо сейчас тоже нет.