perf(2meet-data-optimizer): route status and date ordering to entity tables
- TMDO_Post_Query_Router: when a group's post_status / post_date copies are ready and none is NULL (one index probe per group per request), a single-post-type search with explicit statuses and date ordering INNER JOINs the flat table, filters the copied status, orders by the copied post_date and skips GROUP BY. Otherwise the query keeps its old shape. - New posts_orderby filter; bench gains an `entity+copy` variant. - Bench (1M, with SQL_CALC_FOUND_ROWS): scenario 1 p95 3,739 -> 773 ms (p50 670 -> 770); scenario 2 p50 356 -> 448 ms. Without found-rows the same shape runs in 8 ms (PR-030). - Verify: core unit 659 / integration 471 green, phpcs clean - PR: PR-029
This commit is contained in:
@@ -20,6 +20,13 @@
|
||||
* 3. posts_where — append WHERE conditions targeting flat columns
|
||||
* 4. posts_groupby — ensure GROUP BY posts.ID to dedup join multiplication
|
||||
*
|
||||
* Status / date on the flat table (O-1): when the group's post_status /
|
||||
* post_date copies are ready (TMDO_Post_Status_Sync) and none is missing,
|
||||
* a single-post-type search with explicit statuses and date ordering
|
||||
* INNER JOINs the table, filters the copies, orders by the copied
|
||||
* post_date and skips GROUP BY (one row per post), so MySQL can walk
|
||||
* `idx_post_status_date` and stop at the LIMIT.
|
||||
*
|
||||
* 🔒 v2.9.x → v2.10.x frozen contract: never touches user entity. Routes
|
||||
* only entity_type='post'.
|
||||
*
|
||||
@@ -43,6 +50,16 @@ class TMDO_Post_Query_Router {
|
||||
/** Query var key for capturing routed clauses (consumed by posts_join/where). */
|
||||
private const QUERY_VAR = 'wpdo_post_clauses';
|
||||
|
||||
/** Query var holding the status / date copy route, when one applies. */
|
||||
private const COPY_VAR = 'wpdo_post_copy_route';
|
||||
|
||||
/**
|
||||
* Per-request answer to "are this group's copies usable?".
|
||||
*
|
||||
* @var array<string, bool>
|
||||
*/
|
||||
private static array $copies_usable = array();
|
||||
|
||||
/**
|
||||
* Register all WP_Query hooks.
|
||||
*
|
||||
@@ -53,6 +70,7 @@ class TMDO_Post_Query_Router {
|
||||
add_filter( 'posts_join', array( $this, 'posts_join' ), 10, 2 );
|
||||
add_filter( 'posts_where', array( $this, 'posts_where' ), 10, 2 );
|
||||
add_filter( 'posts_groupby', array( $this, 'posts_groupby' ), 10, 2 );
|
||||
add_filter( 'posts_orderby', array( $this, 'posts_orderby' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,6 +152,11 @@ class TMDO_Post_Query_Router {
|
||||
|
||||
$query->set( 'meta_query', $remaining );
|
||||
$query->set( self::QUERY_VAR, $routed );
|
||||
|
||||
$copy = self::copy_route( $query, $routed );
|
||||
if ( $copy ) {
|
||||
$query->set( self::COPY_VAR, $copy );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,7 +190,8 @@ class TMDO_Post_Query_Router {
|
||||
continue;
|
||||
}
|
||||
|
||||
$join .= " LEFT JOIN `{$table}` AS `{$alias}` "
|
||||
$type = self::copy_route_for( $query, $post_type ) ? 'INNER' : 'LEFT';
|
||||
$join .= " {$type} JOIN `{$table}` AS `{$alias}` "
|
||||
. "ON (`{$wpdb->posts}`.`ID` = `{$alias}`.`post_id`)";
|
||||
}
|
||||
|
||||
@@ -201,9 +225,31 @@ class TMDO_Post_Query_Router {
|
||||
}
|
||||
}
|
||||
|
||||
$copy = $query->get( self::COPY_VAR );
|
||||
if ( ! empty( $copy ) ) {
|
||||
$alias = self::table_alias( $copy['post_type'], $copy['group'] );
|
||||
$where .= self::build_condition( "`{$alias}`.`post_status`", 'IN', $copy['statuses'] );
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
/**
|
||||
* Order by the flat table's post_date copy when the copy route applies.
|
||||
*
|
||||
* @param string|null $orderby Current ORDER BY SQL.
|
||||
* @param \WP_Query $query Current WP_Query.
|
||||
* @return string Modified ORDER BY SQL.
|
||||
*/
|
||||
public function posts_orderby( ?string $orderby, \WP_Query $query ): string {
|
||||
$orderby = (string) ( $orderby ?? '' );
|
||||
$copy = $query->get( self::COPY_VAR );
|
||||
if ( empty( $copy ) ) {
|
||||
return $orderby;
|
||||
}
|
||||
return '`' . self::table_alias( $copy['post_type'], $copy['group'] ) . '`.`post_date` ' . $copy['order'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure GROUP BY posts.ID to deduplicate JOIN-multiplied rows.
|
||||
*
|
||||
@@ -214,7 +260,7 @@ class TMDO_Post_Query_Router {
|
||||
public function posts_groupby( ?string $groupby, \WP_Query $query ): string {
|
||||
$groupby = (string) ( $groupby ?? '' );
|
||||
$routed = $query->get( self::QUERY_VAR );
|
||||
if ( empty( $routed ) || ! is_array( $routed ) ) {
|
||||
if ( empty( $routed ) || ! is_array( $routed ) || ! empty( $query->get( self::COPY_VAR ) ) ) {
|
||||
return $groupby;
|
||||
}
|
||||
|
||||
@@ -242,6 +288,76 @@ class TMDO_Post_Query_Router {
|
||||
return TMDO_Mode_Manager::reads_from_flat( 'post' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Forget per-request copy checks (tests, or after `post-status-sync`).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function flush_copy_cache(): void {
|
||||
self::$copies_usable = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* The status / date copy route for a routed query, or [] when it does not apply.
|
||||
*
|
||||
* @param \WP_Query $query Current WP_Query.
|
||||
* @param array<string, array<int, array>> $routed Routed clauses keyed by post type.
|
||||
* @return array{post_type:string, group:string, statuses:string[], order:string}|array{}
|
||||
*/
|
||||
private static function copy_route( \WP_Query $query, array $routed ): array {
|
||||
if ( 1 !== count( $routed ) ) {
|
||||
return array();
|
||||
}
|
||||
$post_type = (string) array_key_first( $routed );
|
||||
$groups = array_values( array_unique( array_column( $routed[ $post_type ], 'group' ) ) );
|
||||
if ( 1 !== count( $groups ) || ! in_array( $query->get( 'orderby' ), array( '', 'date', 'post_date' ), true ) ) {
|
||||
return array();
|
||||
}
|
||||
$statuses = $query->get( 'post_status' );
|
||||
$statuses = array_values( array_filter( array_map( 'sanitize_key', is_array( $statuses ) ? $statuses : explode( ',', (string) $statuses ) ) ) );
|
||||
if ( empty( $statuses ) || in_array( 'any', $statuses, true ) || ! self::copies_usable( $groups[0] ) ) {
|
||||
return array();
|
||||
}
|
||||
return array(
|
||||
'post_type' => $post_type,
|
||||
'group' => $groups[0],
|
||||
'statuses' => $statuses,
|
||||
'order' => 'ASC' === strtoupper( (string) $query->get( 'order' ) ) ? 'ASC' : 'DESC',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the copy route applies to this post type's join.
|
||||
*
|
||||
* @param \WP_Query $query Current WP_Query.
|
||||
* @param string $post_type Post type slug.
|
||||
* @return bool
|
||||
*/
|
||||
private static function copy_route_for( \WP_Query $query, string $post_type ): bool {
|
||||
$copy = $query->get( self::COPY_VAR );
|
||||
return ! empty( $copy ) && $copy['post_type'] === $post_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies are marked ready and no row lacks them (bulk writes that bypass
|
||||
* the Hook Bus leave NULLs until `wp tmdo post-status-sync` runs again).
|
||||
* One index probe per group per request.
|
||||
*
|
||||
* @param string $group Entity group name.
|
||||
* @return bool
|
||||
*/
|
||||
private static function copies_usable( string $group ): bool {
|
||||
if ( ! isset( self::$copies_usable[ $group ] ) ) {
|
||||
global $wpdb;
|
||||
$table = TMDO_Schema_Manager::get_table_name( 'post', $group );
|
||||
self::$copies_usable[ $group ] = class_exists( 'TMDO_Post_Status_Sync' )
|
||||
&& 'ready' === TMDO_Post_Status_Sync::state( $group )
|
||||
&& TMDO_Schema_Manager::table_exists( $table )
|
||||
&& null === $wpdb->get_var( "SELECT 1 FROM `{$table}` WHERE `post_status` IS NULL LIMIT 1" );
|
||||
}
|
||||
return self::$copies_usable[ $group ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a deterministic JOIN alias for a (post_type, group) pair.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user