Код, интерфейсы и трафик без воды
lawebbox

Проектирование AI-задач на Go: как управлять длительными процессами при разрыве HTTP-соединения

По материалу на Хабре, в Go-сервисе отмена HTTP-запроса может остановить ожидание ответа от LLM, но не обязательно саму работу у провайдера.

Максим Воронцов, Хардкорный бэкендер и девопс · обновлено 12 октября 2026 г.

Проектирование AI-задач на Go: как управлять длительными процессами при разрыве HTTP-соединения

Для веб-продукта это вопрос не только контекста в коде, но и контроля над длительной платной задачей: пользователь закрыл вкладку, а система может уже потратить ресурсы и потерять результат.

context.Canceled не равен отмене генерации

В обычном API передавать r.Context от handler в сервис и внешние вызовы удобно. Клиент ушёл или сработал таймаут, связанные операции отменяются. Но у LLM-запроса есть внешний участник: что именно происходит после отмены на стороне провайдера, зависит от API и момента отмены.

Локально приложение может увидеть context.Canceled. Это говорит, что отменён контекст, но не доказывает, что провайдер прекратил обработку. Запрос мог уже выполняться, часть токенов могла быть потрачена, а готовый результат мог не дойти до backend. Значит, отмена ожидания и отмена работы не одно и то же.

Для короткого CRUD-вызова разница обычно не меняет архитектуру. Для анализа документа, который занимает десятки секунд, уже важно решить, что означает закрытая вкладка. Пользователь отказался от операции или просто потерял соединение? Если оба случая представлены одним r.Context, транспортный уровень фактически управляет жизненным циклом бизнес-задачи.

Goroutine не заменяет очередь

Оставить обработку в goroutine после ухода клиента можно, но это лишь снимает связь с HTTP-запросом. При остановке контейнера процесс исчезнет вместе с goroutine. Без отдельного механизма пользователь не узнает состояние задачи, повторный запуск может создать дубликат, а восстановление после сбоя не появится само собой.

Есть и нагрузочный сценарий: несколько сотен одновременных запусков могут превратиться в столько же конкурентных обращений к модели. Тогда нужны явные правила для повторов, лимитов, отмены и учёта стоимости. Иначе приложение просто переносит проблему из handler в фоновые потоки, добавляя костыль вместо управления задачами.

Практический вывод для Go-бэкенда: прежде чем отвязывать работу от r.Context, определите, кто владеет задачей после ответа HTTP. Если она должна переживать закрытие страницы, её состояние и жизненный цикл нельзя сводить к соединению клиента. Если закрытие страницы должно останавливать работу, отдельно проверьте, что реально отменяет API провайдера. context.Canceled в логах не является квитанцией об остановленной генерации.