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:
2026-09-27 17:46:54 +08:00
parent 7851f150fa
commit ca2cbdab65
5 changed files with 32 additions and 5 deletions
+3
View File
@@ -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' ) );
+1
View File
@@ -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).
*