Forum Replies Created

Viewing 3 replies - 1 through 3 (of 3 total)
  • Thread Starter 7logic

    (@7logic)

    Здравствуйте!

    Технически ограничение по дате имеет смысл в некоторых ситуациях, т.к. кардинально меняет план запроса, что сказывается положительно на времени выполнения SQL.

    В нашем примере с wp_postmeta 25 млн записей при запросе всего 40 записей для фида Яндекс.Турбо время выполнения SQL занимало 160 секунд. После добавления фильтра по дате – порядка 10 секунд.

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

    есть предложения, как еще улучшить wp_query, не используя ограничение по дате?

    На данный момент проблема существует непосредственно в ядре WP в коде WP_Query при использовании meta_query с логикой OR при наличии большой таблицы wp_postmeta (тикет 24093, тикет 30044).

    Альтернативным решением может быть отказ от условия OR для проверки ytrssenabled_meta_value:
    array('key' => 'ytrssenabled_meta_value', 'compare' => 'NOT EXISTS',),
    В этом случае WP не будет генерировать много JOIN в SQL, и запрос будет работать быстрее. Для того, чтобы это работало, можно предусмотреть добавление отсутствующих в wp_postmeta значений ytrssenabled_meta_value при активации плагина.

    Ещё одним из вариантов может быть кастомный код для формирования SQL вместо WP_Query.

    Самым лучшим вариантом я вижу написание патча для ядра WP в части оптимизации SQL при использовании проверки NOT EXISTS и проверки значения для одного ключа (релевантный тикет):

        'meta_query' => array(
            'relation' => 'OR',
                array('key' => 'ytrssenabled_meta_value', 'compare' => 'NOT EXISTS',),
                array('key' => 'ytrssenabled_meta_value', 'value' => 'yes', 'compare' => '!=',),
        )

    В этом случае не придётся изменять код плагина, а WP_Query будет генерировать более оптимальный SQL. Наша команда попробует поучаствовать в этом процессе. Если сделаем патч, и он войдёт в новую сборку WP, сделаю апдейт в этой ветке.

    Thread Starter 7logic

    (@7logic)

    Добрый день!

    Дело в том, что WP_Query – всего лишь инструмент в руках разработчика, и его важно правильно использовать. Мы поддерживаем доработку плагинов и не хотим плодить хаки в проектах для решения проблем. Я углубился в проблему, разобрался и хочу предложить вам решение. Буду признателен, если найдёте время для анализа предложенного решения и доработки плагина. Кстати, вы поддерживаете pull request через github?

    Предлагаю добавить в настройки плагина фильтр по дате создания записей – “Выгружать записи не старше %s”. Например, можно добавить список с вариантами 1 год, 6 мес, 3 мес, 1 мес, 2 нед. При включении этого фильтра добавлять date_query в args[] для WPQuery. Подобная настройка была бы полезна и для плагина RSS for Yandex Zen.

    Ниже я опишу технические детали, почему это решение работает.

    Указанный в тикете запрос относится к следующему участку кода:

    $args = array(
        'paged' => $paged,
        'ignore_sticky_posts' => 1,
        'post_type' => $yttype,
        'post_status' => 'publish',
        'posts_per_page' => $ytrazbnumber,
        'tax_query' => $tax_query,
        'meta_query' => array(
            'relation' => 'OR',
                array('key' => 'ytrssenabled_meta_value', 'compare' => 'NOT EXISTS',),
                array('key' => 'ytrssenabled_meta_value', 'value' => 'yes', 'compare' => '!=',),
        )
    );
    $args = apply_filters( 'yturbo_query_args', $args );
    $query = new WP_Query( $args );

    Аналогичная проблема есть в плагине RSS for Yandex Zen на следующем участке кода:

    $args = array('ignore_sticky_posts' => 1, 'post_type' => $yztype, 'post_status' => 'publish', 'posts_per_page' => $yznumber,'tax_query' => $tax_query,
    'meta_query' => array('relation' => 'OR', array('key' => 'yzrssenabled_meta_value', 'compare' => 'NOT EXISTS',),
    array('key' => 'yzrssenabled_meta_value', 'value' => 'yes', 'compare' => '!=',),));
    
    $args_alt = apply_filters( 'yzen_query_args', $args, 8 );
    if (isset($args_alt) && is_array($args_alt)) $args = $args_alt;
    $query = new WP_Query( $args );

    Указанный выше порядок аргументов заставляет WP генерировать неоптимальный SQL-код с несколькими LEFT JOIN, что вызывает сборку временной таблицы, которая на больших БД может не влезать в память и начинает сбрасываться на диск. Проблема не новая, проявляется на запросах WPQuery, где включён meta_query с логикой OR, проблема описана в тикетах WP 6-летней давности:
    https://core.trac.wordpress.org/ticket/24093
    https://core.trac.wordpress.org/ticket/30044

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

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

    
    SELECT SQL_CALC_FOUND_ROWS  wp_posts.ID FROM wp_posts  LEFT JOIN wp_term_relationships ON (wp_posts.ID = wp_term_relationships.object_id) LEFT JOIN wp_postmeta ON (wp_posts.ID = wp_postmeta.post_id AND wp_postmeta.meta_key = 'ytrssenabled_meta_value' )  LEFT JOIN wp_postmeta AS mt1 ON ( wp_posts.ID = mt1.post_id ) WHERE 1=1  AND ( 
      wp_term_relationships.term_taxonomy_id IN (1,4,16,178,591,1113,1465)
    ) AND ( 
      wp_postmeta.post_id IS NULL 
      OR 
      ( mt1.meta_key = 'ytrssenabled_meta_value' AND mt1.meta_value != 'yes' )
    ) AND wp_posts.post_date >= '2020-07-18' and wp_posts.post_type = 'post' AND ((wp_posts.post_status = 'publish')) GROUP BY wp_posts.ID ORDER BY wp_posts.post_date DESC LIMIT 0, 40

    План выполнения запроса изменился кардинально:

    +----+-------------+-----------------------+------------+-------+---------------------------------------------------------------------------------------+------------------+---------+---------------------------+------+----------+-----------------------------------------------------------+
    | id | select_type | table                 | partitions | type  | possible_keys                                                                         | key              | key_len | ref                       | rows | filtered | Extra                                                     |
    +----+-------------+-----------------------+------------+-------+---------------------------------------------------------------------------------------+------------------+---------+---------------------------+------+----------+-----------------------------------------------------------+
    |  1 | SIMPLE      | wp_posts              | NULL       | range | PRIMARY,type_status_date,type_status_modified_gmt,status_type_password1_date_modified | type_status_date | 169     | NULL                      |  719 |   100.00 | Using where; Using index; Using temporary; Using filesort |
    |  1 | SIMPLE      | wp_term_relationships | NULL       | ref   | PRIMARY,term_taxonomy_id                                                              | PRIMARY          | 8       | u100007_ramns.wp_posts.ID |    2 |    34.26 | Using where; Using index                                  |
    |  1 | SIMPLE      | wp_postmeta           | NULL       | ref   | post_id,meta_key                                                                      | post_id          | 8       | u100007_ramns.wp_posts.ID |  232 |   100.00 | Using where                                               |
    |  1 | SIMPLE      | mt1                   | NULL       | ref   | post_id                                                                               | post_id          | 8       | u100007_ramns.wp_posts.ID |  232 |   100.00 | Using where                                               |
    +----+-------------+-----------------------+------------+-------+---------------------------------------------------------------------------------------+------------------+---------+---------------------------+------+----------+-----------------------------------------------------------+
    4 rows in set, 1 warning (0.00 sec)

    Такой запрос выполняется в десятки раз быстрее, а результат выполнения запроса тот же (в проекте добавляются десятки записей в день).

    Резюмируя, хочу добавить, что ту же логику возможно реализовать через встроенные в код плагина фильтры yturbo_query_args и yzen_query_args (для плагина RSS for Yandex Zen), но добавление фильтра по дате в настройки плагина видится наиболее правильным решением. Подобный фильтр поможет избежать проблем производительности в проектах с относительно большим объёмом данных.

    Спасибо за внимание.

    С уважением,
    Александр Зайцев
    /* moderator note: пожалуйста, не используйте подписи на форумах. */

    • This reply was modified 5 years, 9 months ago by Yui.
    • This reply was modified 5 years, 9 months ago by Yui. Reason: drop signature part

    Also have a serious problems with Gutenberg after update WP to 5.3

    Galleries with using custom js and css was crashed after update because it changed their DOM structure ( and broken css ad js ) It was very bad idea 🙁 it is too big torubles for minor update 🙁

Viewing 3 replies - 1 through 3 (of 3 total)