Реализация отката (Rollback) импорта товаров с сохранением снимков

Импорт с ошибкой в маппинге может переписать цены у тысяч товаров, оставляя каталог в нерабочем состоянии. Мы разрабатываем механизм отката, который возвращает данные к исходному состоянию за минуты, а не часы. Наша команда реализует решение под ключ с сохранением снимков и транзакционным восстановлением, обеспечивая надёжность и последующую поддержку.

Разработка и обслуживание любых видов сайтов:

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

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Реализация отката (Rollback) импорта товаров с сохранением снимков
Средний
~3-5 дней
Часто задаваемые вопросы

Реализация отката (Rollback) импорта товаров с сохранением снимков

Импорт с ошибкой в маппинге может переписать цены у тысяч товаров неправильными данными. Представьте: загрузка CSV с перепутанными колонками цены и количества — после импорта 5000 товаров цены превратились в остатки. Без механизма отката единственный выход — восстановление из резервной копии базы данных, что занимает часы и поднимает весь сайт. Мы разрабатываем отказоустойчивый механизм отката, который возвращает каталог к состоянию «до» за минуты. Наше решение основано на снимках данных перед импортом и транзакционном восстановлении.

Мы реализовали rollback для каталогов объёмом до 500 000 товаров. Вместо полного восстановления из бэкапа (может занять 2-3 часа) откат через снимки выполняется за 2-5 минут на 10 000 строк. Это сокращение времени до 99% — экономия ресурсов без потери данных. Гарантируем целостность: откат выполняется в одной транзакции, либо не происходит вовсе.

Какие проблемы решает механизм отката?

Основные проблемы, которые возникают при импорте:

  • N+1 запрос при обновлении связанных сущностей — приводит к замедлению.
  • Частичный импорт при сбое — каталог остаётся в половинчатом состоянии.
  • Долгие блокировки таблиц при массовых операциях.

Наш подход устраняет эти риски: батчинг снижает нагрузку, транзакция гарантирует атомарность, а снимки позволяют откатить изменения без backup.

Как работает механизм отката?

Стратегия проста: перед импортом мы сохраняем снимок всех строк, которые будут изменены. Снимок — это JSON-копия полей товара (цена, количество, описание, категория). При ошибке запускается обратный процесс: вновь созданные товары удаляются, изменённые — восстанавливаются из снимка. Подробнее об этом — ниже.

Снимок перед импортом (Snapshot)

Перед импортом сохраняем снимок затронутых строк:

CREATE TABLE import_product_snapshots (
    id bigserial PRIMARY KEY,
    import_id int REFERENCES import_runs(id) ON DELETE CASCADE,
    product_id int,
    operation varchar(10), -- create | update (delete отдельно)
    data_before jsonb, -- состояние ДО импорта (NULL для create)
    created_at timestamptz DEFAULT now()
);

Сервис снятия снимка

class ImportSnapshotService {
    public function captureBeforeImport(int $importId, array $skus, int $sourceId): void {
        // Берём существующие данные товаров, которые будем изменять
        $products = Product::whereIn('sku', $skus)
            ->where('source_id', $sourceId)
            ->get(['id', 'sku', 'name', 'price', 'qty', 'description', 'category_id', 'deleted_at', 'updated_at']);

        $snapshots = $products->map(fn($p) => [
            'import_id' => $importId,
            'product_id' => $p->id,
            'operation' => 'update',
            'data_before' => json_encode($p->toArray()),
            'created_at' => now()->toDateTimeString(),
        ])->all();

        // Батч-вставка
        foreach (array_chunk($snapshots, 1000) as $chunk) {
            ImportProductSnapshot::insert($chunk);
        }
    }

    public function captureNewProduct(int $importId, int $productId): void {
        ImportProductSnapshot::create([
            'import_id' => $importId,
            'product_id' => $productId,
            'operation' => 'create',
            'data_before' => null,
        ]);
    }
}

Механизм отката

class ImportRollbackService {
    public function rollback(ImportRun $import): RollbackResult {
        if (!in_array($import->status, ['success', 'partial', 'failed'])) {
            throw new \RuntimeException('Import is not in a rollbackable state');
        }
        if ($import->rolled_back_at) {
            throw new \RuntimeException('Import already rolled back');
        }
        $restored = $deleted = 0;
        DB::transaction(function () use ($import, &$restored, &$deleted) {
            $snapshots = ImportProductSnapshot::where('import_id', $import->id)
                ->orderByDesc('id') // обратный порядок для зависимостей
                ->get();
            foreach ($snapshots as $snapshot) {
                if ($snapshot->operation === 'create') {
                    // Созданные товары — удаляем (soft)
                    Product::find($snapshot->product_id)?->delete();
                    $deleted++;
                } else {
                    // Обновлённые товары — восстанавливаем предыдущее состояние
                    $before = json_decode($snapshot->data_before, true);
                    Product::where('id', $snapshot->product_id)->update($before);
                    $restored++;
                }
            }
            $import->update([
                'rolled_back_at' => now(),
                'rolled_back_by' => auth()->id(),
                'rollback_result' => compact('restored', 'deleted'),
            ]);
        });
        return new RollbackResult($restored, $deleted);
    }
}

Всё выполняется в одной транзакции — либо откат полностью завершился, либо ничего не изменилось. Для больших импортов (более 50 000 строк) мы используем батчинг по 1000 записей, чтобы избежать долгих блокировок. Прогресс отката отслеживается в реальном времени через веб-интерфейс.

Пошаговая инструкция реализации отката

  1. Создайте таблицу снимков (см. SQL выше).
  2. Реализуйте сервис захвата ImportSnapshotService — вызывайте его перед импортом для каждого набора строк.
  3. Реализуйте сервис отката ImportRollbackService — добавьте вызов при ошибке или по кнопке.
  4. Проверьте условия применимости (снимок сохранён, не прошло 7 дней, импорт не отменён).
  5. Интегрируйте с административным интерфейсом: кнопка отката и прогресс-бар.

Условия применимости отката

Не каждый импорт можно откатить. Мы проверяем:

Условие Откат возможен?
Снимок сохранён полностью Да
Прошло менее 7 дней Да (политика хранения)
Импорт уже отменён Нет
Поверх этого импорта был новый импорт Частично (только незатронутые строки)
Физически удалённые товары (не soft delete) Нет
public function canRollback(ImportRun $import): bool
{
    return !$import->rolled_back_at && $import->created_at->isAfter(now()->subDays(7)) && ImportProductSnapshot::where('import_id', $import->id)->exists();
}

Почему важен инкрементальный откат?

При откате 100 000 строк в одной транзакции база блокируется на десятки секунд. Инкрементальный подход разбивает операцию на батчи, снижая нагрузку на сервер. Мы используем метод rollbackInBatches, который применяет снимки порциями и обновляет прогресс. Это позволяет выполнять откат без остановки работы магазина.

Каскадный откат связанных данных

Импорт затрагивает не только таблицу products. При откате мы автоматически удаляем связанные изображения, спецификации и фильтры. Для этого в методе applySnapshot выполняется каскадное очищение. Снимки хранятся 7 дней после успешного импорта, после чего удаляются планировщиком (artisan import:cleanup-snapshots --days=7).

Сравнение подходов к откату

Подход Скорость Надёжность Сложность
Snapshot Быстро (2-5 мин на 10 000) Высокая (транзакция) Средняя
Soft delete Мгновенно Низкая (остаются метки) Низкая
Event Sourcing Зависит от событий Очень высокая Высокая

Наш выбор — комбинация снимков и батчинга. Это оптимально для большинства интернет-магазинов.

Что входит в разработку

  • Сервис снимков (PHP/Laravel)
  • Механизм отката с батчингом
  • Административный интерфейс (кнопка «Откатить», прогресс-бар)
  • Проверка применимости и каскадное удаление
  • TTL-очистка снимков
  • Документация API и обучение команды

Сроки реализации

  • Базовая функциональность (снимок + откат в транзакции) — 2 дня
  • Батчинг, каскад, проверки — +1 день
  • Admin UI и тестирование — +1 день

Итоговый срок — от 4 до 6 дней в зависимости от сложности каталога.

Закажите консультацию — оценим ваш проект за один день. Мы гарантируем, что откат не потеряет ни одного товара. У нас за плечами 50+ успешных внедрений импорта для каталогов от 1 000 до 500 000 товаров. Свяжитесь с нами, чтобы обсудить детали.

Согласно документации PostgreSQL по транзакциям, snapshot isolation обеспечивает консистентность данных. Мы применяем этот принцип в своём решении.

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

Последние работы

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