После того как обход каталога заменили одним запросом (первая часть), сохранение товара всё равно шло секунды. Оставшаяся работа выглядела дешёвой: у 82 адресатов пересобрать список совместимых товаров. Запросов к базе на это уходило 70, а времени — 4,69 секунды из 4,71. Запросы повторялись, и кэш WordPress не помогал ни разу. Причина — в том, что цикл сам писал в базу.

Это вторая часть цикла «Массовый импорт в WordPress».

Как выглядел цикл

foreach ($recipients as $rid) {
    // соседи по группе в нужном порядке — одинаковы для многих адресатов
    $group = get_posts([
        'post_type'  => 'katalog',
        'include'    => $ids,
        'orderby'    => 'meta_value_num',
        'meta_key'   => 'order',
        'order'      => 'ASC',
        'numberposts'=> -1,
    ]);
    update_post_meta($rid, 'fitsProductUnique', build_list($group, $rid));
    clean_post_cache($rid);
}

Запрос get_posts внутри цикла почти всегда один и тот же. WordPress кэширует результаты запросов в объектном кэше, так что со второй итерации ответ должен приходить из памяти. Но не приходил.

Почему кэш не срабатывал

Ключ кэша у результатов WP_Query содержит метку last_changed группы posts. Любое изменение записи — clean_post_cache(), wp_update_post(), wp_insert_post() — ставит новую метку. Все ранее закэшированные результаты запросов остаются в памяти, но больше никогда не находятся: ключ у следующего запроса уже другой.

В нашем цикле clean_post_cache стоял в конце каждой итерации. Значит, кэш запросов обнулялся 82 раза подряд, и каждый get_posts шёл в базу заново — с сортировкой по мете и соединением таблиц.

Причём clean_post_cache — не единственный виновник. В современных версиях WordPress ту же метку двигают и added_post_meta, updated_post_meta, deleted_post_meta: в нашем цикле update_post_meta перед чисткой делал то же самое. Убрать одну строку было бы мало.

Общее правило: если цикл что-то пишет в записи или их мету — wp_update_post, update_post_meta, clean_post_cache, — то get_posts и WP_Query внутри того же цикла не кэшируются. Всё, что одинаково для всех итераций, собирайте один раз до цикла.

Лечение: таблица порядка один раз

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

$table = build_order_table($allIds);   // один запрос: ID → order, title, common_name
foreach ($recipients as $rid) {
    $group = array_intersect_key($table, array_flip($idsFor[$rid]));
    update_post_meta($rid, 'fitsProductUnique', build_list($group, $rid));
    clean_post_cache($rid);             // теперь не мешает: запросов в цикле больше нет
}

У этого приёма есть подвох: сортировка в PHP должна давать тот же порядок, что давала база. При одинаковых значениях order это не гарантировано — разбор в третьей части.

Результат

Замер по логу nginx на рабочем сайте до и после выката, вместе с правкой из первой части:

БылоСтало
товаров в минуту при обмене12104
среднее время записи товара5,00 с0,56 с
самый связный товар (101 связь)5–8 с0,95 с
товар, на который никто не ссылается (87 % каталога)0,5–0,7 с0,02 с

На втором сайте с той же темой тот же перенос дал 14,2 → 2,0 секунды на тесте, а самый связный товар на рабочем сайте стал записываться за 1,64 секунды.

Где ещё встречается

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

Признак везде один: запросов немного, они повторяются, а время растёт с размером каталога. Проверка — посчитать $wpdb->num_queries до и после цикла и сравнить с числом разных запросов. Если одинаковые запросы идут в базу повторно, кэш запросов кто-то обнуляет.

Импорт тормозит, хотя запросов мало?

Пришлите код обработчика — найду, что обнуляет кэш, и вынесу общее из цикла.

· полезно?
АЧ
Алексей Чикун веб-разработчик, команда TRIOHEAD

Делаю сайты и сервисы целиком: от вёрстки до сервера. Пишу о том, что проверил на своих проектах.

Комментарии

// загружаю комментарии…

Комментарии публикуются после проверки. Ссылки и HTML не принимаются.