perf: make the tours archive query index-only by default

The archive attached an always-on _vc_featured NOT EXISTS join and a
_vc_review_count join (plus SQL_CALC_FOUND_ROWS) to every request, which made
/tours/ crawl/time out over a ~70k-tour catalogue. Default sort now orders by
post date with no postmeta joins; price/rating sorts add a single meta_key join
on demand; meta_query is only attached when a filter is active. Bump 1.8.4.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J4hNL1PSHxXbu94SCenpKj
This commit is contained in:
2026-09-16 07:48:11 +03:30
parent 01f414e10b
commit b0e302be15
3 changed files with 26 additions and 33 deletions

View File

@@ -289,7 +289,7 @@ class VC_Display {
*/
public static function build_query_vars( array $base_tax = array() ) {
$f = self::get_filter_state();
$vars = array( 'posts_per_page' => 12 );
$vars = array( 'posts_per_page' => 12, 'ignore_sticky_posts' => true );
if ( '' !== $f['q'] ) {
$vars['s'] = $f['q'];
@@ -346,46 +346,39 @@ class VC_Display {
if ( $f['sellout'] ) {
$meta_query[] = array( 'key' => '_vc_flags', 'value' => 'LIKELY_TO_SELL_OUT', 'compare' => 'LIKE' );
}
// Featured tours always sort first, whatever the chosen sort. OR/NOT
// EXISTS keeps non-featured tours in the results too.
$meta_query['vc_featured'] = array(
'relation' => 'OR',
array( 'key' => '_vc_featured', 'value' => '1', 'compare' => '=' ),
array( 'key' => '_vc_featured', 'compare' => 'NOT EXISTS' ),
);
$orderby = array( 'vc_featured' => 'DESC' );
// Sort. Performance: the default (newest) orders by post date only — no
// postmeta JOIN and no featured/review tiebreak — so the unfiltered
// archive stays fast even with a very large catalogue (tens of thousands
// of tours). Price/rating sorts add a single meta_key JOIN, on demand.
switch ( $f['sort'] ) {
case 'price_asc':
$meta_query['vc_price'] = array( 'key' => '_vc_price_from', 'type' => 'DECIMAL', 'compare' => 'EXISTS' );
$orderby['vc_price'] = 'ASC';
$vars['meta_key'] = '_vc_price_from';
$vars['orderby'] = 'meta_value_num';
$vars['order'] = 'ASC';
break;
case 'price_desc':
$meta_query['vc_price'] = array( 'key' => '_vc_price_from', 'type' => 'DECIMAL', 'compare' => 'EXISTS' );
$orderby['vc_price'] = 'DESC';
$vars['meta_key'] = '_vc_price_from';
$vars['orderby'] = 'meta_value_num';
$vars['order'] = 'DESC';
break;
case 'rating':
$meta_query['vc_rating_sort'] = array( 'key' => '_vc_rating', 'type' => 'DECIMAL', 'compare' => 'EXISTS' );
$orderby['vc_rating_sort'] = 'DESC';
$vars['meta_key'] = '_vc_rating';
$vars['orderby'] = 'meta_value_num';
$vars['order'] = 'DESC';
break;
default:
$vars['orderby'] = 'date';
$vars['order'] = 'DESC';
}
// Keep the selected sort as the primary order, then prefer the more
// established tour when two products otherwise rank the same.
$meta_query['vc_reviews_sort'] = array(
'key' => '_vc_review_count',
'type' => 'NUMERIC',
'compare' => 'EXISTS',
);
$orderby['vc_reviews_sort'] = 'DESC';
$orderby['date'] = 'DESC'; // stable tiebreak
if ( count( $meta_query ) > 1 ) {
$meta_query['relation'] = 'AND';
// Only attach a meta_query when a filter is actually active, so the plain
// archive query carries no postmeta joins at all.
if ( ! empty( $meta_query ) ) {
if ( count( $meta_query ) > 1 ) {
$meta_query['relation'] = 'AND';
}
$vars['meta_query'] = $meta_query;
}
$vars['meta_query'] = $meta_query;
$vars['orderby'] = $orderby;
return $vars;
}