Реалізація масового імпорту товарів (Bulk Import) на сайт

Коли прайс-лист на десятки тисяч позицій потрібно завантажити на сайт, звичайне завантаження може призвести до зависання та збоїв. Ми розробляємо масовий імпорт товарів (bulk import) із чанковою обробкою та чергами, щоб дані завантажувалися швидко і без перевантаження бази. Наша команда реалізує проєкт під ключ — від аудиту до впровадження та подальшої підтримки, забезпечуючи стабільну роботу навіть із великими обсягами.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 2062 послуг
Реалізація масового імпорту товарів (Bulk Import) на сайт
Середній
~5 днів
Часті запитання

Коли 150 000 позицій кладуть сайт

Уявіть: клієнт приносить прайс-лист на 150 000 товарів. Спроба завантажити все одразу — і сайт висне, база не відповідає. Це типовий запит на Bulk Import. За 5 років ми реалізували понад 80 проектів з обсягами до 500 000 SKU. Накопичили досвід, як уникнути повільного завантаження, N+1 запитів та крашу БД. Розкажемо, як досягти стабільності за допомогою чанків, черг та попереднього завантаження довідників.

Обсяг Метод Час обробки
До 1 000 позицій Синхронно в запиті Секунди
1 000 – 50 000 Одна чергова задача з чанками Хвилини
50 000 – 500 000 Fan-out: N паралельних Jobs 10–60 хвилин
Понад 500 000 Batch insert + окремий pipeline Години

Які проблеми вирішуємо

Повільне завантаження: поштучні INSERT/UPDATE 100 000 рядків займають години. N+1 запити при розв'язанні довідників вбивають продуктивність. Краш БД через неоптимальні транзакції. Втрата даних при помилках у середині імпорту. Всі ці болі усуваються правильною архітектурою. Замовте розробку вже сьогодні — ми покажемо, як це працює на вашому проекті.

Як масовий імпорт впливає на продуктивність?

Ключовий фактор — обсяг даних. Для 1 000 позицій достатньо синхронної обробки, для 100 000 — асинхронна черга з чанками.

Метод Час на 100 000 записів Навантаження на БД
Синхронний поштучний ~30 хвилин Висока (500 запитів/сек)
Асинхронний chunk+upsert ~5 хвилин Низька (50 запитів/сек)

Bulk upsert в 10 разів швидше поштучних запитів — це підтверджує наша практика.

Чому chunk + queue — ключ до стабільності?

Файл з 100 000 рядків не обробляється в один Job. Розбиваємо на чанки по 500 рядків, кожен чанк — окремий Job в черзі bulk-import. Воркери (2–4) обробляють паралельно, не зачіпаючи основну чергу.

class BulkImportDispatcher {
    private const CHUNK_SIZE = 500;

    public function dispatch(ImportFile $file): void {
        $import = ImportRun::create([
            'file_id' => $file->id,
            'status' => 'dispatching',
            'total' => 0,
        ]);

        $chunkIndex = 0;
        foreach ($file->parser()->chunks(self::CHUNK_SIZE) as $chunk) {
            ProcessImportChunkJob::dispatch($import->id, $chunkIndex, $chunk)
                ->onQueue('bulk-import');
            $chunkIndex++;
        }

        $import->update([
            'status' => 'processing',
            'total_chunks' => $chunkIndex,
        ]);
    }
}

Технічна реалізація: від чанків до upsert

Bulk Upsert замість поштучних INSERT/UPDATE

Головний інструмент продуктивності — INSERT ... ON CONFLICT DO UPDATE (upsert). Laravel підтримує це через Model::upsert(). Одна операція upsert для 500 рядків у PostgreSQL займає ~50–200 мс — проти 500 × 5 мс = 2500 мс для поштучних запитів.

class ProcessImportChunkJob implements ShouldQueue {
    public int $timeout = 120;

    public function handle(): void {
        $rows = [];
        foreach ($this->chunk as $item) {
            $rows[] = [
                'sku' => $item['sku'],
                'name' => $item['name'],
                'price' => $item['price'],
                'qty' => $item['qty'],
                'category_id' => $this->resolveCategory($item['category']),
                'source_id' => $this->import->source_id,
                'updated_at' => now(),
                'created_at' => now(),
            ];
        }

        Product::upsert(
            $rows,
            uniqueBy: ['sku'],
            update: ['name', 'price', 'qty', 'category_id', 'updated_at']
        );

        DB::table('import_runs')
            ->where('id', $this->importId)
            ->increment('processed_chunks');
    }
}

Попереднє завантаження довідників у пам'ять

Найдорожча операція — запити до БД для розв'язання залежностей. Рішення — завантажити всі довідники в пам'ять перед обробкою:

class ImportContext {
    private array $categoryMap;
    private array $supplierMap;
    private array $existingSkus;

    public function preload(int $sourceId): void {
        $this->categoryMap = Category::pluck('id', 'name_normalized')->all();
        $this->supplierMap = Supplier::pluck('id', 'code')->all();
        $this->existingSkus = Product::where('source_id', $sourceId)
            ->pluck('id', 'sku')->all();
    }

    public function resolveCategoryId(string $name): ?int {
        return $this->categoryMap[mb_strtolower(trim($name))] ?? null;
    }

    public function productExists(string $sku): bool {
        return isset($this->existingSkus[$sku]);
    }
}

Фінальний Job: агрегація результатів

Використовуємо Bus::batch() — вбудований механізм Laravel для групування задач з колбеком на завершення:

Bus::batch(
    collect($chunks)->map(fn($chunk, $i) => new ProcessImportChunkJob($importId, $i, $chunk))
)->then(function (Batch $batch) use ($importId) {
    ImportRun::find($importId)->update([
        'status' => 'completed',
        'completed_at' => now(),
    ]);
    PostImportPipeline::dispatch($importId);
})->onQueue('bulk-import')->dispatch();

Post-import pipeline

Після завершення імпорту потрібно оновити денормалізовані дані: перерахувати наявність, оновити пошуковий індекс, фасети.

class PostImportPipeline {
    public function handle(int $importId): void {
        $productIds = ImportedProduct::where('import_id', $importId)->pluck('product_id');
        Product::whereIn('id', $productIds)->each(function (Product $p) {
            $p->update(['in_stock' => $p->qty > 0]);
        });
        Product::whereIn('id', $productIds)->searchable();
        FilterValueRebuilder::dispatch($productIds);
    }
}

Моніторинг та обмеження навантаження

В адмін-інтерфейсі оператор бачить прогрес у реальному часі: кількість оброблених, помилки, час, що залишився. Дані беруться з таблиці import_runs. Виділяємо чергу bulk-import з 2–4 воркерами, основну default не зачіпаємо. Важкий імпорт запускаємо в нічний час через планувальник. Для кожної помилки записується лог, і оператор може перезапустити лише невдалі чанки — це типовий кейс для інтернет-магазинів з великим каталогом. Додатково підтримуємо інтеграцію з 1С та CommerceML — популярними джерелами даних в українському e-commerce. Отримайте консультацію — ми допоможемо налаштувати такий механізм під ваш проект.

Типові помилки при проектуванні імпорту
  1. Неправильний розмір чанка: занадто маленький (багато Job-ів, оверхед черги) або занадто великий (таймаут, навантаження на пам'ять). Оптимальний розмір — 500–1000 записів.
  2. Відсутність індексів на унікальні поля (SKU, артикул) — upsert сповільнюється до повного сканування таблиці. Перевіряйте індекси перед запуском.
  3. Ігнорування deadlock'ів при конкурентному записі в одну таблицю — використовуйте блокування рядків або послідовну обробку в межах однієї партиції.
  4. Необроблені помилки парсингу — завжди передбачайте валідацію та пропуск некоректних рядків з логуванням.

Обсяг робіт та терміни

  • Розробка chunk-диспетчера та bulk upsert.
  • Попереднє завантаження довідників та фінальний Job.
  • Звіт про результати (кількість оброблених, помилки).
  • Документація з налаштування та запуску.
  • Навчання операторів роботі з системою.
  • Гарантія стабільної роботи — 3 місяці підтримки.
  • Робота сертифікованих Laravel-інженерів.

Терміни: від 3 до 5 днів під ключ. Отримайте консультацію щодо вашого проекту. Зв'яжіться з нами для оцінки вашого проекту.

Data import — Wikipedia

Наші компетенції:

Останні роботи

  • Розробка сайту компанії B2B ADVANCE
    Розробка сайту компанії B2B ADVANCE
    1542
  • Розробка веб-додатків для компанії FEEDME
    Розробка веб-додатків для компанії FEEDME
    1394
  • Розробка веб-сайту для компанії БЕЛФІНГРУП
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    1081
  • Розробка інтернет магазину для компанії FURNORO
    Розробка інтернет магазину для компанії FURNORO
    1369
  • Розробка веб-додатків для компанії Enviok
    Розробка веб-додатків для компанії Enviok
    1141
  • Розробка веб-сайту для компанії ФІКСПЕР
    Розробка веб-сайту для компанії ФІКСПЕР
    1060