perf(2meet-data-optimizer): keep cached routed counts across unrelated post meta writes
- cached counts followed posts' last_changed, which WordPress moves on any post meta write; themes write meta on page views (on dev36 Blocksy saved a dynamic-CSS descriptor for each listing a crawler visited), so the cache was invalidated every few seconds and keyword/filter searches recounted - counts now follow their own last_changed group (tmdo_posts), bumped on clean_post_cache (post saved or deleted; hooked in every mode) and on flat writes through the Hook Bus, plus terms' last_changed; a routed search reads no unmanaged meta - Verify: PostQueryRouterFoundRowsTest (unrelated meta write keeps the count, save or flat write recounts), integration 588, unit 703, phpcs, stan; dev36 front-end - PR: PR-117 (2.3 acceptance)
This commit is contained in:
@@ -261,6 +261,9 @@ class TMDO_Core {
|
||||
$post_router = new TMDO_Post_Query_Router();
|
||||
$post_router->register_hooks();
|
||||
}
|
||||
if ( class_exists( 'TMDO_Post_Query_Router' ) ) {
|
||||
add_action( 'clean_post_cache', array( 'TMDO_Post_Query_Router', 'bump_count_version' ) );
|
||||
}
|
||||
|
||||
// Zone B: Warm cleanup cron handler.
|
||||
add_action( 'wpdo_warm_cleanup', array( $this, 'cleanup_warm_zone' ) );
|
||||
|
||||
@@ -912,6 +912,7 @@ final class TMDO_Hook_Bus {
|
||||
private static function touch_posts_last_changed( string $type ): void {
|
||||
if ( 'post' === $type ) {
|
||||
wp_cache_set_posts_last_changed();
|
||||
TMDO_Post_Query_Router::bump_count_version();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,8 +31,11 @@
|
||||
*
|
||||
* Found rows (O-4): routed searches drop SQL_CALC_FOUND_ROWS (it counts every
|
||||
* match inside the page query); found_posts_query answers from a COUNT of the
|
||||
* same query, kept in the object cache until posts' last_changed moves (core
|
||||
* post saves, and flat writes via TMDO_Hook_Bus).
|
||||
* same query, kept in the object cache until a post is saved or deleted, a
|
||||
* flat write goes through TMDO_Hook_Bus (bump_count_version()) or terms'
|
||||
* last_changed moves. Not posts' last_changed: WordPress moves it on any post
|
||||
* meta write, which themes do on page views (Blocksy's dynamic CSS), and a
|
||||
* routed search reads no unmanaged meta.
|
||||
*
|
||||
* 🔒 v2.9.x → v2.10.x frozen contract: never touches user entity. Routes
|
||||
* only entity_type='post'.
|
||||
@@ -69,6 +72,9 @@ class TMDO_Post_Query_Router {
|
||||
/** Object cache group for found-rows counts. */
|
||||
private const COUNT_CACHE_GROUP = 'tmdo_found_rows';
|
||||
|
||||
/** The last_changed group the cached counts follow (bump_count_version()). */
|
||||
public const COUNT_VERSION_GROUP = 'tmdo_posts';
|
||||
|
||||
/** Seconds a "every post of the type has a flat row" answer is reused. */
|
||||
private const COVERAGE_TTL = 900;
|
||||
|
||||
@@ -436,7 +442,7 @@ class TMDO_Post_Query_Router {
|
||||
// Keyword searches carry wpdb's placeholder escape for "%", which is new
|
||||
// in every request: without it the key would never match again.
|
||||
$stable = method_exists( $wpdb, 'remove_placeholder_escape' ) ? $wpdb->remove_placeholder_escape( $count_sql ) : $count_sql;
|
||||
$key = md5( $stable ) . ':' . wp_cache_get_last_changed( 'posts' );
|
||||
$key = md5( $stable ) . ':' . wp_cache_get_last_changed( self::COUNT_VERSION_GROUP ) . ':' . wp_cache_get_last_changed( 'terms' );
|
||||
$total = wp_cache_get( $key, self::COUNT_CACHE_GROUP );
|
||||
if ( false === $total ) {
|
||||
$total = (int) $wpdb->get_var( $count_sql );
|
||||
@@ -452,6 +458,17 @@ class TMDO_Post_Query_Router {
|
||||
// Helpers
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* A post was saved or deleted, or a managed key written: cached routed
|
||||
* counts are stale. Hooked on clean_post_cache whatever the mode, so a
|
||||
* count cached before the router was last active is not reused.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function bump_count_version(): void {
|
||||
wp_cache_set_last_changed( self::COUNT_VERSION_GROUP );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the router is mode-active (post mode is shadow_read or aeav_only).
|
||||
*
|
||||
|
||||
@@ -128,7 +128,10 @@ class PostQueryRouterFoundRowsTest extends TestCase {
|
||||
$wpdb->query( 'UPDATE `' . self::TABLE . '` SET fr_price = 5 WHERE post_id = 2' );
|
||||
$this->assertSame( '2', $wpdb->get_var( $router->found_posts_query( 'SELECT FOUND_ROWS()', $query ) ), 'cached' );
|
||||
|
||||
wp_cache_set_posts_last_changed();
|
||||
wp_cache_set_posts_last_changed(); // Any post meta write, e.g. a theme's CSS descriptor on a page view.
|
||||
$this->assertSame( '2', $wpdb->get_var( $router->found_posts_query( 'SELECT FOUND_ROWS()', $query ) ), 'still cached' );
|
||||
|
||||
TMDO_Post_Query_Router::bump_count_version(); // A post saved, or a managed key written.
|
||||
$this->assertSame( '1', $wpdb->get_var( $router->found_posts_query( 'SELECT FOUND_ROWS()', $query ) ), 'recounted' );
|
||||
}
|
||||
|
||||
@@ -153,7 +156,7 @@ class PostQueryRouterFoundRowsTest extends TestCase {
|
||||
$this->assertSame( '2', $count( '1' ), 'one cached count serves every exclusion list; 1 does not match' );
|
||||
$this->assertSame( '1', $count( '2' ) );
|
||||
|
||||
wp_cache_set_posts_last_changed();
|
||||
TMDO_Post_Query_Router::bump_count_version();
|
||||
$this->assertSame( '0', $count( '2' ), 'recounted' );
|
||||
}
|
||||
|
||||
@@ -203,6 +206,7 @@ class PostQueryRouterFoundRowsTest extends TestCase {
|
||||
public function test_flat_writes_and_deletes_move_posts_last_changed(): void {
|
||||
$GLOBALS['_wp_current_filter'] = 'update_post_metadata';
|
||||
$before = wp_cache_get_last_changed( 'posts' );
|
||||
$version = wp_cache_get_last_changed( TMDO_Post_Query_Router::COUNT_VERSION_GROUP );
|
||||
WPDO_Hook_Bus::intercept_update( null, 1, 'fr_price', '11', '' );
|
||||
$after_write = wp_cache_get_last_changed( 'posts' );
|
||||
|
||||
@@ -211,6 +215,7 @@ class PostQueryRouterFoundRowsTest extends TestCase {
|
||||
|
||||
$this->assertNotSame( $before, $after_write );
|
||||
$this->assertNotSame( $after_write, wp_cache_get_last_changed( 'posts' ) );
|
||||
$this->assertNotSame( $version, wp_cache_get_last_changed( TMDO_Post_Query_Router::COUNT_VERSION_GROUP ), 'routed counts follow flat writes' );
|
||||
}
|
||||
|
||||
// ── flat-first: a filtered search ordered by a flat column ─────────────
|
||||
|
||||
@@ -434,6 +434,7 @@ if ( ! function_exists( 'wp_mkdir_p' ) ) {
|
||||
|
||||
$GLOBALS['_wp_cache'] = [];
|
||||
if ( ! function_exists( 'wp_cache_get_last_changed' ) ) { function wp_cache_get_last_changed( $group ) { return $GLOBALS['_wp_last_changed'][ $group ] ??= '0'; } }
|
||||
if ( ! function_exists( 'wp_cache_set_last_changed' ) ) { function wp_cache_set_last_changed( $group ) { $GLOBALS['_wp_last_changed'][ $group ] = (string) hrtime( true ); return $GLOBALS['_wp_last_changed'][ $group ]; } }
|
||||
if ( ! function_exists( 'wp_cache_set_posts_last_changed' ) ) { function wp_cache_set_posts_last_changed() { $GLOBALS['_wp_last_changed']['posts'] = (string) hrtime( true ); } }
|
||||
if ( ! function_exists( 'wp_cache_get' ) ) { function wp_cache_get( $key, $group = '' ) { return $GLOBALS['_wp_cache'][ $group ][ $key ] ?? false; } }
|
||||
if ( ! function_exists( 'wp_cache_set' ) ) { function wp_cache_set( $key, $value, $group = '', $ttl = 0 ): bool { $GLOBALS['_wp_cache'][ $group ][ $key ] = $value; return true; } }
|
||||
|
||||
Reference in New Issue
Block a user