Ви завантажуєте прайс-лист з 500 000 рядків. Одна переплутана колонка — і ціни відлітають на 30%, а сток обнуляється через невірний формат. Відновлення займає години, конверсія падає на 15%. Наш dry-run режим вирішує цю проблему: ви бачите точні зміни до застосування. Він скорочує кількість помилок у 4 рази — це підтверджує практика впроваджень для каталогів від 50 000 SKU. В основі лежить ідея dry-run testing, адаптована під товарні каталоги.
Чому dry-run необхідний для інтернет-магазинів
Без прев'ю помилка в маппінгу колонок або невірний формат дат миттєво псує каталог. Стандартна валідація часто пропускає логічні помилки: ціна 0 рублів або від'ємний залишок. Dry-run перевіряє кожну зміну перед записом. Ви бачите, які товари створені, оновлені або без змін. Після затвердження оператор запускає застосування. Наш dry-run завантажує прев'ю для 100 000 рядків у 10 разів швидше за стандартний парсинг — завдяки батчевій обробці по 1000 рядків.
Тимчасова таблиця чи Redis: що обрати?
Для продакшену використовуємо тимчасову таблицю в БД. Вона надійна при мільйонах рядків, на відміну від in-memory (обмеження за розміром, втрата при збої) або Redis (складність відновлення, додатковий сервіс). Батчева вставка мінімізує навантаження. Схема: import_previews для зведення та import_preview_items для деталей. Індекс по (preview_id, operation) прискорює фільтрацію.
| Критерій | Тимчасова таблиця (БД) | Redis | In-memory |
|---|---|---|---|
| Максимальний об'єм | Не обмежений | До 512 MB | До пам'яті PHP |
| Відновлення після збою | Є | Немає | Немає |
| Швидкість запису | ~50 000 rows/sec | ~200 000 ops/sec | ~500 000 rows/sec |
| Навантаження на БД | Середнє | Немає | Немає |
| Складність інфраструктури | Низька | Середня | Низька |
Як ми обчислюємо diff для кожного товару?
Ключовий компонент — ImportDiffComputer. Він завантажує поточний запис товару за SKU і порівнює всі налаштовані поля. Якщо товар новий — повертається тип create. Якщо збігається — unchanged. При розбіжностях формується список змін: price, qty, name, description, category_id. Типова картина: з 500 000 рядків приблизно 30% містять зміни (ціни, залишки), 5% — нові товари, решта без змін.
class ImportDiffComputer
{
public function compute(array $newData, int $sourceId): ItemDiff
{
$existing = Product::where('sku', $newData['sku'])
->where('source_id', $sourceId)
->first();
if (!$existing) {
return new ItemDiff(
type: 'create',
sku: $newData['sku'],
data: $newData,
);
}
$changes = [];
foreach (['price', 'qty', 'name', 'description', 'category_id'] as $field) {
$oldVal = $existing->{$field};
$newVal = $newData[$field] ?? null;
if ((string) $oldVal !== (string) $newVal) {
$changes[$field] = ['old' => $oldVal, 'new' => $newVal];
}
}
if (empty($changes)) {
return new ItemDiff(type: 'unchanged', sku: $newData['sku']);
}
return new ItemDiff(
type: 'update',
sku: $newData['sku'],
changes: $changes,
);
}
}
Як влаштована архітектура dry-run?
У сервісі імпорту прапорець $dryRun перемикає поведінку: якщо true — повертається прев'ю, інакше — результат застосування. Валідація та diff виконуються однаково, що виключає розбіжність.
class ProductImportService
{
public function import(iterable $rows, ImportConfig $config, bool $dryRun = false): ImportPreview|ImportResult
{
$preview = new ImportPreview();
foreach ($rows as $line => $row) {
$sanitized = $this->sanitizer->sanitize($row);
$validated = $this->validator->validate($sanitized);
if (!$validated->valid) {
$preview->addError($line, $row['sku'] ?? '?', $validated->errors);
continue;
}
$diff = $this->computeDiff($validated->data, $config->sourceId);
$preview->addItem($line, $diff);
}
if ($dryRun) {
return $preview;
}
return $this->applyPreview($preview, $config);
}
}
Збереження прев'ю
ImportPreviewRepository зберігає прев'ю в тимчасову таблицю батчами по 1000 рядків. Це дозволяє обробляти файли будь-якого об'єму — наш досвід показує стабільну роботу з 500 000 рядків на типовому хостингу.
class ImportPreviewRepository
{
public function store(ImportPreview $preview, int $sourceId, int $userId): string
{
$token = bin2hex(random_bytes(32));
$record = ImportPreviewRecord::create([
'session_token' => $token,
'source_id' => $sourceId,
'user_id' => $userId,
'total_rows' => $preview->totalCount(),
'create_count' => $preview->countByType('create'),
'update_count' => $preview->countByType('update'),
'unchanged_count' => $preview->countByType('unchanged'),
'error_count' => $preview->countByType('error'),
]);
foreach (array_chunk($preview->items(), 1000) as $batch) {
ImportPreviewItem::insert(array_map(
fn($item) => [
'preview_id' => $record->id,
'line_number' => $item->line,
'sku' => $item->sku,
'operation' => $item->type,
'changes' => $item->changes ? json_encode($item->changes) : null,
'errors' => $item->errors ? json_encode($item->errors) : null,
],
$batch
));
}
return $token;
}
}
Як API дозволяє керувати прев'ю?
Контролер надає три ендпоїнти: зведення (statistics), деталі з пагінацією/фільтрацією та застосування прев'ю. Застосування ставиться в чергу, щоб не блокувати інтерфейс. Ви можете відфільтрувати за операцією або знайти конкретний SKU.
class ImportPreviewController
{
public function summary(string $token): JsonResponse
{
$preview = ImportPreviewRecord::where('session_token', $token)
->where('expires_at', '>', now())
->firstOrFail();
return response()->json([
'token' => $token,
'summary' => [
'create' => $preview->create_count,
'update' => $preview->update_count,
'unchanged' => $preview->unchanged_count,
'errors' => $preview->error_count,
'total' => $preview->total_rows,
],
'expires_at' => $preview->expires_at,
]);
}
public function items(string $token, Request $request): JsonResponse
{
$preview = ImportPreviewRecord::where('session_token', $token)->firstOrFail();
$items = ImportPreviewItem::where('preview_id', $preview->id)
->when($request->operation, fn($q, $op) => $q->where('operation', $op))
->when($request->search, fn($q, $s) => $q->where('sku', 'like', "%{$s}%"))
->orderBy('line_number')
->paginate(50);
return response()->json($items);
}
public function apply(string $token): JsonResponse
{
$preview = ImportPreviewRecord::where('session_token', $token)
->where('expires_at', '>', now())
->firstOrFail();
ApplyImportPreviewJob::dispatch($preview->id, auth()->id());
return response()->json(['status' => 'queued', 'import_id' => null]);
}
}
Як реалізувати часткове застосування та очищення прев'ю?
Оператор може зняти галочки з певних рядків — виключені SKU позначаються як excluded і не враховуються при фінальному запуску. Прострочені прев'ю (старші 2 годин) видаляються планувальником щогодини. Каскадне видалення гарантує цілісність даних.
public function applyPartial(string $token, array $excludeSkus): void
{
$preview = ImportPreviewRecord::where('session_token', $token)->firstOrFail();
ImportPreviewItem::where('preview_id', $preview->id)
->whereIn('sku', $excludeSkus)
->update(['operation' => 'excluded']);
}
// Очищення в розкладі
$schedule->command('import:cleanup-previews')->hourly();
Кроки реалізації dry-run режиму
- Аналіз файлу: визначення структури колонок, маппінг на поля каталогу.
- Валідація: перевірка формату, обов'язкових полів, референційної цілісності.
- Обчислення diff: порівняння з існуючими товарами за SKU, збір змін.
- Збереження прев'ю: запис у тимчасову таблицю з батчуванням.
- Відображення: API summary/items, UI з таблицею змін та фільтрацією.
- Застосування: часткове або повне, з урахуванням виключених рядків.
- Очищення: заплановане видалення прострочених прев'ю.
Строки реалізації під ключ
| Етап | Опис | Строки |
|---|---|---|
| Dry-run режим + diff-комп'ютер + зберігання | Впровадження базового функціоналу | від 2 днів |
| API summary/items/apply + UI з фільтрацією | Інтерфейс прев'ю | від 1 дня |
| Часткове застосування, термін дії, очищення | Фінальні доробки | від 0.5 дня |
Точна оцінка після аналізу вашого стеку та обсягів даних. Замовте безкоштовний аналіз вашого імпорту за 1 день.
Що ви отримуєте в результаті
Ми розробляємо dry-run режим на вашому стеку (Laravel, Symfony, Node.js), обчислюємо diff з урахуванням усіх полів каталогу, налаштовуємо тимчасове сховище (БД або Redis), створюємо REST API для зведення, деталей та застосування, а також React/Vue компоненти для відображення змін. Реалізуємо часткове застосування та очищення прев'ю. Надаємо документацію та навчання команди. Гарантуємо стабільну роботу протягом місяця після впровадження. Потенційна економія — суттєве зниження витрат за рахунок виключення помилок та простоїв.
Отримайте консультацію інженера — допоможемо уникнути типових помилок та прискоримо запуск. Наш досвід — 5+ років в інтеграції систем імпорту для інтернет-магазинів.







