Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e57dd5bd15 | |||
| b2585459e3 | |||
| d129139c7d | |||
| f06a80eae5 |
@@ -3,7 +3,7 @@
|
||||
* Plugin Name: 2meet Data Optimizer — Hivepress AddOn
|
||||
* Plugin URI: https://2meet.io/2meet-data-optimizer/hivepress
|
||||
* Description: HivePress + 12 擴充整合:13 adapter (core/reviews/bookings/messages/memberships/requests/favorites/statistics/tags/seo/social-links/blocks/marketplace) + 7 HPCT interceptor + 5 query interceptor + admin tab + CLI namespace
|
||||
* Version: 0.3.0
|
||||
* Version: 0.4.0
|
||||
* Requires at least: 6.0
|
||||
* Tested up to: 6.9.4
|
||||
* Requires PHP: 8.1
|
||||
@@ -24,7 +24,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
define( 'TMDO_HIVEPRESS_VERSION', '0.3.0' );
|
||||
define( 'TMDO_HIVEPRESS_VERSION', '0.4.0' );
|
||||
define( 'TMDO_HIVEPRESS_PATH', plugin_dir_path( __FILE__ ) );
|
||||
define( 'TMDO_HIVEPRESS_URL', plugin_dir_url( __FILE__ ) );
|
||||
define( 'TMDO_HIVEPRESS_FILE', __FILE__ );
|
||||
|
||||
@@ -11,6 +11,45 @@ Versioning follows [Semantic Versioning](https://semver.org/).
|
||||
|
||||
---
|
||||
|
||||
## [0.4.0] — 2026-09-27
|
||||
|
||||
Released with 2meet-data-optimizer 1.3.0 (hardening 2.3).
|
||||
|
||||
### Changed
|
||||
|
||||
- `hp_listing_core` declares indexes on `hp_hourly_rate` and `hp_rating_count`
|
||||
(HivePress filters and sorts on both). They are declared through the group's
|
||||
`indexes`, not `searchable`, so the pinned schema hash is unchanged and the
|
||||
table is not rebuilt; core 1.3.0 adds them in place from WP-CLI / WP-Cron.
|
||||
dev36 (902k listings): two indexes in about 4.7 s; routed `rating_count IN`
|
||||
2.1 s → 0.36 s, `rating_count !=` 2.1 s → 0.76 s, hourly rate + price sort
|
||||
2.1 s → 0.65 s. (PR-090)
|
||||
- With a persistent object cache, transient entries now expire: a timeout
|
||||
entry when it records, a value after at most a week (`MAX_CACHE_TTL`;
|
||||
HivePress writes the value before its timeout, and a miss only makes it
|
||||
rebuild). Entries of deleted posts no longer pile up. (PR-099)
|
||||
|
||||
### Upgrading
|
||||
|
||||
- Deploy with core 1.3.0: on an older core the AddOn declares no index at all
|
||||
(core 1.2.0 would run the ALTER inside a web request). (PR-090b)
|
||||
- Redis: entries written by 0.3.0 have no TTL (dev36 had 3.0 M). Give them one
|
||||
with non-blocking commands — never `wp cache flush-group`, which the Redis
|
||||
Object Cache drop-in runs as one server-side script and which blocked Redis
|
||||
on a large group. `NX` leaves entries written by 0.4.0 alone:
|
||||
|
||||
```
|
||||
redis-cli --scan --pattern '<WP_CACHE_KEY_SALT>wp:tmdo_hp_transient:*' \
|
||||
| awk '{printf "EXPIRE %s 604800 NX\r\n", $0}' | redis-cli --pipe
|
||||
```
|
||||
|
||||
dev36: 3,013,352 keys in 45 s, 0 errors, Redis latency max 2 ms. The pattern
|
||||
follows the drop-in's key layout (`<salt><blog prefix>:<group>:<key>`);
|
||||
check it with `redis-cli --scan --pattern '<salt>*tmdo_hp_transient*' | head`
|
||||
first. redis-cli 7 has no `--count` with `--scan`.
|
||||
|
||||
---
|
||||
|
||||
## [0.3.0] — 2026-09-27
|
||||
|
||||
Released with 2meet-data-optimizer 1.2.0 (hardening 2.2).
|
||||
|
||||
@@ -257,7 +257,18 @@ final class TMDO_HivePress_Entity_Groups {
|
||||
'label' => 'Hourly rate',
|
||||
),
|
||||
),
|
||||
array( 'post_types' => array( 'hp_listing' ) )
|
||||
array(
|
||||
'post_types' => array( 'hp_listing' ),
|
||||
// Filtered and sorted on by HivePress search. Declared as indexes
|
||||
// (added in place) rather than `searchable`, which would change
|
||||
// the schema hash and rebuild a table that may hold aeav_only data.
|
||||
// Only with core 1.3.0+ (ddl_allowed()): older cores run the ALTER
|
||||
// inside whatever request comes first, a visitor's included.
|
||||
'indexes' => method_exists( 'TMDO_Schema_Manager', 'ddl_allowed' ) ? array(
|
||||
'hourly_rate' => array( 'hp_hourly_rate' ),
|
||||
'rating_count' => array( 'hp_rating_count' ),
|
||||
) : array(),
|
||||
)
|
||||
);
|
||||
}
|
||||
/**
|
||||
|
||||
@@ -80,6 +80,9 @@ final class TMDO_Hivepress_Transient_Filter {
|
||||
/** Object cache group used instead of wp_options when a persistent cache is present. */
|
||||
public const CACHE_GROUP = 'tmdo_hp_transient';
|
||||
|
||||
/** Longest an object-cache entry lives: a week (HivePress's default is a day). */
|
||||
public const MAX_CACHE_TTL = 604800;
|
||||
|
||||
/**
|
||||
* Register the four metadata filters on `init`. Idempotent — safe to call
|
||||
* multiple times.
|
||||
@@ -267,8 +270,10 @@ final class TMDO_Hivepress_Transient_Filter {
|
||||
* In wp_options both halves of the transient pair are written with
|
||||
* `autoload=no` (they are read on-demand only — never via
|
||||
* wp_load_alloptions() — so eagerly loading them on every page would defeat
|
||||
* the point of the move). HivePress checks the timeout itself, so the
|
||||
* cache entries get no TTL of their own.
|
||||
* the point of the move). In the object cache a timeout entry expires when
|
||||
* it says; the value, written before its timeout, after MAX_CACHE_TTL — a
|
||||
* miss only makes HivePress rebuild it — so entries of deleted posts do
|
||||
* not pile up (2.3 PR-099).
|
||||
*
|
||||
* @param int $post_id Post ID.
|
||||
* @param string $meta_key Original `_transient_*` meta_key.
|
||||
@@ -281,7 +286,8 @@ final class TMDO_Hivepress_Transient_Filter {
|
||||
$option_name = ( $is_timeout ? '_transient_timeout_' : '_transient_' ) . $translated;
|
||||
|
||||
if ( wp_using_ext_object_cache() ) {
|
||||
wp_cache_set( $option_name, $meta_value, self::CACHE_GROUP );
|
||||
$ttl = $is_timeout ? min( self::MAX_CACHE_TTL, max( 1, (int) $meta_value - time() ) ) : self::MAX_CACHE_TTL;
|
||||
wp_cache_set( $option_name, $meta_value, self::CACHE_GROUP, $ttl );
|
||||
return;
|
||||
}
|
||||
update_option( $option_name, $meta_value, false );
|
||||
|
||||
@@ -213,6 +213,26 @@ class HivepressTransientFilterTest extends TestCase {
|
||||
$this->assertSame( array( '99999999' ), WPDO_Hivepress_Transient_Filter::on_read( null, 100, '_transient_timeout_hp_models/cat/v1', true ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Entries expire in the object cache too (2.3 PR-099): the timeout at the
|
||||
* time it records, the value (written first, before its timeout is known)
|
||||
* after at most a week. Orphans of deleted posts no longer pile up.
|
||||
*/
|
||||
public function test_with_an_object_cache_entries_get_a_ttl(): void {
|
||||
$GLOBALS['_wp_using_ext_object_cache'] = true;
|
||||
$key = '_transient_hp_models/cat/v1';
|
||||
$translated = WPDO_Hivepress_Transient_Filter::translate_key( 100, $key );
|
||||
|
||||
WPDO_Hivepress_Transient_Filter::on_update( null, 100, $key, 'val', '' );
|
||||
WPDO_Hivepress_Transient_Filter::on_update( null, 100, '_transient_timeout_hp_models/cat/v1', (string) ( time() + 3600 ), '' );
|
||||
WPDO_Hivepress_Transient_Filter::on_update( null, 101, '_transient_timeout_hp_models/cat/v1', (string) ( time() - 5 ), '' );
|
||||
|
||||
$ttl = $GLOBALS['_wp_cache_ttl'][ WPDO_Hivepress_Transient_Filter::CACHE_GROUP ];
|
||||
$this->assertSame( WPDO_Hivepress_Transient_Filter::MAX_CACHE_TTL, $ttl[ '_transient_' . $translated ] );
|
||||
$this->assertEqualsWithDelta( 3600, $ttl[ '_transient_timeout_' . $translated ], 2 );
|
||||
$this->assertSame( 1, $ttl[ '_transient_timeout_' . WPDO_Hivepress_Transient_Filter::translate_key( 101, $key ) ], 'already expired' );
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
@@ -21,6 +21,14 @@ if ( ! file_exists( $tmdo_core_bootstrap ) ) {
|
||||
. dirname( __DIR__, 3 ) . '/2meet-data-optimizer'
|
||||
);
|
||||
}
|
||||
// Defined before the core bootstrap (which only adds it when missing) so tests can read the TTL.
|
||||
if ( ! function_exists( 'wp_cache_set' ) ) {
|
||||
function wp_cache_set( $key, $value, $group = '', $ttl = 0 ): bool {
|
||||
$GLOBALS['_wp_cache'][ $group ][ $key ] = $value;
|
||||
$GLOBALS['_wp_cache_ttl'][ $group ][ $key ] = (int) $ttl;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
require_once $tmdo_core_bootstrap;
|
||||
|
||||
// External object cache switch for the transient filter tests (Redis on real sites).
|
||||
|
||||
@@ -58,6 +58,21 @@ class HivePressEntityGroupsTest extends TestCase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* HivePress filters and sorts listings by these; they are declared as
|
||||
* group indexes (added in place by core) rather than `searchable`, which
|
||||
* would change the pinned hash and rebuild the table.
|
||||
*/
|
||||
public function test_listing_columns_filtered_and_sorted_on_are_indexed(): void {
|
||||
$this->assertSame(
|
||||
array(
|
||||
'hourly_rate' => array( 'hp_hourly_rate' ),
|
||||
'rating_count' => array( 'hp_rating_count' ),
|
||||
),
|
||||
TMDO_Entity_Registry::get_group_indexes( 'post', 'hp_listing_core' )
|
||||
);
|
||||
}
|
||||
|
||||
public function test_shared_keys_keep_their_current_owner(): void {
|
||||
foreach ( self::SHARED_KEY_OWNERS as $key => $group ) {
|
||||
$this->assertSame( $group, TMDO_Entity_Registry::get_field( 'post', $key )['group'], $key );
|
||||
|
||||
Reference in New Issue
Block a user