Знакомая ситуация? Вам нужно обновить произвольное поле у 50 000 товаров или переработать метаданные пользователей на огромном membership-сайте. Вы пишете PHP-скрипт, вставляете его в шаблон или хук и нажимаете «Обновить».
Через тридцать секунд вы видите ошибку 504 Gateway Timeout.
Браузер — ужасное место для миграции данных. Таймауты Nginx/Apache и лимиты выполнения PHP-FPM ясно показывают, что веб-серверы предназначены для передачи страниц, а не для обработки огромных массивов данных. В такой ситуации необходимо отказаться от браузера и обратиться к командной строке.
- Почему ваши скрипты аварийно завершают работу?
- Решение: PHP-генераторы
- Настраиваем WP_CLI::add_command
- Создаем цикл пакетной обработки
- Управление объектным кэшем
- Защитная обратная связь и транзакции
- Устойчивость к сбоям: транзакции и логирование
- Рабочий процесс: безопасность прежде всего с WP Migrate
- Кнопка спасения
Почему ваши скрипты аварийно завершают работу?
Проблема сводится к таймаутам и доступной памяти. При использовании функции get_posts() с параметром ‘posts_per_page’ => -1, WordPress пытается загрузить в оперативную память все соответствующие объекты одновременно. На крупном сайте это прямой путь к ошибке «Memory Exhausted».
Даже если вы обрабатываете посты пакетами, внутренний кэш объектов WordPress увеличивается с каждой обрабатываемой записью. Если вы его не очистите, использование памяти будет неуклонно расти, пока процесс не завершится.
Решение: PHP-генераторы
Вместо загрузки огромного массива в память мы используем генераторы PHP. Генератор позволяет итерационно проходиться по данным без предварительного создания огромного массива в оперативной памяти. Используя ключевое слово yield, функция предоставляет по одному элементу за раз и «приостанавливает» свое выполнение до тех пор, пока не будет запрошен следующий элемент. В сочетании с ручной очисткой кэша это гарантирует, что для обработки 1 000 000 сообщений потребуется такое же количество оперативной памяти, как и для обработки 10.
Настраиваем WP_CLI::add_command
WP-CLI — это стандарт для взаимодействия с командной строкой WordPress. Используя WP_CLI::add_command(), мы можем зарегистрировать произвольный инструмент, который функционирует в обход веб-сервера.
Создаем цикл пакетной обработки
Для повышения эффективности мы создаём генератор, который извлекает идентификаторы из базы данных. Затем мы проходим в цикле по этому генератору, обрабатывая каждый элемент по отдельности. Очищая внутреннюю память для каждого отдельного поста и выполняя регулярный сброс глобального кэша, мы поддерживаем абсолютно стабильное использование оперативной памяти сервера от начала до конца.
if ( defined( 'WP_CLI' ) && WP_CLI ) {
WP_CLI::add_command( 'db-migrate', 'DB_Migration_Command' );
}
class DB_Migration_Command {
/**
* Updates product metadata in bulk.
*
* ## OPTIONS
*
* [--batch-size=<number>]
* : How many posts to process before performing a global cache flush.
* ---
* default: 500
* ---
*
* [--dry-run]
* : Run the script without saving changes to the database.
*/
public function products( $args, $assoc_args ) {
global $wpdb;
$dry_run = isset( $assoc_args['dry-run'] );
// Ensure batch size is at least 1 to avoid division by zero errors
$batch_size = max( 1, (int) $assoc_args['batch-size'] );
// 1. Get total count for the progress bar feedback
$total_count = $wpdb->get_var( "SELECT COUNT(ID) FROM {$wpdb->posts} WHERE post_type = 'product' AND post_status = 'publish'" );
$progress = \WP_CLI\Utils\make_progress_bar( 'Updating Products', $total_count );
// 2. The Safety Net: Start a SQL Transaction
$wpdb->query( 'START TRANSACTION' );
try {
foreach ( $this->get_all_product_ids() as $index => $post_id ) {
// Validate data before processing to prevent partial corruption
if ( ! $this->validate_product( $post_id ) ) {
WP_CLI::warning( "Skipping Post ID: $post_id - Invalid status." );
continue;
}
if ( ! $dry_run ) {
// Core function ensures hooks fire for search/caching plugins
update_post_meta( $post_id, '_new_meta_key', 'updated_value' );
}
$progress->tick();
// 3. Memory Management: The "Scalpel"
// Clear the internal cache for the post we just finished to keep RAM low
clean_post_cache( $post_id );
// 4. Memory Management: The "Sledgehammer"
// Periodically clear the global object cache for massive datasets
if ( 0 === ( $index + 1 ) % $batch_size ) {
wp_cache_flush();
}
}
$progress->finish();
// 5. Finalize or Rollback
if ( ! $dry_run ) {
$wpdb->query( 'COMMIT' );
WP_CLI::success( 'Migration complete and saved to database!' );
} else {
$wpdb->query( 'ROLLBACK' );
WP_CLI::log( 'Dry run complete. No changes were saved.' );
}
} catch ( Exception $e ) {
// Undo everything if a critical error occurs
$wpdb->query( 'ROLLBACK' );
WP_CLI::error( 'Critical Error: ' . $e->getMessage() );
}
}
/**
* A Generator that yields post IDs one by one to save memory.
* Replaces get_posts() with -1 which can cause memory exhaustion.
*/
private function get_all_product_ids() {
global $wpdb;
$query = "SELECT ID FROM {$wpdb->posts} WHERE post_type = 'product' AND post_status = 'publish'";
$ids = $wpdb->get_col( $query );
foreach ( $ids as $id ) {
yield (int) $id; // "Pauses" execution to keep the array out of memory
}
}
/**
* Simple validation stub to ensure data integrity.
*/
private function validate_product( $post_id ) {
return get_post_status( $post_id ) === 'publish';
}
}
Использование функции WP_CLI\Utils\make_progress_bar() превращает фоновый скрипт в прозрачный процесс. Мы получаем обратную связь и видим приблизительное время завершения.
Управление объектным кэшем
Внутренний объектный кэш WordPress увеличивается с каждой обрабатываемой записью. Если его не очистить, использование памяти будет неуклонно расти, пока процесс не завершится. Хотя wp_cache_flush() — самый простой способ предотвратить это, для продакшн-сайтов, использующих Redis или Memcached, это может быть «радикальным решением», поскольку функция очищает кэш для каждого посетителя сайта.
Наш скрипт основан на гибридном подходе. Мы применяем clean_post_cache() для точечной очистки памяти, используемой отдельным постом, сразу после его обработки. Затем мы обращаемся к wp_cache_flush() только один раз за пакет, чтобы гарантировать, что внутренняя память PHP остаётся свободной, не перегружая сервер постоянного кэширования.
Защитная обратная связь и транзакции
Когда что-то идёт не так, вам нужен стандартный вывод. Используйте WP_CLI::success() для отображения положительного исхода и WP_CLI::warning() для уведомления оператора о незначительных проблемах с данными без прерывания скрипта.
Что наиболее важно, код использует SQL-транзакции (START TRANSACTION, COMMIT и ROLLBACK). Это обеспечивает атомарность, принцип «всё или ничего», когда либо вся миграция проходит идеально, либо БД остаётся полностью нетронутой. Если в процессе возникает критическая ошибка, база данных автоматически возвращается в исходное состояние. Это ваша страховка, гарантирующая, что вам никогда не придётся сталкиваться с кривым продакшном, застрявшем в «частично мигрированном» состоянии.
Устойчивость к сбоям: транзакции и логирование
Надеяться на успех и молиться при обработке 50 000 записей — не лучшая стратегия. Если ваш скрипт зависнет на полпути, вы получите несогласованные данные. Обернув цикл в SQL-транзакцию, вы гарантируете, что изменения будут окончательно внесены только в том случае, если вся обработка пакета завершится успешно. В случае критической ошибки базу данных можно будет откатить до исходного состояния.
В случае незначительных проблем, например, если у одного товара отсутствует обязательное поле, не прерывайте выполнение скрипта с помощью WP_CLI::error(). Вместо этого используйте WP_CLI::warning(), чтобы оповестить оператора и записать проблемный ID в текстовый файл для последующей ручной проверки. Это позволит продолжить миграцию, гарантируя, что ни одна запись не будет упущена.
Рабочий процесс: безопасность прежде всего с WP Migrate
Даже с флагом —dry-run запуск скрипта миграции в первый раз может быть стрессовым. Именно здесь WP Migrate становится незаменимым инструментом.
- Перенос в локальную/тестовую среду: Используйте WP Migrate для переноса вашей базы данных с продакшна в локальную среду.
- Выполнение команды: Запустите свою произвольную команду CLI локально. Следите за индикатором выполнения и проверяйте наличие ошибок.
- Проверка данных: Проверьте несколько записей, чтобы убедиться, что функция update_post_meta() выполнила все так, как вы ожидали.
- Пуш или деплой: После проверки вы можете либо запустить команду в продакшне через SSH, либо отправить локальную базу данных обратно в тестовую среду для окончательной QA-проверки.
Кнопка спасения
Даже при локальном тестировании и пробных запусках никогда не трогайте базу данных продакшна без наличия быстрого бэкапа. Непосредственно перед нажатием клавиши «Enter» в команде миграции выполните ручной экспорт базы данных через WP-CLI:
wp db export pre-migration-backup.sql
Это означает, что даже если сервер выйдет из строя, у вас все равно останется файл с меткой времени, готовый для немедленного импорта в БД WordPress. Это быстрый шаг, который может сэкономить вам часы переживаний.
Источник: https://deliciousbrains.com
