Поставщик прислал файл на несколько десятков тысяч строк. Вы запускаете обновление, а через несколько минут замечаете: карточки товаров открываются медленнее обычного, корзина задумывается, поиск отвечает с паузой. Покупатель не знает про ваш прайс-лист и не обязан ждать, пока он «доедет». Задача не в том, чтобы импорт закончился как можно быстрее, а в том, чтобы он шёл так, чтобы магазин оставался магазином.

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

Сначала файл, потом товары: два разных этапа

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

Встроенный CSV-инструмент WooCommerce умеет импортировать, экспортировать и массово обновлять товары, но требует, чтобы файл соответствовал схеме столбцов и был в кодировке UTF-8 (документация WooCommerce). Это требование удобно использовать как границу между этапами: всё, что связано со схемой и кодировкой, проверяется до первого касания живого магазина. Если поставщик прислал файл в Windows-1251 или с «поехавшими» столбцами, лучше узнать об этом на подготовке, а не когда четверть каталога уже получила кракозябры в названиях.

Практически это значит, что у файла должна быть своя «приёмная». Исходный файл сохраняется как есть и не редактируется. Рядом создаётся рабочая копия, приведённая к нужной схеме. Дальше идёт сверка: сколько строк, сколько уникальных идентификаторов, сколько из них уже есть в магазине, сколько новых, сколько встретилось дважды. Эти цифры потом пригодятся в конце, когда нужно будет доказать, что поставка применена целиком.

Что искать на подготовке

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

Подготовка — это место, где VPS работает на вас без риска. Разбор большого файла, сортировка, подсчёт уникальных значений грузят процессор и диск, но не веб-сервер магазина, если вы делаете это отдельным процессом с пониженным приоритетом или вообще на другой машине. Когда у сайта есть собственный сервер с root-доступом, такой отдельный процесс запускается без согласований с хостером, а скорый NVMe-диск заметно ускоряет чтение и запись промежуточных файлов. Но ресурсы тарифа — это не обещание ёмкости: сколько строк в минуту выдержит именно ваш магазин, показывает только замер.

Фоновый режим: что реально есть, а что придётся построить

Здесь нужна честность. Слово «фон» легко произнести, но оно не означает, что любой инструмент умеет работать в фоне. Встроенный CSV-импортёр WooCommerce описан в документации как инструмент импорта, экспорта и массового обновления; о собственной внешней очереди там не сказано, и приписывать ему такую возможность нельзя. Платное расширение Product CSV Import Suite имеет отдельную инструкцию, и его поведение нужно читать по ней, а не переносить с встроенного инструмента по аналогии (та же страница документации).

Поэтому перед тем как строить процесс, ответьте себе на вопрос: чем именно вы импортируете? Возможны три положения дел.

  1. Встроенный импортёр. Подходит для разовых и умеренных обновлений. Если нужен фоновый режим, его придётся организовать самостоятельно: разрезать файл на части и подавать их по одной, не рассчитывая на готовую очередь.
  2. Расширение или сторонний модуль с собственной фоновой обработкой. Возможности, лимиты и поведение при сбое определяются его документацией. Проверяйте, как он хранит прогресс и что делает при остановке.
  3. Собственная очередь. Скрипт или небольшой сервис, который берёт порцию, применяет её к магазину и записывает результат. Это больше работы, зато вы точно знаете, что происходит на каждом шаге.

Дальше в статье предполагается именно третий вариант как образец мышления: даже если вы пользуетесь готовым модулем, те же вопросы к нему остаются теми же. Как устроена порция? Сколько их выполняется одновременно? Что считается завершённым? Что будет после остановки?

Единица работы: порция, а не файл

Файл — неудобная единица. Он слишком большой, чтобы его можно было повторить целиком без последствий, и слишком грубый, чтобы понять, где именно случился сбой. Единица фоновой обработки должна быть такой, чтобы её можно было выполнить быстро, проверить отдельно и при необходимости повторить без вреда. Обычно это порция из заданного числа строк.

Как выбрать размер? Универсального числа нет, и называть его заранее было бы выдумкой. Но есть способ найти своё. Возьмите условный пример: порция в 50 строк занимает, скажем, 4 секунды, а порция в 500 строк — 60 секунд. Нелинейность — признак того, что с ростом порции что-то начинает копиться: память, блокировки таблиц, очередь запросов к базе. Это числа для иллюстрации, а не результаты испытаний: ваши будут другими. Смысл в методе: прогоните несколько пробных порций разного размера на копии магазина и посмотрите не только на время порции, но и на то, как в этот момент отвечает витрина.

Критерии выбора порции такие:

  • Время одной порции. Чем оно короче, тем меньше потеряно при обрыве и тем проще пауза между порциями. Порция, которая идёт минуту, — это минута повышенной нагрузки без передышки.
  • Размер самой тяжёлой строки. Строка с десятком вариаций и галереей изображений стоит дороже строки с одной ценой. Если файл неоднороден, порции можно формировать не по числу строк, а по «весу»: например, считать тяжёлую строку за несколько лёгких.
  • Цена повтора. Если порция безопасна для повтора, размер можно выбирать смелее. Если нет, лучше мельче.

Каждой порции стоит присвоить номер и хранить её состояние: ожидает, выполняется, применена, завершилась ошибкой. Это самый простой журнал, но именно он превращает «импорт идёт» в проверяемую картину. Позже он понадобится для сверки.

Сколько порций одновременно: ограничение по отклику магазина

Соблазн понятен: у сервера несколько ядер, значит, можно запустить несколько порций параллельно и закончить вчетверо быстрее. Но у магазина общий узкий участок — база данных. Параллельные порции пишут в одни и те же таблицы, и после какого-то числа процессов выигрыша нет, а витрина начинает страдать. Число ядер здесь плохая подсказка. Для сравнения: тариф с большим числом vCPU даёт больше возможностей, но не отменяет конкуренцию за диск и базу внутри одного приложения.

Разумнее ограничивать параллелизм не числом ядер, а тем, что вы действительно хотите защитить, — откликом витрины. Идея простая: рядом с импортом работает «дозорный», который периодически запрашивает одну-две типовые страницы магазина, например карточку товара и категорию, и измеряет время ответа. Дальше правила такие:

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

Пороги — ваши. Чтобы их выбрать, сначала измерьте обычный отклик в спокойное время и в часы пик, и только потом решайте, на сколько он может вырасти ради обновления прайса. Это решение скорее коммерческое, чем техническое: сколько лишних долей секунды вы готовы подарить покупателю ради того, чтобы остатки обновились сегодня, а не завтра.

Есть и вторая сторона. Когда импорт идёт ночью, а трафик минимален, порог можно сделать мягче. Когда обновление нужно срочно в разгар дня, он должен быть жёстче, а импорт — медленнее. Скорость обработки — не свойство сервера, а договорённость между импортом и витриной.

Отдельный процесс и приоритет

Если импорт запускается как отдельный процесс на том же VPS, его можно запускать с пониженным приоритетом процессора и дисковых операций, чтобы веб-сервер при нехватке ресурсов получал преимущество. Это не заменяет ограничение по отклику: приоритет решает спор за процессор, но не спор за блокировки в базе. Два механизма дополняют друг друга.

Повтор порции: почему нужен стабильный идентификатор

Любая длинная операция рано или поздно прерывается: перезапуск службы, нехватка памяти, сетевой обрыв при загрузке изображения, ручная остановка. Вопрос не «как избежать обрыва», а «что произойдёт, если порцию придётся выполнить второй раз». Если повтор создаёт дубли товаров или удваивает остатки, процесс хрупкий. Если повтор приводит к тому же состоянию, что и первое выполнение, — устойчивый.

Ключ к устойчивости — стабильный идентификатор товара. Это значение, которое не меняется между поставками и однозначно указывает на товар: обычно артикул поставщика, внутренний код или ID из учётной системы. Важна именно стабильность. Название товара для этого не годится, потому что его правят. Порядковый номер строки в файле тоже не годится, потому что в следующем файле та же строка окажется на другом месте.

Логика обновления по идентификатору проста: найти товар с этим идентификатором; если есть — обновить нужные поля, если нет — создать. Тогда повтор порции просто ещё раз записывает те же значения. Но ключевое слово — «значения». Операция «установить остаток равным 17» безопасна для повтора. Операция «уменьшить остаток на 3» — нет: выполненная дважды, она вычтет шесть. Поэтому в файле поставщика должны быть абсолютные значения, а не изменения, а ваша обработка не должна превращать их в приращения.

Здесь легко обмануться: свежий номер попытки или новая метка времени не делают операцию безопасной для повтора сами по себе. Безопасность даёт то, что операция записывает итоговое состояние по неизменному ключу. Попытку можно перезапускать сколько угодно, если результат определяется содержимым строки, а не историей выполнения.

Встроенный импортёр при массовом обновлении сопоставляет строки с существующими товарами; какие именно столбцы он использует для сопоставления и как ведёт себя при повторе, нужно проверить по его документации и на тестовой копии, прежде чем строить на этом процесс. А вот в собственной очереди вы управляете этим сами: порция хранит список идентификаторов, и перед записью каждая строка находится по ним.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации Россия, США, Франция и Великобритания. Оплата картой РФ и по СБП.

АРЕНДОВАТЬ СЕРВЕР

Цены, остатки и изображения живут с разной скоростью

Файл поставщика выглядит как единое целое, но его столбцы очень разные по стоимости обработки и по срочности. Обновить цену и остаток — это запись пары значений. Загрузить изображение — скачать файл с внешнего адреса, сохранить, создать вложения нужных размеров и привязать к товару. Первое занимает миллисекунды, второе — иногда секунды, и зависит от чужого сервера. Если гнать всё это одной порцией, медленный элемент определит темп для всех остальных.

Поэтому полезно разделить поставку на несколько проходов с разным приоритетом:

Три прохода по одной поставке
ПроходЧто обновляетсяПочему отдельно
ПервыйОстатки и наличиеСамые срочные данные: неверный остаток приводит к заказам на то, чего нет. Запись лёгкая, порции можно делать крупнее.
ВторойЦены и акционные значенияВлияют на деньги, поэтому важно применить согласованно; запись лёгкая, но проверять нужно тщательнее.
ТретийОписания, атрибуты, изображенияСамые тяжёлые и наименее срочные. Порции мельче, пауза длиннее, повторы допустимы.

Разделение даёт сразу несколько преимуществ. Остатки обновляются первыми и быстро, а значит, окно, когда витрина показывает устаревшее наличие, сокращается. Тяжёлая работа с изображениями не задерживает цены. А если внешний сервер картинок недоступен, это не блокирует всю поставку: проход с изображениями просто отстаёт и догоняется позже.

Цена и остаток: осторожность с половинчатым состоянием

Есть тонкость. Если цены обновляются по порциям, на какое-то время часть каталога будет показывать новые цены, а часть — старые. Для большинства магазинов это допустимо, но есть случаи, когда нет: например, комплекты, где цена набора зависит от цен компонентов, или акция, которая должна начаться для всех товаров одновременно. В таком случае стоит подумать, нужно ли обновить значения «втёмную» и переключить их разом. Один из способов — сначала записать новые цены в отдельное поле или временное хранилище, а затем применить их быстрым проходом. Это усложняет процесс, и решать, оправдано ли это, нужно по характеру ассортимента, а не из принципа.

Изображения: медленные и ненадёжные по природе

Изображения зависят от внешних адресов, которые вы не контролируете: сервер поставщика может отвечать медленно, отдавать ошибку или блокировать частые запросы. Поэтому для картинок разумны отдельные ограничения: меньше параллельных загрузок, таймаут на каждый файл, учёт неудачных попыток и повтор позже, а не немедленно. Товар без новой картинки — меньшая проблема, чем замороженный импорт. Пусть у такого товара остаётся прежнее изображение или заглушка, а в журнале появляется пометка «изображение не получено».

Что видит покупатель, пока файл обработан наполовину

Половина файла применена, половина ждёт. В этот момент магазин находится в смешанном состоянии, и важно решить заранее, как это состояние выглядит снаружи и что с ним делать.

Первый вопрос — незавершённые изменения. Если новый товар создаётся, а его изображения, атрибуты и категории дозаписываются позже, у покупателя может оказаться карточка без картинки или без нужной категории. Чтобы этого избежать, новые товары можно сначала создавать в состоянии черновика, а публиковать только после того, как обязательные поля заполнены. Для существующих товаров та же логика работает иначе: у них уже есть опубликованная версия, и неполное обновление ухудшает её, а не создаёт пустое место. Поэтому порядок проходов — остатки, затем цены, затем остальное — это ещё и порядок безопасности: самые критичные поля обновляются первыми и сразу.

Второй вопрос — как отличить товар, который уже получил новые данные, от того, который ещё нет. Для этого нужна метка поставки. Например, в служебное поле товара записывается идентификатор поставки или её дата, и по ней видно, к какой версии прайса относятся значения. Тогда выборка товаров без метки текущей поставки — это точный список того, что осталось. Без такой метки приходится угадывать по косвенным признакам вроде даты изменения, а она меняется и от ручных правок менеджера.

Третий вопрос — кэш. Если на сайте включено кэширование страниц, обновлённая цена может не появиться сразу, а частично очищенный кэш создаст ещё более причудливую картину. Решение зависит от используемого кэша, но принцип общий: очистка должна быть привязана к применённым порциям, а не происходить на каждую запись, иначе она сама станет нагрузкой.

После остановки: с чего продолжить

Допустим, процесс остановился на порции 62 из 140. Что делать? Самый плохой ответ — запустить файл заново с начала «на всякий случай». Он работает, если повтор безопасен, но тратит время и снова нагружает магазин. Лучший ответ опирается на журнал порций.

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

Практический порядок действий после остановки такой:

  1. Прекратить запуск новых порций и убедиться, что ни один процесс импорта не остался работать.
  2. Посмотреть журнал: сколько порций применено, сколько ожидает, где ошибки.
  3. Выяснить причину остановки, пока она не повторилась: нехватка памяти, таймаут, перезапуск службы, недоступный внешний адрес.
  4. Перевести порции «выполняется» обратно в «ожидает».
  5. Запустить продолжение с прежними или более осторожными ограничениями.

Обратите внимание, что в этом списке нет совета «увеличить время выполнения скрипта». Таймаут — это предохранитель, а не причина проблемы. Если порция не укладывается в лимит, её нужно сделать меньше. Растянутый лимит лишь позволит одной тяжёлой порции дольше занимать базу и дольше мешать покупателям.

Конфигурация VPS под такой процесс

Отдельный сервер позволяет вынести ночную подготовку и фоновую обработку из общего окружения и настроить их под себя: root-доступ даёт возможность запускать собственные планировщики и воркеры, а NVMe-накопитель сокращает время на работу с большими файлами и базой. В публичных предложениях MAATRIX VPS — NVMe, процессоры AMD EPYC и root-доступ, доступны локации UK, US, RU и FR, оплата возможна картой РФ и через СБП, а сервер создаётся автоматически в течение пары минут после оплаты. Подробности и актуальное наличие образов проверяйте в форме заказа на странице аренды VPS.

Как соотнести тарифы с задачей? Линейка начинается с Ba (1 vCPU, 2 ГБ RAM, 40 ГБ NVMe) и идёт через Uab (2 vCPU, 4 ГБ, 80 ГБ), Heka (4 vCPU, 8 ГБ, 160 ГБ), Maat (8 vCPU, 16 ГБ, 240 ГБ) до Djed (16 vCPU, 32 ГБ, 320 ГБ). Это набор ресурсов, а не обещание ёмкости: сколько товаров и покупателей выдержит конкретная конфигурация, определяется замером на вашем каталоге. Мысленно разделите потребности. Память нужна и базе, и PHP-процессам, и обработчику файла; диск — под саму базу, медиатеку с изображениями и временные копии файлов поставок, которые быстро накапливаются; ядра — под параллельные процессы, но, как уже сказано, параллелизм ограничен базой, а не их числом.

Если магазин только настраивается и вы ещё выбираете конфигурацию, пригодится материал о установке и настройке WooCommerce: от базовых решений там зависит, насколько комфортно потом принимать большие поставки.

Поставка считается принятой, когда сошлись числа

Вернёмся к началу. Файл поставщика пришёл, часть работы сделана, очередь постепенно пустеет. Соблазн сказать «готово», когда в очереди ничего не осталось, велик, но пустая очередь говорит только о том, что процесс закончил работу, а не о том, что он сделал её правильно. Порция могла пройти с ошибкой, которую никто не заметил; строка могла быть отброшена как некорректная; изображение могло не загрузиться.

Проверяемый результат конкретной поставки — это набор сверяемых чисел и выборок. Вот как он может выглядеть на условном примере: в файле 12 400 строк, из них 12 380 уникальных идентификаторов (20 дублей отмечены на подготовке). После обработки в магазине должно быть именно то, что соответствует этим цифрам.

  • Количество. Число товаров с меткой текущей поставки равно числу уникальных идентификаторов за вычетом осознанно отклонённых строк. Расхождение — повод искать пропущенные порции.
  • Выборочное сравнение значений. Из файла берётся случайная выборка в несколько десятков строк, и для каждой сравниваются цена и остаток в магазине со значениями из файла. Для крупных расхождений проверяется вся группа.
  • Контрольные товары. Заранее выбираются несколько товаров разного вида: простой, вариативный, с акцией, без остатка, с многими изображениями. Их проверяют вручную на витрине.
  • Отклонённые строки. Список строк с ошибками сохранён с причинами и передан тому, кто может их исправить, а не потерян в журнале.
  • Изображения. Отдельно подсчитано, у скольких товаров картинка не получена, и для них назначен повторный проход.

Такая приёмка отвечает на вопрос, который на самом деле волнует владельца магазина: не «закончился ли процесс», а «соответствует ли магазин присланному прайсу и что именно не соответствует». Если есть список исключений, поставка принята с известными оговорками. Если списка нет, а очередь пуста, вы не знаете, принята она или просто остановилась.

В итоге хорошая картина выглядит так: файл проверен и разрезан заранее, порции небольшие и повторяемые, параллелизм подчиняется отклику витрины, остатки приходят раньше картинок, а у каждого товара есть отметка, к какой поставке он относится. Покупатель в это время листает каталог и ничего не замечает. А вы можете открыть журнал и сказать, какая часть прайса уже на витрине, какая ждёт своей очереди и какие семнадцать строк придётся уточнить у поставщика.