perf(2meet-data-optimizer): route sort-only searches from the flat table when every post has a row

- A sort-only search (HivePress _sort, no routed filter) LEFT JOINs the flat table to keep posts without a row; it now drives from the flat table (STRAIGHT_JOIN, INNER JOIN) when every post of the type has a flat row. The router gives a new post of a type with its own groups an empty row (wp_insert_post); coverage is one anti-join per group, cached 15 min and per post-mode change (forget_coverage()).
- A flat-first search drops the GROUP BY posts.ID WordPress adds for any tax query when the flat row is the only join (WooCommerce's product_visibility NOT IN on searches is a subquery); a joining tax query keeps it.
- Verify: live router suite (5 new cases, RED before); dev36 rating__desc page, ids identical: native ~3.3 s, 2.1 routing ~1.5–1.9 s, now ~0.51–0.85 s (main query 440 → 46 ms, COUNT 400 → 160 ms). p50 ≤ 300 ms is still not met: page rendering and HivePress's own get_posts() in pre_get_posts (suppress_filters, ~100 ms) remain.
- PR: PR-079
This commit is contained in:
2026-09-27 08:31:47 +08:00
parent ad7acc40b7
commit 84f6e32913
2 changed files with 236 additions and 8 deletions
+132
View File
@@ -424,3 +424,135 @@ TMDO_Live::test(
tmdo_live_assert_true( 0 === strpos( ltrim( $routed->request ), 'SELECT STRAIGHT_JOIN' ), 'flat-first: ' . $routed->request );
}
);
/**
* HivePress-style sort-only search (rating__order), fields ids, first page.
*
* @param string $order ASC | DESC.
* @return array<string, mixed> WP_Query args.
*/
function tmdo_live_router_rating_sort( string $order ): array {
return array(
'post_type' => 'hp_listing',
'post_status' => 'publish',
'posts_per_page' => 10,
'fields' => 'ids',
'suppress_filters' => false,
'meta_query' => array(
array(
'relation' => 'OR',
'rating__order' => array( 'key' => 'hp_rating', 'type' => 'DECIMAL', 'compare' => 'NOT EXISTS' ),
array( 'key' => 'hp_rating', 'type' => 'DECIMAL', 'compare' => 'EXISTS' ),
),
),
'orderby' => 'rating__order',
'order' => $order,
);
}
/**
* Native and routed runs of the same query.
*
* @param array<string, mixed> $args Query args.
* @return array{0:WP_Query, 1:WP_Query}
*/
function tmdo_live_router_native_and_routed( array $args ): array {
$native = new WP_Query( $args );
$router = new TMDO_Post_Query_Router();
$router->register_hooks();
try {
$routed = new WP_Query( $args );
} finally {
tmdo_live_router_unhook( $router );
}
return array( $native, $routed );
}
TMDO_Live::test(
'a sort-only search drives from the flat table when every post of the type has a flat row (2.2 PR-079)',
static function () {
TMDO_Live_Fixtures::field( 'post', 'hp_rating', 'hp_listing' );
TMDO_Live_Fixtures::use_modes( array( 'post' => 'shadow_read' ) );
TMDO_Post_Query_Router::forget_coverage();
foreach ( array( 'DESC', 'ASC' ) as $order ) {
list( $native, $routed ) = tmdo_live_router_native_and_routed( tmdo_live_router_rating_sort( $order ) );
tmdo_live_assert_same( array_map( 'intval', $native->posts ), array_map( 'intval', $routed->posts ), "{$order} ids" );
tmdo_live_assert_same( $native->found_posts, $routed->found_posts, "{$order} found_posts" );
tmdo_live_assert_true( 0 === strpos( ltrim( $routed->request ), 'SELECT STRAIGHT_JOIN' ), "{$order} flat-first: " . $routed->request );
}
}
);
TMDO_Live::test(
'a sort-only search keeps the LEFT JOIN while a post of the type has no flat row (2.2 PR-079)',
static function () {
global $wpdb;
TMDO_Live_Fixtures::field( 'post', 'hp_rating', 'hp_listing' );
TMDO_Live_Fixtures::use_modes( array( 'post' => 'shadow_read' ) );
$bare = TMDO_Live_Fixtures::post( 'hp_listing' );
$wpdb->delete( TMDO_Schema_Manager::get_table_name( 'post', 'hp_listing_core' ), array( 'post_id' => $bare ) );
TMDO_Post_Query_Router::forget_coverage();
try {
list( $native, $routed ) = tmdo_live_router_native_and_routed( tmdo_live_router_rating_sort( 'DESC' ) );
} finally {
TMDO_Post_Query_Router::forget_coverage();
}
tmdo_live_assert_same( array_map( 'intval', $native->posts ), array_map( 'intval', $routed->posts ), 'ids' );
tmdo_live_assert_true( false === strpos( $routed->request, 'STRAIGHT_JOIN' ), 'not flat-first: ' . $routed->request );
}
);
TMDO_Live::test(
'a post created while the router is active gets its empty flat row (2.2 PR-079)',
static function () {
global $wpdb;
TMDO_Live_Fixtures::field( 'post', 'hp_rating', 'hp_listing' );
TMDO_Live_Fixtures::use_modes( array( 'post' => 'shadow_read' ) );
$router = new TMDO_Post_Query_Router();
$router->register_hooks();
try {
$id = TMDO_Live_Fixtures::post( 'hp_listing' );
} finally {
tmdo_live_router_unhook( $router );
}
$table = TMDO_Schema_Manager::get_table_name( 'post', 'hp_listing_core' );
tmdo_live_assert_same( '1', $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM `{$table}` WHERE post_id = %d", $id ) ), 'flat row created' ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
}
);
TMDO_Live::test(
'a flat-first search drops the GROUP BY WordPress adds for a subquery-only tax query (2.2 PR-079)',
static function () {
TMDO_Live_Fixtures::field( 'post', 'hp_rating', 'hp_listing' );
TMDO_Live_Fixtures::use_modes( array( 'post' => 'shadow_read' ) );
TMDO_Post_Query_Router::forget_coverage();
// Like WooCommerce's product_visibility exclusion on front-end searches: NOT IN
// is a subquery, yet WordPress groups by posts.ID for any tax query.
$args = tmdo_live_router_rating_sort( 'DESC' ) + array(
'tax_query' => array( array( 'taxonomy' => 'hp_listing_category', 'field' => 'term_id', 'terms' => array( 999999999 ), 'operator' => 'NOT IN' ) ),
);
list( $native, $routed ) = tmdo_live_router_native_and_routed( $args );
tmdo_live_assert_same( array_map( 'intval', $native->posts ), array_map( 'intval', $routed->posts ), 'ids' );
tmdo_live_assert_true( 0 === strpos( ltrim( $routed->request ), 'SELECT STRAIGHT_JOIN' ), 'flat-first: ' . $routed->request );
tmdo_live_assert_true( false === stripos( $routed->request, 'GROUP BY' ), 'no GROUP BY: ' . $routed->request );
}
);
TMDO_Live::test(
'a flat-first search keeps the GROUP BY when a tax query joins term relationships (2.2 PR-079)',
static function () {
TMDO_Live_Fixtures::field( 'post', 'hp_rating', 'hp_listing' );
TMDO_Live_Fixtures::use_modes( array( 'post' => 'shadow_read' ) );
TMDO_Post_Query_Router::forget_coverage();
$terms = get_terms( array( 'taxonomy' => 'hp_listing_category', 'number' => 2, 'hide_empty' => true, 'fields' => 'ids' ) );
if ( count( (array) $terms ) < 2 ) {
throw new TMDO_Live_Skip( 'needs two used hp_listing_category terms' );
}
$args = tmdo_live_router_rating_sort( 'DESC' ) + array(
'tax_query' => array( array( 'taxonomy' => 'hp_listing_category', 'field' => 'term_id', 'terms' => $terms, 'operator' => 'IN' ) ),
);
list( $native, $routed ) = tmdo_live_router_native_and_routed( $args );
tmdo_live_assert_same( array_map( 'intval', $native->posts ), array_map( 'intval', $routed->posts ), 'ids' );
tmdo_live_assert_true( false !== stripos( $routed->request, 'GROUP BY' ), 'GROUP BY kept: ' . $routed->request );
}
);