fix(2meet-data-optimizer-hivepress-addon): with an object cache, keep HivePress transients out of wp_options entirely
Tests / PHP Lint (pull_request) Successful in 10s
Tests / Unit Tests (pull_request) Successful in 1m2s
Tests / Integration Tests (pull_request) Successful in 1m53s

- On a cache miss the filter fell back to get_option(): with the values now written to the cache, the option is absent, so WordPress added its name to `notoptions` for good (dev36: +1,000 entries for 1,000 posts, one array loaded on every miss); a hit kept the old row in the options cache group with no expiry, which slowed the selective Redis flush (8.9 s, BUSY). The cache is now the only store for reads and deletes; old rows wait for cleanup-hp-transients --options. Without an object cache nothing changes.
- Verify: HivepressTransientFilterTest (2 RED before); dev36 notoptions 210 → 210 over 1,000 posts (was 128 → 1,128)
- PR: PR-089c
This commit is contained in:
2026-09-27 10:32:12 +08:00
parent c6faaebbc1
commit 788ddccfb3
3 changed files with 21 additions and 11 deletions
+6 -4
View File
@@ -20,10 +20,12 @@ Released with 2meet-data-optimizer 1.2.0 (hardening 2.2).
- With a persistent object cache (`wp_using_ext_object_cache()`), the - With a persistent object cache (`wp_using_ext_object_cache()`), the
transient filter keeps HivePress's per-post transient pairs in the transient filter keeps HivePress's per-post transient pairs in the
`tmdo_hp_transient` cache group instead of `wp_options`, where they piled `tmdo_hp_transient` cache group instead of `wp_options`, where they piled
up (1.15 M pairs on dev36). Reads fall back to `wp_options` for values up (1.15 M pairs on dev36). The cache is then the only store: rows written
written before; deletes clear both. Without an object cache nothing changes. before are neither read nor deleted (HivePress rebuilds those caches once),
After upgrading, remove the rows left behind with core's because a `get_option()` miss adds the name to WordPress's `notoptions` for
`wp tmdo cleanup-hp-transients --options --confirm`. good and a hit keeps the old row in the `options` cache group with no expiry.
Without an object cache nothing changes. After upgrading, remove the rows
left behind with core's `wp tmdo cleanup-hp-transients --options --confirm`.
### Fixed ### Fixed
@@ -167,9 +167,14 @@ final class TMDO_Hivepress_Transient_Filter {
// HivePress cache layer reads the timeout *first*, then decides whether // HivePress cache layer reads the timeout *first*, then decides whether
// to read the value. We must preserve raw stored values until HivePress // to read the value. We must preserve raw stored values until HivePress
// itself orders deletion. // itself orders deletion.
$value = wp_using_ext_object_cache() ? wp_cache_get( $option_name, self::CACHE_GROUP ) : false; if ( wp_using_ext_object_cache() ) {
if ( false === $value ) { // The object cache is the only store. Rows left in wp_options from
// wp_options: where values went before an object cache was used. // before are not read (`wp tmdo cleanup-hp-transients --options`
// removes them): a get_option() miss would add the name to
// `notoptions` for good, as later writes never reach wp_options.
$value = wp_cache_get( $option_name, self::CACHE_GROUP );
$value = false === $value ? null : $value;
} else {
$value = get_option( $option_name, null ); $value = get_option( $option_name, null );
} }
@@ -248,6 +253,7 @@ final class TMDO_Hivepress_Transient_Filter {
if ( wp_using_ext_object_cache() ) { if ( wp_using_ext_object_cache() ) {
wp_cache_delete( $option_name, self::CACHE_GROUP ); wp_cache_delete( $option_name, self::CACHE_GROUP );
return true;
} }
delete_option( $option_name ); delete_option( $option_name );
return true; return true;
@@ -213,16 +213,18 @@ class HivepressTransientFilterTest extends TestCase {
$this->assertSame( array( '99999999' ), WPDO_Hivepress_Transient_Filter::on_read( null, 100, '_transient_timeout_hp_models/cat/v1', true ) ); $this->assertSame( array( '99999999' ), WPDO_Hivepress_Transient_Filter::on_read( null, 100, '_transient_timeout_hp_models/cat/v1', true ) );
} }
public function test_with_an_object_cache_a_value_left_in_wp_options_still_reads(): void { public function test_with_an_object_cache_a_value_left_in_wp_options_is_not_read(): void {
// A miss must not fall back to get_option(): the option is absent after
// cleanup and WordPress would add its name to `notoptions` for good.
$key = '_transient_hp_models/cat/v1'; $key = '_transient_hp_models/cat/v1';
$translated = WPDO_Hivepress_Transient_Filter::translate_key( 100, $key ); $translated = WPDO_Hivepress_Transient_Filter::translate_key( 100, $key );
update_option( '_transient_' . $translated, 'from-before' ); update_option( '_transient_' . $translated, 'from-before' );
$GLOBALS['_wp_using_ext_object_cache'] = true; $GLOBALS['_wp_using_ext_object_cache'] = true;
$this->assertSame( array( 'from-before' ), WPDO_Hivepress_Transient_Filter::on_read( null, 100, $key, true ) ); $this->assertNull( WPDO_Hivepress_Transient_Filter::on_read( null, 100, $key, true ) );
} }
public function test_with_an_object_cache_delete_clears_both_stores(): void { public function test_with_an_object_cache_delete_leaves_wp_options_alone(): void {
$key = '_transient_hp_models/cat/v1'; $key = '_transient_hp_models/cat/v1';
$translated = WPDO_Hivepress_Transient_Filter::translate_key( 100, $key ); $translated = WPDO_Hivepress_Transient_Filter::translate_key( 100, $key );
update_option( '_transient_' . $translated, 'from-before' ); update_option( '_transient_' . $translated, 'from-before' );
@@ -232,7 +234,7 @@ class HivepressTransientFilterTest extends TestCase {
WPDO_Hivepress_Transient_Filter::on_delete( null, 100, $key, '', false ); WPDO_Hivepress_Transient_Filter::on_delete( null, 100, $key, '', false );
$this->assertNull( WPDO_Hivepress_Transient_Filter::on_read( null, 100, $key, true ) ); $this->assertNull( WPDO_Hivepress_Transient_Filter::on_read( null, 100, $key, true ) );
$this->assertFalse( get_option( '_transient_' . $translated ) ); $this->assertSame( 'from-before', get_option( '_transient_' . $translated ), 'left for cleanup-hp-transients --options' );
} }
// ── count/purge legacy ────────────────────────────────────────────────── // ── count/purge legacy ──────────────────────────────────────────────────