Реализация очереди импорта товаров (фоновая обработка)
Представьте: загружаете прайс-лист с 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. Гарантируем отсутствие потерь данных даже при сбое воркера.
Получите консультацию — мы предложим архитектуру и точную оценку. Свяжитесь с нами, чтобы обсудить импорт ваших товаров.







