← Назад к вопросам

Как прямо сейчас получить отчет из базы данных если он готовится 10-15 минут?

1.8 Middle🔥 11 комментариев
#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как получить отчет из базы данных при долгой подготовке

При работе с большими объемами данных, аналитическими отчетами или сложными вычислениями часто возникает задача: как предоставить клиенту результат, если его подготовка занимает 10-15 минут? Простое ожидание в HTTP запросе неприемлемо. Рассмотрим современные подходы.

1. Асинхронная обработка с polling

Клиент инициирует запрос, получает ID задачи и периодически проверяет статус:

// Фронтенд - инициирование
async function generateReport() {
  const response = await fetch("/api/v1/reports/generate", {
    method: "POST",
    body: JSON.stringify({ period: "2024-01" })
  });
  
  const { task_id } = await response.json();
  return task_id;
}

// Фронтенд - polling статуса
async function pollReportStatus(taskId, maxAttempts = 90) {
  for (let i = 0; i < maxAttempts; i++) {
    const response = await fetch(`/api/v1/reports/status/${taskId}`);
    const { status, data, error } = await response.json();
    
    if (status === "completed") {
      return { success: true, data };
    }
    
    if (status === "failed") {
      return { success: false, error };
    }
    
    // Пауза 10 секунд перед следующей проверкой
    await new Promise(resolve => setTimeout(resolve, 10000));
  }
  
  throw new Error("Таймаут: отчет не был подготовлен");
}

// Использование
const taskId = await generateReport();
const result = await pollReportStatus(taskId);

Минусы: повышенная нагрузка на сервер, задержки, неэффективно по трафику.

2. WebSocket для real-time обновлений

Наиболее элегантный способ - установить WebSocket соединение и получать обновления в реальном времени:

// Фронтенд - WebSocket подход
async function generateReportWithWebSocket() {
  return new Promise((resolve, reject) => {
    const ws = new WebSocket("wss://api.prepbro.ru/ws/reports");
    
    ws.onopen = () => {
      ws.send(JSON.stringify({
        action: "generate",
        period: "2024-01"
      }));
    };
    
    ws.onmessage = (event) => {
      const message = JSON.parse(event.data);
      
      if (message.type === "progress") {
        console.log(`Прогресс: ${message.percentage}%`);
        updateProgressBar(message.percentage);
      }
      
      if (message.type === "completed") {
        resolve(message.data);
        ws.close();
      }
      
      if (message.type === "error") {
        reject(new Error(message.error));
        ws.close();
      }
    };
    
    ws.onerror = reject;
  });
}

// Использование
try {
  const report = await generateReportWithWebSocket();
  displayReport(report);
} catch (error) {
  console.error("Ошибка генерации отчета:", error);
}

3. Server-Sent Events (SSE)

Альтернатива WebSocket - более простой протокол для сервер -> клиент коммуникации:

// Фронтенд - SSE подход
function generateReportWithSSE() {
  return new Promise((resolve, reject) => {
    const eventSource = new EventSource("/api/v1/reports/generate-stream");
    
    eventSource.addEventListener("progress", (e) => {
      const { percentage } = JSON.parse(e.data);
      console.log(`Прогресс: ${percentage}%`);
    });
    
    eventSource.addEventListener("completed", (e) => {
      const { data } = JSON.parse(e.data);
      resolve(data);
      eventSource.close();
    });
    
    eventSource.addEventListener("error", (e) => {
      reject(new Error(e.data));
      eventSource.close();
    });
  });
}

4. Фоновые задачи с Celery/RQ

Для очень долгих операций используйте задачи в фоне:

# Бэкенд - инициирование задачи
from celery import shared_task

@shared_task
def generate_report_task(period):
    # Долгая операция...
    time.sleep(600)  # 10 минут
    return {"report": "данные"}

@app.post("/api/v1/reports/generate")
async def generate_report(request: ReportRequest):
    task = generate_report_task.delay(request.period)
    return {"task_id": task.id}

@app.get("/api/v1/reports/status/{task_id}")
async def get_report_status(task_id: str):
    task = generate_report_task.AsyncResult(task_id)
    
    if task.ready():
        return {
            "status": "completed",
            "data": task.result
        }
    
    return {
        "status": "processing",
        "progress": task.info.get("progress", 0)
    }

5. Кэширование готовых отчетов

Если отчеты часто запрашиваются:

from functools import lru_cache
from datetime import datetime, timedelta

cached_reports = {}

@app.post("/api/v1/reports/generate")
async def generate_report(request: ReportRequest):
    cache_key = f"report_{request.period}"
    
    # Проверить кэш (например, 1 час)
    if cache_key in cached_reports:
        cached_time = cached_reports[cache_key]["time"]
        if datetime.now() - cached_time < timedelta(hours=1):
            return {
                "status": "cached",
                "data": cached_reports[cache_key]["data"]
            }
    
    # Если не в кэше, запустить подготовку
    task_id = str(uuid.uuid4())
    # ... запустить задачу ...
    
    return {"task_id": task_id}

Рекомендуемый выбор

  • WebSocket - лучший для реал-тайм прогресса и интерактивности
  • SSE - проще чем WebSocket для односторонней коммуникации
  • Polling - если низкие требования к responsiveness, просто реализуется
  • Celery - для очень долгих задач (часы/дни)

Комбинируйте подходы: показывайте instant кэшированные результаты + готовьте новые в фоне.