Добрый день!
Помогите пожалуйста разобраться с оптимизацией "долгого" скрипта. В PHP, WP я не силен.
В рамках профессии "тыжпрограммист", набросал простенький плагин, который загружает xml-файл товаров и категорий формата Яндекс.Магазин (yandex_catalog). Делает парсинг xml, записывает данные во временные таблицы, затем "генерирует" категории товаров и сами товары в формат, понятный WP с помощью wp_insert_post, wp_insert_term и пр..
Для товаров и категорий используется portfolio custom taxonomy (cherry framework). Товары нужны не как товары интернет-магазина, а как каталог продукции.
Проблема возникла в производительности скрипта, дело в том что кол-во позиций товаров - over 1.2k, плюс ко всему прочему необходимо подгружать изображения этих товаров напрямую с другого сайта, сохраняя их в медиа-библиотеку, затем прикрепляя к посту. Скрипт долго висит, в это время нормально происходит импорт, и через 30 дефолтных секунд отваливается по таймауту: Fatal error: Maximum execution time of 30 seconds exceeded...
Из-за нехватки знаний и опыта, ничего другого не придумал кроме set_time_limit(1000). За это время все импортируется, но это ведь совсем не хорошо, да?
Подскажите как можно обойти таймаут на долгое выполнение? Распараллелить, как вариант, но без особых заморочек, я не знаток PHP.
Рассматривал порционирование "генерации" товаров с помощью задания cron через wp_schedule_event, но у него регламент по времени - час минимум, что для 1.2к товаров не хорошо.
Плагины импорта рассматривал, - не подошли.
wp_schedule_event не настоящий cron: "The action will trigger when someone visits your WordPress site"("Действие запускается когда кто-то посещает ваш сайт")
к wp_schedule_event можно создавать свои интервалы.
я бы сделал наверное так:
1. получил бы файл, сохранил его, записал в базу путь и данные с какого элемента и кол-во элементов нужно спарсить, запустить wp_schedule_event к примеру через 5 минут.
2. по wp_schedule_event через 5 минут распарсил кол-во заданных элементов, добавил записи, обновил запись в базе - стартовую позицию, wp_schedule_event через 5 мин. и т.д.
но, тут нужен настоящий крон я так думаю, с псевдо кроном ВП 100% гарантии запуска события нет.
@rOOse, спасибо, я так и собрался делать.
Завтра попробую. Надеюсь, пользователь, который будет дожидаться, пока распарсится загруженный файл, сгодится за визитера сайта для псевдокрона.
Консоли ПХП нет, да и заказчику необходимо, чтобы функционал был доступен из админки, поскольку пользоваться им будут офисные фефочки, посему я упростил плагин до пары кнопок на страницее.
очень осторожно нужно относиться к запуску пхп скриптов из консоли... особенно к способу их запуска и юзеру от которого они запускаются... (это лично мое мнение)
@VoVanJinn, можно использовать очередь задач. Из админки вы добавляете в очередь задачу, запускается в фоне консольный скрипт и делает ее... Можно периодически уведомлять о прогрессе выполнения задачи.
@VoVanJinn, имею ввиду в админке жмете "сделать", а оно на стороне сервера его не делать будет, а добавит в очередь. А консольный скрипт будет все это делать, смотреть в очередь и забирать новые задачи.
@VoVanJinn, очередь задач это очередь задач. Как она реализуется в контексте WP я без малейшего понятия. Я обычно в таких случая использую штуки типа RabbitMQ/ActiveMQ, но думаю это не про данный случай.
самый простой, возможно не самый красивый способ - писать куда то прогресс выполнения задачи.
1. забрать и распарсить xml - это быстро, после этого этапа скрипт уже знает сколько позиций ему предстоит обработать. (так ведь?)
2. обрабатываем в цикле позиции, после обработки каждой позиции пишем ее ID в таблицу (файл).
3. скрипт перезапускаем до тех пор пока циферка из шага 2 не сравняется с количеством позиций из принятого xml (это количество можно так же посчитать один раз на шаге 1 и больше не вычислять.)
Ок, звучит отлично!
Тогда нубский вопрос:
Мой плагин, к моему стыду, организован как один длинный скрипт, он же является отображалкой кнопки загрузки файла и кнопки начала импорта в админке WP. В скрипт входят этапы: загрузка, парсинг, генерация постов и скачивание картинок. Все это организовано в виде отдельных функций, которые вызываются последовательно. Вопрос такой: как из этого скрипта правильно вычленить части функционала, чтобы их можно было запускать/перезапускать отдельно?
все зависит от вас. :) можно оставить вообще все в одном скрипте, и просто условиями реализовать алгоритм, при котором пока нужные части не отработают, ненужные не запускались вообще.
Я бы, все таки отделил "скрытую работу" такую как краулинг, парсинг от работы по отображению.
если у вас все реализовано отдельными функциями, наверняка можно либо разнести функции по отдельным файлам, либо, как я выше написал - оставить все в одном, но изменить логику вызова этих функций.
и еще момент, если уж ставите set_time_limit(), тогда уж и ставьте ignore_user_abort(); но имейте в виду, что если скрипт начнет делать "что то не то", остановить его вы сможете только рестартом апача (если пхп работает как его модуль) ну или прибив процесс в шелле
Ок, спасибо! Мне как программисту со стажем не сложно придумать алгоритм для такой задачи, сложность в другом - в незнании архитектуры WP и PHP )
К примеру: как можно организовать вызов какой-то функции, которая будет, скажем, парсить порционально, разумеется с учетом того, чтобы порция не превышала лимит времени обработки в 30 секунд? При этом вызов этой функции должен быть автоматическим, без вмешательства пользователя. При этом сделать это отдельно от отображения.
а как бы вы это реализовали на языке, на котором у вас стаж? думаю, на пхп это будет абсолютно так же.
Вариантов запустить самого себя по событию тоже немало (в пхп даже есть такая неоднозначная вещь как register_shutdown_function(); )
но по хорошему, если делаете всерьез и на долго, и еще есть время, разделите отображение от получения.
если я правильно поимаю логику.
1. получили xml, распарсили его
2. в цикле тащим информацию о товаре и преобразуем его в вид (к сожалению, не известного мне плагина WP) все это делаем с сохранением состояния
3. картинки я бы тащил отдельным краулером, (там же насколько я понимаю, обработки никакой нет? тупо получили на вход пачку ссылок, стащили по ссылкам пачку картинок и записали их в ФС а ссылки на них в БД.
@begemot_nn
2. вот и получается, что мой цикл работает в теле моего большого скрипта. И у меня сейчас складывается такое представление, даже если я вызову обработку процесса, которая реализована в другом файле php, то вызов был из моего первичного скрипта и он будет отрабатывать пока не завершится процесс в другом файле php.
Меня тут больше интересует механизмы вызова.
Вот register_shutdown_function, который вы упомянули - это что-то похожее на то что я имел ввиду, спасибо. Но ваше предостережение "неоднозначная вещь" я вменяю с опасностью :)
@begemot_nn
Почитал, прикрутил внутри этого же скрипта, но не помогло. Такое ощущение что вызов завершающей функции прошел вместе с вызовом скрипта и попал под тот же таймаут 30 секунд.
Скрипт успел обработать 18 записей :)
Вот думаю после загрузки файла и парсинга - создать wp_schedule_event-камикадзе. В смысле, чтобы он сам себя убил после выполнения. Так будет нормально или это костыль?