perf(2meet-data-optimizer): let the status/date copy route stop at the LIMIT
- Copy route: drop WordPress's GROUP BY when the flat row is the only join, and read posts.ID tie-breaks as the flat post_id (own direction, once) - Post status sync: index (post_status, post_date, post_id) replaces the two-column idx_post_status_date (added / dropped by post-status-sync) - dev36, 235k of 1.3M listings in a price range: main query 1.7 s -> 3 ms - Verify: composer test (704), test:integration (597), phpcs, stan - PR: PR-128
This commit is contained in:
@@ -31,7 +31,7 @@ class PostQueryRouterCopyTest extends TestCase {
|
||||
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( '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_id (post_status, post_date, post_id) ) 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' ) );
|
||||
@@ -58,6 +58,55 @@ class PostQueryRouterCopyTest extends TestCase {
|
||||
$mode->setValue( null, null );
|
||||
}
|
||||
|
||||
/**
|
||||
* The full request WordPress would build for the routed search, after posts_request.
|
||||
*
|
||||
* @param string $extra_join Another JOIN (a tax query's).
|
||||
* @param string $ties ID tie-breaks plugins appended.
|
||||
*/
|
||||
private function request( string $extra_join = '', string $ties = ', {posts}.ID DESC, {posts}.ID DESC' ): string {
|
||||
global $wpdb;
|
||||
$ties = str_replace( '{posts}', $wpdb->posts, $ties );
|
||||
$query = new WP_Query();
|
||||
foreach ( array(
|
||||
'post_type' => 'rc_item',
|
||||
'post_status' => 'publish',
|
||||
'meta_query' => array( array( 'key' => 'rc_price', 'value' => array( 5, 20 ), 'compare' => 'BETWEEN', 'type' => 'NUMERIC' ) ),
|
||||
) as $k => $v ) {
|
||||
$query->set( $k, $v );
|
||||
}
|
||||
$router = new TMDO_Post_Query_Router();
|
||||
$router->pre_get_posts( $query );
|
||||
$request = 'SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts ' . $router->posts_join( $extra_join, $query )
|
||||
. ' WHERE 1=1 ' . $router->posts_where( " AND wp_posts.post_status = 'publish'", $query )
|
||||
. " GROUP BY {$wpdb->posts}.ID ORDER BY " . $router->posts_orderby( 'wp_posts.post_date DESC', $query ) . $ties . ' LIMIT 0, 10';
|
||||
return $router->posts_request( $request, $query );
|
||||
}
|
||||
|
||||
/**
|
||||
* 2.4 PR-128: with GROUP BY or a posts.ID tie-break MariaDB sorts every
|
||||
* match (dev36, 235k of 1.3M listings: 1.7 s); without them it walks
|
||||
* (post_status, post_date, post_id) backwards and stops at the LIMIT (3 ms).
|
||||
*/
|
||||
public function test_copy_route_orders_by_the_flat_date_and_post_id_without_group_by(): void {
|
||||
$sql = $this->request();
|
||||
|
||||
$this->assertStringNotContainsString( 'GROUP BY', $sql, 'one flat row per post: nothing to collapse' );
|
||||
$this->assertStringEndsWith( 'ORDER BY ' . self::ALIAS . '.`post_date` DESC, ' . self::ALIAS . '.`post_id` DESC LIMIT 0, 10', $sql );
|
||||
}
|
||||
|
||||
public function test_copy_route_keeps_each_tie_break_direction(): void {
|
||||
$this->assertStringEndsWith( 'ORDER BY ' . self::ALIAS . '.`post_date` DESC, ' . self::ALIAS . '.`post_id` ASC LIMIT 0, 10', $this->request( '', ', {posts}.ID ASC' ) );
|
||||
$this->assertStringEndsWith( 'ORDER BY ' . self::ALIAS . '.`post_date` DESC LIMIT 0, 10', $this->request( '', '' ) );
|
||||
}
|
||||
|
||||
public function test_copy_route_keeps_group_by_with_another_join(): void {
|
||||
global $wpdb;
|
||||
$sql = $this->request( " LEFT JOIN wp_term_relationships ON ({$wpdb->posts}.ID = wp_term_relationships.object_id)" );
|
||||
|
||||
$this->assertStringContainsString( "GROUP BY {$wpdb->posts}.ID", $sql );
|
||||
}
|
||||
|
||||
/** @return array{join:string, where:string, orderby:string} */
|
||||
private function sql( array $vars ): array {
|
||||
$query = new WP_Query();
|
||||
|
||||
@@ -82,9 +82,34 @@ class PostStatusSyncTest extends TestCase {
|
||||
return $out;
|
||||
}
|
||||
|
||||
private function has_index( string $table ): bool {
|
||||
/** @return array<string, string[]> index name → columns */
|
||||
private function indexes( string $table ): array {
|
||||
global $wpdb;
|
||||
return in_array( 'idx_post_status_date', array_column( (array) $wpdb->get_results( "SHOW INDEX FROM `{$table}`", ARRAY_A ), 'Key_name' ), true );
|
||||
$out = array();
|
||||
foreach ( (array) $wpdb->get_results( "SHOW INDEX FROM `{$table}`", ARRAY_A ) as $row ) {
|
||||
$out[ $row['Key_name'] ][ (int) $row['Seq_in_index'] - 1 ] = $row['Column_name'];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
private function has_index( string $table ): bool {
|
||||
return array( 'post_status', 'post_date', 'post_id' ) === ( $this->indexes( $table )['idx_post_status_date_id'] ?? null );
|
||||
}
|
||||
|
||||
/**
|
||||
* 2.4 PR-128: the date route's ID tie-break is read from post_id, which the
|
||||
* index must end with; the 2.2 two-column index is replaced, not kept beside it.
|
||||
*/
|
||||
public function test_run_replaces_the_two_column_index_of_existing_tables(): void {
|
||||
global $wpdb;
|
||||
$wpdb->query( 'ALTER TABLE `' . self::A . '` ADD COLUMN post_status VARCHAR(20) COLLATE utf8mb4_unicode_520_ci DEFAULT NULL, ADD COLUMN post_date DATETIME DEFAULT NULL, ADD INDEX idx_post_status_date (post_status, post_date)' );
|
||||
$GLOBALS['_wp_options'][ TMDO_Post_Status_Sync::OPTION ] = array( 'ps_a' => 'ready' );
|
||||
|
||||
TMDO_Post_Status_Sync::run();
|
||||
|
||||
$this->assertTrue( $this->has_index( self::A ) );
|
||||
$this->assertArrayNotHasKey( 'idx_post_status_date', $this->indexes( self::A ) );
|
||||
$this->assertSame( 'ready', TMDO_Post_Status_Sync::state( 'ps_a' ) );
|
||||
}
|
||||
|
||||
public function test_run_adds_the_columns_copies_every_row_and_marks_groups_ready(): void {
|
||||
|
||||
Reference in New Issue
Block a user