Как прямо сейчас получить отчет из базы данных если он готовится 10-15 минут?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как получить отчет из базы данных при долгой подготовке
При работе с большими объемами данных, аналитическими отчетами или сложными вычислениями часто возникает задача: как предоставить клиенту результат, если его подготовка занимает 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 кэшированные результаты + готовьте новые в фоне.