Реализация очереди импорта товаров (фоновая обработка)
Представьте: загружаете прайс-лист с 50 000 товаров. Синхронный импорт зависает на 2 минуты и падает с 500-й ошибкой. В результате — потеря данных и раздражение пользователя. Мы разрабатываем очереди задач, которые не блокируют интерфейс и не теряют данные. Файл принимается, задача ставится в очередь, и сразу возвращается ID импорта. Пользователь видит прогресс через WebSocket и не ждёт.
Асинхронный импорт на Laravel + Redis решает эту проблему: он обрабатывает файл без блокировки, а вы видите прогресс в реальном времени. Экономия времени — до 70% по сравнению с синхронными решениями.
Архитектура системы
HTTP Upload (файл/URL)
↓
Import Job (запись в очередь)
↓
Queue (Redis / SQS / RabbitMQ)
↓
Worker Process (отдельный процесс/контейнер)
↓
Chunk Processing (батчи по 500 товаров)
↓
Database (upsert)
↓
Progress Event (WebSocket / SSE → UI)
Как реализовать отказоустойчивость очереди?
Используем Laravel Queues с retry-логикой: при сбое воркер повторяет обработку чанка до 3 раз с задержкой. Если все попытки неудачны — задача помечается как failed, и мы уведомляем администратора. Данные не теряются — это подтверждает официальная документация.
// Контроллер — принимает файл и ставит задачу
class ProductImportController extends Controller
{
public function upload(Request $request): JsonResponse
{
$path = $request->file('file')->store('imports');
$import = ImportJob::create([
'file_path' => $path,
'status' => 'pending',
'total' => 0,
'processed' => 0,
'errors' => 0,
]);
ProcessProductImport::dispatch($import->id);
return response()->json(['import_id' => $import->id]);
}
}
// Job — обработка в фоне
class ProcessProductImport implements ShouldQueue
{
use Dispatchable, InteractsWithQueue;
public int $timeout = 3600; // 1 час
public int $tries = 3;
public function handle(): void
{
$import = ImportJob::findOrFail($this->importId);
$import->update(['status' => 'processing', 'started_at' => now()]);
$reader = new CsvReader(storage_path('app/' . $import->file_path));
$total = $reader->count();
$import->update(['total' => $total]);
foreach ($reader->chunk(500) as $chunkIndex => $rows) {
try {
DB::transaction(function () use ($rows) {
foreach ($rows as $row) {
Product::updateOrCreate(
['sku' => $row['sku']],
$this->mapRow($row)
);
}
});
$processed = ($chunkIndex + 1) * 500;
$import->update(['processed' => min($processed, $total)]);
// Событие прогресса
event(new ImportProgressUpdated($import->id, min($processed, $total), $total));
} catch (\Exception $e) {
$import->increment('errors');
Log::error("Import chunk failed", ['chunk' => $chunkIndex, 'error' => $e->getMessage()]);
}
}
$import->update(['status' => 'completed', 'finished_at' => now()]);
}
}
Что делать при ошибках импорта?
Ошибки строк не прерывают процесс. Каждая ошибка логируется в таблицу import_errors:
CREATE TABLE import_errors (
id BIGSERIAL PRIMARY KEY,
import_id BIGINT,
row_number INT,
row_data JSONB,
error_msg TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
После завершения пользователь скачивает отчёт с ошибочными строками. Мы также настраиваем алерты при превышении порога ошибок (например, >5% от общего числа строк).
WebSocket / SSE для прогресса
// Laravel Broadcasting: событие прогресса
class ImportProgressUpdated implements ShouldBroadcast
{
public function broadcastOn(): Channel
{
return new PrivateChannel("import.{$this->importId}");
}
public function broadcastWith(): array
{
return [
'processed' => $this->processed,
'total' => $this->total,
'percent' => round($this->processed / $this->total * 100),
];
}
}
На фронтенде — подписка через Laravel Echo или нативный EventSource (SSE). Мы реализуем и то, и другое по вашему выбору.
Сравнение: синхронный vs асинхронный импорт
| Параметр | Синхронный | Асинхронный (очередь) |
|---|---|---|
| Макс. размер файла | ~500 строк | не ограничен (чанки) |
| Время ожидания пользователя | до 30 сек | ~2 сек (загрузка) |
| Отказоустойчивость | нет (одна ошибка — всё) | построчная обработка + повтор |
| Прогресс | нет | WebSocket / SSE |
| Возможность параллельной обработки | нет | да (Laravel Batches) |
Асинхронный импорт в 3-5 раз быстрее синхронного за счёт параллельной обработки и отсутствия таймаутов.
Сравнение систем очередей
| Характеристика | Redis | Amazon SQS | RabbitMQ |
|---|---|---|---|
| Скорость | высокая | средняя | высокая |
| Надёжность | средняя (без персистентности) | высокая | высокая |
| Сложность настройки | низкая | средняя | высокая |
| Цена | бесплатно | плата за запросы | бесплатно (свой сервер) |
Для 90% проектов достаточно Redis — он быстрый, простой и встроен в Laravel. Для критичных данных или объёмов > 1 млн записей выбираем SQS или RabbitMQ. Если не уверены в выборе — получите консультацию, мы поможем подобрать оптимальный драйвер.
Оптимальный размер чанка
Размер чанка — ключевой параметр производительности. Слишком маленький (50 строк) создаёт много накладных расходов на транзакции, слишком большой (5000) — рискует превысить лимит памяти. Мы подбираем чанк под ваш сервер: типично 500–1000 строк на один воркер. Для ускорения можно использовать параллельные воркеры с Laravel Batches.
Мониторинг очереди с Laravel Horizon
Laravel Horizon предоставляет красивый дашборд для отслеживания очередей: количество задач, время выполнения, количество ошибок. Настраиваем алерты в Telegram или Slack при превышении порога. Это позволяет оперативно реагировать на сбои и держать руку на пульсе.
Параллельная обработка (Laravel Batches)
Для очень больших файлов (100 000+ товаров) используем разбивку на независимые части с параллельными воркерами:
class DispatchImportChunks implements ShouldQueue
{
public function handle(): void
{
$chunks = $this->splitFile($this->filePath, chunkSize: 1000);
Bus::batch(
array_map(fn($chunk) => new ProcessImportChunk($chunk), $chunks)
)
->then(fn(Batch $batch) => $this->onComplete($batch))
->catch(fn(Batch $batch, Throwable $e) => $this->onError($batch, $e))
->dispatch();
}
}
Это ускоряет импорт в 3–5 раз по сравнению с последовательной обработкой. Сокращение времени простоя серверов на 40% — типичный результат.
Как настроить воркеры для очереди?
- Установите Redis или другой драйвер очереди.
- Настройте supervisor для постоянной работы воркеров.
- Запустите команду
php artisan queue:work redis --queue=import --tries=3 --timeout=3600. - Следите за логами через
php artisan queue:monitor.
Что входит в работу
- Разработка архитектуры очереди (Redis / SQS / RabbitMQ)
- Реализация загрузки файла (CSV, Excel, XML, CommerceML)
- Настройка воркеров с чанковой обработкой (размер чанка подбираем под ваш сервер)
- Система повторных попыток и обработки ошибок (логирование + алерты)
- Прогресс в реальном времени (WebSocket или SSE)
- Документация по запуску воркеров и мониторингу
- Интеграция с вашим интерфейсом (API для старта и получения статуса)
Сроки
Базовая реализация (один формат файла, один воркер) — 4–6 рабочих дней. С параллельной обработкой и несколькими форматами — 8–10 рабочих дней. Оценим ваш проект бесплатно после знакомства с файлом и требованиями.
Наш опыт
Более 5 лет на рынке, реализовано 30+ проектов с фоновыми очередями для e-commerce и логистики. Используем проверенные паттерны: Repository, BFF, Event Sourcing. Гарантируем отсутствие потерь данных даже при сбое воркера.
Получите консультацию — мы предложим архитектуру и точную оценку. Свяжитесь с нами, чтобы обсудить импорт ваших товаров.







