> Как реализовать взаимодействие фронтенда и бэкенда для генерации тяжелого отчета без таймаута (Python)

Уровень: senior · Роль: backend · Язык: Python · Категория: Технические вопросы

Компании: JEDai

Стек: Python

> Пример ответа

Короткий ответ

Для генерации тяжёлого отчёта без таймаута нужно вынести задачу из HTTP-запроса в асинхронный процесс: клиент отправляет запрос на создание отчёта, получает идентификатор задачи, а затем опрашивает статус или подписывается на уведомление. Сам отчёт генерируется в фоновом воркере (Celery, RQ, или отдельный сервис), результат сохраняется в объектное хранилище или БД. Это стандартный паттерн async job + polling/WebSocket.

Подробное объяснение

Проблема: HTTP-запрос имеет жёсткий лимит времени (обычно 30-60 секунд на уровне прокси/балансировщика). Тяжёлый отчёт может генерироваться минуты. Решение - разорвать синхронную связь между запросом и выполнением.

Архитектура:

  1. Клиент отправляет POST /api/reports с параметрами отчёта.
  2. Бэкенд создаёт запись в БД со статусом pending, ставит задачу в очередь (Celery/RQ) и возвращает 202 Accepted с report_id.
  3. Воркер забирает задачу, генерирует отчёт (тяжёлые SQL-запросы, агрегации, экспорт в Excel/PDF), сохраняет файл в S3/MinIO или кладёт результат в БД.
  4. Клиент опрашивает GET /api/reports/{id} - получает статус processing или ready со ссылкой на файл. Либо использует WebSocket/SSE для push-уведомлений.

Ключевые моменты:

  • Очередь должна быть надёжной: RabbitMQ/Redis с persistent-режимом, чтобы задача не потерялась при рестарте.
  • Идемпотентность: повторная генерация того же отчёта не должна создавать дубликаты - используй hash параметров или уникальный ключ.
  • Retry и dead-letter queue: при ошибке воркер должен перезапускать задачу с экспоненциальной задержкой, после N попыток - отправлять в DLQ.
  • Хранение результата: файл в S3 с короткоживущей ссылкой (presigned URL) или в БД как blob (если размер позволяет).
  • Ограничение ресурсов: воркер должен иметь отдельный пул соединений к БД, чтобы не блокировать основной сервис.
  • Таймаут на стороне клиента: если клиент ждёт слишком долго, он может отменить задачу (DELETE /api/reports/{id}) - воркер должен проверять флаг отмены.

На практике

  • Используй Celery с брокером Redis/RabbitMQ - это стандарт для Python. Для простых случаев подойдёт RQ.
  • Для статусов используй enum: pending, processing, completed, failed, cancelled.
  • В API добавь expires_at для отчёта - удаляй старые файлы по расписанию.
  • Если отчёт генерируется дольше 10 минут, добавь прогресс-бар: воркер периодически обновляет progress в БД, клиент показывает процент.
  • Для больших файлов используй потоковую передачу при скачивании (StreamingResponse в FastAPI), чтобы не держать файл в памяти.

Пример кода

PYTHON
# FastAPI + Celery
from celery import Celery
from fastapi import FastAPI, BackgroundTasks
from pydantic import BaseModel
import uuid
app = FastAPI()
celery_app = Celery('tasks', broker='redis://localhost:6379/0')
class ReportRequest(BaseModel):
date_from: str
date_to: str
format: str = 'xlsx'
@celery_app.task(bind=True, max_retries=3)
def generate_report(self, report_id: str, params: dict):
try:
# Обновляем статус
update_status(report_id, 'processing')
# Тяжёлая генерация
file_path = heavy_generation(params)
upload_to_s3(report_id, file_path)
update_status(report_id, 'completed', file_url=presigned_url)
except Exception as exc:
update_status(report_id, 'failed')
raise self.retry(exc=exc, countdown=60)
@app.post('/api/reports', status_code=202)
async def create_report(req: ReportRequest):
report_id = str(uuid.uuid4())
create_report_record(report_id, 'pending')
generate_report.delay(report_id, req.dict())
return {'report_id': report_id}
@app.get('/api/reports/{report_id}')
async def get_report(report_id: str):
record = get_report_record(report_id)
if record.status == 'completed':
return {'status': 'completed', 'url': record.file_url}
return {'status': record.status, 'progress': record.progress}

Как отвечать на собеседовании

Начни с формулировки проблемы: "HTTP-запрос ограничен по времени, поэтому генерация отчёта должна быть асинхронной". Затем опиши паттерн: создание задачи, очередь, воркер, опрос статуса. Упомяни альтернативы: WebSocket для real-time уведомлений, если клиент требует мгновенного обновления. Обязательно скажи про обработку ошибок: retry, DLQ, отмену задач. Если спросят про масштабирование - добавь, что воркеры можно горизонтально масштабировать, а очередь - шардировать.

Что проверяет интервьюер

  • Понимание ограничений HTTP и необходимости асинхронности.
  • Знание паттернов: async job, polling, push-уведомления.
  • Умение проектировать надёжные фоновые задачи: идемпотентность, retry, обработка сбоев.
  • Опыт с конкретными инструментами: Celery, Redis, S3.
  • Умение думать о ресурсах: не блокировать основной сервис, управлять памятью при больших файлах.

Типичные ошибки

  • Пытаться увеличить таймаут на сервере - это не решает проблему, а лишь откладывает её.
  • Генерировать отчёт в BackgroundTasks FastAPI - это не надёжно, задачи теряются при рестарте.
  • Не обрабатывать отмену задачи - клиент ушёл, а воркер продолжает жечь CPU.
  • Хранить файл в памяти воркера - при больших отчётах это OOM.
  • Не использовать идемпотентность - повторный запрос создаёт дубликаты отчётов.
  • Забывать про очистку старых файлов - S3 забивается, расходы растут.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью