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:
@@ -76,6 +76,12 @@ function flat_search( string $table, string $alias, string $conditions ): string
|
||||
. " WHERE 1=1 AND p.post_type = 'hp_listing' AND ((p.post_status = 'publish')){$conditions} GROUP BY p.ID ORDER BY p.post_date DESC LIMIT 0, 20";
|
||||
}
|
||||
|
||||
/** TMDO router shape with post_status / post_date copies (PR-029): INNER JOIN, no GROUP BY. */
|
||||
function flat_copy_search( string $table, string $alias, string $conditions ): string {
|
||||
return 'SELECT SQL_CALC_FOUND_ROWS p.ID FROM `' . P . "posts` p INNER JOIN `" . P . "{$table}` AS `{$alias}` ON (p.ID = `{$alias}`.`post_id`)"
|
||||
. " WHERE 1=1 AND p.post_type = 'hp_listing' AND ((p.post_status = 'publish')){$conditions} AND `{$alias}`.`post_status` IN ('publish') ORDER BY `{$alias}`.`post_date` DESC LIMIT 0, 20";
|
||||
}
|
||||
|
||||
function random_ids( int $count, int $offset, int $max ): string {
|
||||
$ids = array();
|
||||
for ( $i = 0; $i < $count; $i++ ) {
|
||||
@@ -105,6 +111,10 @@ $scenarios = array(
|
||||
[ $lo, $hi ] = $price();
|
||||
return array( flat_search( 'wpdo_post_hp_listing_core', 'e', " AND `e`.`hp_price` BETWEEN '{$lo}' AND '{$hi}'" ), 'SELECT FOUND_ROWS()' );
|
||||
},
|
||||
'entity+copy' => function () use ( $price ) {
|
||||
[ $lo, $hi ] = $price();
|
||||
return array( flat_copy_search( 'wpdo_post_hp_listing_core', 'e', " AND `e`.`hp_price` BETWEEN '{$lo}' AND '{$hi}'" ), 'SELECT FOUND_ROWS()' );
|
||||
},
|
||||
),
|
||||
'2. 價格區間 + verified=1' => array(
|
||||
'native' => function () use ( $price ) {
|
||||
@@ -127,6 +137,10 @@ $scenarios = array(
|
||||
[ $lo, $hi ] = $price();
|
||||
return array( flat_search( 'wpdo_post_hp_listing_core', 'e', " AND `e`.`hp_price` BETWEEN '{$lo}' AND '{$hi}' AND `e`.`hp_verified` = '1'" ), 'SELECT FOUND_ROWS()' );
|
||||
},
|
||||
'entity+copy' => function () use ( $price ) {
|
||||
[ $lo, $hi ] = $price();
|
||||
return array( flat_copy_search( 'wpdo_post_hp_listing_core', 'e', " AND `e`.`hp_price` BETWEEN '{$lo}' AND '{$hi}' AND `e`.`hp_verified` = '1'" ), 'SELECT FOUND_ROWS()' );
|
||||
},
|
||||
),
|
||||
'3. 某 vendor 名下 listing' => array(
|
||||
'post_parent' => fn() => array( 'SELECT SQL_CALC_FOUND_ROWS p.ID FROM `' . P . "posts` p WHERE p.post_parent = " . mt_rand( 1, $vendors ) . " AND p.post_type = 'hp_listing' AND ((p.post_status = 'publish')) ORDER BY p.post_date DESC LIMIT 0, 20", 'SELECT FOUND_ROWS()' ),
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Routed searches filter status and sort by date on the flat table's
|
||||
* post_status / post_date copies (O-1), when those copies are trustworthy.
|
||||
*/
|
||||
class PostQueryRouterCopyTest extends TestCase {
|
||||
|
||||
private const TABLE = 'wp_itest_wpdo_post_rc_core';
|
||||
private const ALIAS = '`wpdoflat_rc_item_rc_core`';
|
||||
|
||||
public static function setUpBeforeClass(): void {
|
||||
$base = WPDO_PLUGIN_DIR;
|
||||
foreach ( array(
|
||||
'includes/adapters/interface-entity-adapter.php',
|
||||
'includes/engine/class-tmdo-entity-registry.php',
|
||||
'includes/engine/class-tmdo-mode-manager.php',
|
||||
'includes/engine/class-tmdo-schema-manager.php',
|
||||
'includes/adapters/class-tmdo-adapter-post.php',
|
||||
'includes/integrations/class-tmdo-post-fields.php',
|
||||
'includes/engine/class-tmdo-post-status-sync.php',
|
||||
'includes/query/class-tmdo-post-query-router.php',
|
||||
) as $file ) {
|
||||
require_once $base . $file;
|
||||
}
|
||||
}
|
||||
|
||||
protected function setUp(): void {
|
||||
global $wpdb;
|
||||
$wpdb->query( 'DROP TABLE IF EXISTS `' . self::TABLE . '`' );
|
||||
$wpdb->query( 'CREATE TABLE `' . self::TABLE . "` ( id BIGINT NOT NULL AUTO_INCREMENT, post_id BIGINT NOT NULL, rc_price DECIMAL(18,6) DEFAULT NULL, post_status VARCHAR(20) DEFAULT NULL, post_date DATETIME DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_entity (post_id), KEY idx_post_status_date (post_status, post_date) ) ENGINE=InnoDB" );
|
||||
$wpdb->query( 'INSERT INTO `' . self::TABLE . "` (post_id, rc_price, post_status, post_date) VALUES (1, 10, 'publish', '2026-01-01 00:00:00')" );
|
||||
|
||||
$GLOBALS['_wp_options'] = array( TMDO_Post_Status_Sync::OPTION => array( 'rc_core' => 'ready' ) );
|
||||
$GLOBALS['_wp_is_admin'] = false;
|
||||
$ref = new ReflectionProperty( WPDO_Schema_Manager::class, 'table_exists_cache' );
|
||||
$ref->setAccessible( true );
|
||||
$ref->setValue( null, array() );
|
||||
$mode = new ReflectionProperty( WPDO_Mode_Manager::class, 'cache' );
|
||||
$mode->setAccessible( true );
|
||||
$mode->setValue( null, array( 'post' => 'aeav_only', 'user' => 'disabled', 'term' => 'disabled', 'comment' => 'disabled' ) );
|
||||
TMDO_Post_Query_Router::flush_copy_cache();
|
||||
|
||||
WPDO_Entity_Registry::init();
|
||||
WPDO_Entity_Registry::register_adapter( 'post', new WPDO_Adapter_Post() );
|
||||
WPDO_Entity_Registry::register_group( 'post', 'rc_core', array( array( 'key' => 'rc_price', 'type' => 'decimal' ) ), array( 'post_types' => array( 'rc_item' ) ) );
|
||||
}
|
||||
|
||||
protected function tearDown(): void {
|
||||
global $wpdb;
|
||||
$wpdb->query( 'DROP TABLE IF EXISTS `' . self::TABLE . '`' );
|
||||
WPDO_Entity_Registry::init();
|
||||
$mode = new ReflectionProperty( WPDO_Mode_Manager::class, 'cache' );
|
||||
$mode->setAccessible( true );
|
||||
$mode->setValue( null, null );
|
||||
}
|
||||
|
||||
/** @return array{join:string, where:string, orderby:string, groupby:string} */
|
||||
private function sql( array $vars ): array {
|
||||
$query = new WP_Query();
|
||||
foreach ( array_merge(
|
||||
array(
|
||||
'post_type' => 'rc_item',
|
||||
'post_status' => 'publish',
|
||||
'meta_query' => array( array( 'key' => 'rc_price', 'value' => array( 5, 20 ), 'compare' => 'BETWEEN' ) ),
|
||||
),
|
||||
$vars
|
||||
) as $k => $v ) {
|
||||
$query->set( $k, $v );
|
||||
}
|
||||
$router = new TMDO_Post_Query_Router();
|
||||
$router->pre_get_posts( $query );
|
||||
return array(
|
||||
'join' => $router->posts_join( '', $query ),
|
||||
'where' => $router->posts_where( " AND wp_posts.post_status = 'publish'", $query ),
|
||||
'orderby' => $router->posts_orderby( 'wp_posts.post_date DESC', $query ),
|
||||
'groupby' => $router->posts_groupby( '', $query ),
|
||||
);
|
||||
}
|
||||
|
||||
private function assert_legacy_shape( array $sql ): void {
|
||||
$this->assertStringContainsString( 'LEFT JOIN', $sql['join'] );
|
||||
$this->assertStringNotContainsString( self::ALIAS . '.`post_status`', $sql['where'] );
|
||||
$this->assertSame( 'wp_posts.post_date DESC', $sql['orderby'] );
|
||||
$this->assertSame( '`wp_itest_posts`.`ID`', $sql['groupby'] );
|
||||
}
|
||||
|
||||
public function test_ready_copies_filter_status_and_order_by_date_on_the_flat_table(): void {
|
||||
$sql = $this->sql( array( 'order' => 'ASC' ) );
|
||||
|
||||
$this->assertStringContainsString( 'INNER JOIN', $sql['join'] );
|
||||
$this->assertStringContainsString( self::ALIAS . ".`post_status` IN ('publish')", $sql['where'] );
|
||||
$this->assertStringContainsString( "wp_posts.post_status = 'publish'", $sql['where'], 'WordPress status clause stays' );
|
||||
$this->assertSame( self::ALIAS . '.`post_date` ASC', $sql['orderby'] );
|
||||
$this->assertSame( '', $sql['groupby'], 'one row per post: no GROUP BY, so LIMIT can stop early' );
|
||||
}
|
||||
|
||||
public function test_several_explicit_statuses_are_kept(): void {
|
||||
$sql = $this->sql( array( 'post_status' => array( 'publish', 'private' ), 'orderby' => 'date' ) );
|
||||
|
||||
$this->assertStringContainsString( self::ALIAS . ".`post_status` IN ('publish','private')", $sql['where'] );
|
||||
$this->assertSame( self::ALIAS . '.`post_date` DESC', $sql['orderby'] );
|
||||
}
|
||||
|
||||
public function test_copies_not_marked_ready_keep_the_legacy_shape(): void {
|
||||
$GLOBALS['_wp_options'] = array( TMDO_Post_Status_Sync::OPTION => array( 'rc_core' => 'columns' ) );
|
||||
|
||||
$this->assert_legacy_shape( $this->sql( array() ) );
|
||||
}
|
||||
|
||||
public function test_rows_with_missing_copies_keep_the_legacy_shape(): void {
|
||||
global $wpdb;
|
||||
$wpdb->query( 'INSERT INTO `' . self::TABLE . '` (post_id, rc_price) VALUES (2, 12)' );
|
||||
|
||||
$this->assert_legacy_shape( $this->sql( array() ) );
|
||||
}
|
||||
|
||||
public function test_other_orderings_or_implicit_statuses_keep_the_legacy_shape(): void {
|
||||
$this->assert_legacy_shape( $this->sql( array( 'orderby' => 'title' ) ) );
|
||||
$this->assert_legacy_shape( $this->sql( array( 'post_status' => '' ) ) );
|
||||
$this->assert_legacy_shape( $this->sql( array( 'post_status' => 'any' ) ) );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user