Как ускорить фильтр записей по мета полям acf wordpress?
Здравствуйте. Есть тип записи у которого почти пол миллиона постов. Раньше когда постов было меньше 2000 фильтрация по meta_query работала быстро. Но сейчас когда постов больше 400к загрузка длится от 5 минут. Судя по информации в различных форумах дело именно в фильтрации по метаполям. Но у меня много полей которые участвуют в фильтрации и менять 400к записей под таксономии будет проблематично. Есть ли какой то способ ускорить фильтр постов в wp_query используя meta_query? На страницу вывожу по 5 штук. Ведь выборка не должна отобрать первые 5 записей подходящих и выдать результат, просто почему так долго?
Пример кода(урезанный вариант):
// $template = $_POST["template"]; $args['post_type'] = 'objects'; $args['post_status'] = 'publish'; $args['posts_per_page'] = 10; // СОРТИРОВКА // по цене if (!empty($_POST["sortByPrice"])) { $args['meta_key'] = 'price'; $args['orderby'] = 'meta_value_num'; $args['order'] = $_POST["sortByPrice"]; $args['ignore_custom_sort'] = true; } $args['meta_query'] = array( 'relation' => 'AND' ); // Страница if (!empty($_POST["page"])) { $args['paged'] = $_POST["page"]; } // META // Цена ОТ if (!empty($_POST["priceFrom"])) { $args['meta_query'][] = array( 'key' => 'price', 'value' => $_POST["priceFrom"], 'type' => 'NUMERIC', 'compare' => '>' ); } // Цена ДО if (!empty($_POST["priceTo"])) { $args['meta_query'][] = array( 'key' => 'price', 'value' => $_POST["priceTo"], 'type' => 'NUMERIC', 'compare' => '<' ); } // Площадь ОТ if (!empty($_POST["squareFrom"])) { $args['meta_query'][] = array( 'key' => 'square', 'value' => $_POST["squareFrom"], 'type' => 'NUMERIC', 'compare' => '>' ); } // Площадь ДО if (!empty($_POST["squareTo"])) { $args['meta_query'][] = array( 'key' => 'square', 'value' => $_POST["squareTo"], 'type' => 'NUMERIC', 'compare' => '<' ); } // Тип сделки if(!empty($_POST["dealType"])) { $objCategories[] = $_POST["dealType"]; } // вторичное if($_POST["street"] == 'вторичное') { $objCategories[] = 'real-estate-apartments-sale-secondary'; } if(!empty($_POST["dealType"]) or $_POST["street"] == 'вторичное') { $args['tax_query'][] = array( 'taxonomy' => 'obj_categories', 'field' => 'slug', 'terms' => $objCategories, ); } // Только с фото if (!empty($_POST["withPhotos"])) { $args['meta_query'][] = array( array( 'key' => 'gallery', 'value' => array('', 0), 'compare' => 'NOT IN' ) ); } // Только с газом if (!empty($_POST["gas_type"])) { $args['meta_query'][] = array( array( 'key' => 'gas_type_name', 'value' => array('', 0), 'compare' => 'NOT IN' ) ); } // Кол-во комнат // TODO: Нужно найти все совпадения if (!empty($_POST["rooms"])) { $args_for_rooms = array( 'relation' => 'OR' ); $arrayRooms = explode(",", $_POST["rooms"]); foreach ($arrayRooms as $key => $value) { // $value = 0,1,2,3,4+ switch ($value) { case '4+': $array_for_four = [ 'relation' => 'AND' ]; $array_for_four[] = array( 'key' => 'rooms', 'value' => 3, 'compare' => '>' ); // исключаю студии, так студия = 6 символов и они присутствуют в выдаче if (!in_array('studia', $arrayRooms)) { $array_for_four[] = array( 'key' => 'rooms', 'value' => array('студия', '', '-'), 'compare' => 'NOT IN' ); } $args_for_rooms[] = $array_for_four; break; case 3: case 2: case 1: $args_for_rooms[] = array( 'key' => 'rooms', 'value' => $value, 'compare' => 'IN' ); break; case 'studia': $args_for_rooms[] = array( 'key' => 'rooms', 'value' => array('студия'), 'compare' => 'IN' ); break; } } $args['meta_query'][] = $args_for_rooms; } // Поиск в названии (улица и тд) if (!empty($_POST["street"])) { //$args['obj_title'] = $_POST["street"]; // добавялю в массив args $args['s_meta_keys'] = array('title', 'tags'); $args['s'] = $_POST["street"]; } function title_filter($where, &$wp_query) { global $wpdb; // check if $_POST['novostroi'] exists if (!empty($_POST['novostroi'])) { // exclude posts with "участок" in their title $where .= ' AND ' . $wpdb->posts . '.post_title NOT LIKE '%участок%''; $where .= ' AND ' . $wpdb->posts . '.post_title NOT LIKE '%коммерч%''; $where .= ' AND ' . $wpdb->posts . '.post_title NOT LIKE '%дом%''; } if ($search_term = $wp_query->get('obj_title')) { $where .= ' AND ' . $wpdb->posts . '.post_title LIKE '%' . esc_sql(like_escape($search_term)) . '%''; } return $where; } add_filter('posts_where', 'title_filter', 10, 2); add_filter('posts_where', 'yanco_posts_where'); function yanco_posts_where( $where ) { $where = str_replace( "meta_key = 'metro_station_$", "meta_key LIKE 'metro_station_%", $where ); return $where; } $args['no_found_rows'] = true; $posts = new WP_Query($args); $response_array = array(); remove_filter('posts_where', 'title_filter', 10, 2); if ($posts->have_posts()) { $response_array['success'] = true; while ($posts->have_posts()) { $posts->the_post(); ob_start(); get_template_part("template-parts/loop/object-item-new"); $response_array['html'] .= ob_get_contents(); ob_end_clean(); } } else { $response_array['success'] = false; } $response_array['args'] = $args; $response_array['$_POST'] = $_POST; wp_send_json($response_array); wp_die(); |
// $template = $_POST["template"]; $args['post_type'] = 'objects'; $args['post_status'] = 'publish'; $args['posts_per_page'] = 10; // СОРТИРОВКА // по цене if (!empty($_POST["sortByPrice"])) { $args['meta_key'] = 'price'; $args['orderby'] = 'meta_value_num'; $args['order'] = $_POST["sortByPrice"]; $args['ignore_custom_sort'] = true; } $args['meta_query'] = array( 'relation' => 'AND' ); // Страница if (!empty($_POST["page"])) { $args['paged'] = $_POST["page"]; } // META // Цена ОТ if (!empty($_POST["priceFrom"])) { $args['meta_query'][] = array( 'key' => 'price', 'value' => $_POST["priceFrom"], 'type' => 'NUMERIC', 'compare' => '>' ); } // Цена ДО if (!empty($_POST["priceTo"])) { $args['meta_query'][] = array( 'key' => 'price', 'value' => $_POST["priceTo"], 'type' => 'NUMERIC', 'compare' => '<' ); } // Площадь ОТ if (!empty($_POST["squareFrom"])) { $args['meta_query'][] = array( 'key' => 'square', 'value' => $_POST["squareFrom"], 'type' => 'NUMERIC', 'compare' => '>' ); } // Площадь ДО if (!empty($_POST["squareTo"])) { $args['meta_query'][] = array( 'key' => 'square', 'value' => $_POST["squareTo"], 'type' => 'NUMERIC', 'compare' => '<' ); } // Тип сделки if(!empty($_POST["dealType"])) { $objCategories[] = $_POST["dealType"]; } // вторичное if($_POST["street"] == 'вторичное') { $objCategories[] = 'real-estate-apartments-sale-secondary'; } if(!empty($_POST["dealType"]) or $_POST["street"] == 'вторичное') { $args['tax_query'][] = array( 'taxonomy' => 'obj_categories', 'field' => 'slug', 'terms' => $objCategories, ); } // Только с фото if (!empty($_POST["withPhotos"])) { $args['meta_query'][] = array( array( 'key' => 'gallery', 'value' => array('', 0), 'compare' => 'NOT IN' ) ); } // Только с газом if (!empty($_POST["gas_type"])) { $args['meta_query'][] = array( array( 'key' => 'gas_type_name', 'value' => array('', 0), 'compare' => 'NOT IN' ) ); } // Кол-во комнат // TODO: Нужно найти все совпадения if (!empty($_POST["rooms"])) { $args_for_rooms = array( 'relation' => 'OR' ); $arrayRooms = explode(",", $_POST["rooms"]); foreach ($arrayRooms as $key => $value) { // $value = 0,1,2,3,4+ switch ($value) { case '4+': $array_for_four = [ 'relation' => 'AND' ]; $array_for_four[] = array( 'key' => 'rooms', 'value' => 3, 'compare' => '>' ); // исключаю студии, так студия = 6 символов и они присутствуют в выдаче if (!in_array('studia', $arrayRooms)) { $array_for_four[] = array( 'key' => 'rooms', 'value' => array('студия', '', '-'), 'compare' => 'NOT IN' ); } $args_for_rooms[] = $array_for_four; break; case 3: case 2: case 1: $args_for_rooms[] = array( 'key' => 'rooms', 'value' => $value, 'compare' => 'IN' ); break; case 'studia': $args_for_rooms[] = array( 'key' => 'rooms', 'value' => array('студия'), 'compare' => 'IN' ); break; } } $args['meta_query'][] = $args_for_rooms; } // Поиск в названии (улица и тд) if (!empty($_POST["street"])) { //$args['obj_title'] = $_POST["street"]; // добавялю в массив args $args['s_meta_keys'] = array('title', 'tags'); $args['s'] = $_POST["street"]; } function title_filter($where, &$wp_query) { global $wpdb; // check if $_POST['novostroi'] exists if (!empty($_POST['novostroi'])) { // exclude posts with "участок" in their title $where .= ' AND ' . $wpdb->posts . '.post_title NOT LIKE '%участок%''; $where .= ' AND ' . $wpdb->posts . '.post_title NOT LIKE '%коммерч%''; $where .= ' AND ' . $wpdb->posts . '.post_title NOT LIKE '%дом%''; } if ($search_term = $wp_query->get('obj_title')) { $where .= ' AND ' . $wpdb->posts . '.post_title LIKE '%' . esc_sql(like_escape($search_term)) . '%''; } return $where; } add_filter('posts_where', 'title_filter', 10, 2); add_filter('posts_where', 'yanco_posts_where'); function yanco_posts_where( $where ) { $where = str_replace( "meta_key = 'metro_station_$", "meta_key LIKE 'metro_station_%", $where ); return $where; } $args['no_found_rows'] = true; $posts = new WP_Query($args); $response_array = array(); remove_filter('posts_where', 'title_filter', 10, 2); if ($posts->have_posts()) { $response_array['success'] = true; while ($posts->have_posts()) { $posts->the_post(); ob_start(); get_template_part("template-parts/loop/object-item-new"); $response_array['html'] .= ob_get_contents(); ob_end_clean(); } } else { $response_array['success'] = false; } $response_array['args'] = $args; $response_array['$_POST'] = $_POST; wp_send_json($response_array); wp_die();
Дополнительно:
Ответы:
Почему медленно? Посмотрите какие SQL-запросы порождает wordpress. Там все довольно наглядно. Увы, при такой архитектуре хранения метаданных тормоза - неизбежное зло.
Можно ли ускорить? Можно попробовать в таблице wp_postmeta создать индекс по полю meta_value. В некоторых случаях немного помогает.
А чтобы радикально улучшить, то только менять способ хранения метаданных.
3) Use Local JSON (or Export to PHP). Local JSON is a new feature added in version 5 which saves field group and field settings as .json files within your theme. The idea is similar to caching, and both dramatically speeds up ACF and allows for version control over your field settings! - https://help.filandor.com/pamyatka-po-proizvolnym-...
У меня такое было с магазином вукомерц, надо индексы добавлять, летает просто:
Index WP MySQL For Speed
SQLite Object Cache (For Ubuntu: sudo apt update / sudo apt install php7.4-sqlite3 / sudo phpenmod pdo_sqlite / sudo systemctl restart apache2)
ну и естессно мемкеш
Опишите проблему, и специалист поможет с настройкой, исправлением ошибки или доработкой сайта. Подберём понятный план работ без лишней переписки.
Пока нет других ответов. Будьте первым, кто поможет автору.
Ответить на вопрос

Фильтр по ACF-полям на 400 тысячах записей тормозит не из-за самого ACF, а из-за устройства таблицы
wp_postmeta. В WordPress каждое поле хранится отдельной строкой, и сложныйmeta_queryпревращается в несколько JOIN по огромной таблице. На малом сайте это работает, на сотнях тысяч записей начинает занимать минуты.Главное правило: всё, по чему пользователь фильтрует каталог, лучше хранить не только в ACF meta, а в таксономиях или отдельной индексной таблице. Например, город, тип объекта, статус, категория, год, бренд — это хорошие кандидаты на taxonomies. По ним WordPress фильтрует заметно быстрее, чем по произвольным полям.
Если нужны диапазоны, даты, числовые значения и сложная математика, делайте lookup-таблицу:
CREATE TABLE wp_object_filter_index ( post_id BIGINT UNSIGNED NOT NULL PRIMARY KEY, city_id BIGINT UNSIGNED NULL, price INT UNSIGNED NULL, rooms TINYINT UNSIGNED NULL, status VARCHAR(32) NULL, KEY city_id (city_id), KEY price (price), KEY rooms (rooms), KEY status (status) );
При сохранении записи обновляйте строку в этой таблице, а на фронте сначала выбирайте ID через быстрый SQL с индексами, потом отдавайте эти ID в
post__in. Это нормальная практика для больших каталогов.Дополнительно включите кэш результатов фильтра, используйте
no_found_rows => true, не вытягивайте все поля записи без необходимости и проверьте, нет ли сортировки по meta_value без индекса. Если поиск текстовый, лучше подключить OpenSearch/ElasticSearch или хотя бы отдельный полнотекстовый индекс, а не пытаться заставитьWP_Queryделать всё черезwp_postmeta.