chore: initial snapshot of 2meet-data-optimizer v0.1.0

Baseline before backporting wp-data-optimizer v3.0.1-v3.4.6.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TbG1keQQ7XBa7qMQY16KCY
This commit is contained in:
2026-07-31 05:06:36 +08:00
commit d36bb954d1
206 changed files with 66538 additions and 0 deletions
@@ -0,0 +1,329 @@
<?php
/**
* TMDO_Entity_Health — Entity Bridge 健康狀態聚合器
*
* 收集每個 entity type (user/term/comment) 的即時健康快照:
* - 模式與已停留天數
* - 每個群組的覆蓋率(flat table rows vs EAV rows
* - Shadow diff 計數
* - 遷移進度(斷點位置、已搬移筆數)
* - Auto-promote 資格評估
* - 操作建議
*
* @package WP_Data_Optimizer
* @since 2.6.6
*/
// phpcs:disable Squiz.Commenting,Generic.Commenting,WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber,Generic.CodeAnalysis.UnusedFunctionParameter,Generic.CodeAnalysis.EmptyStatement,Squiz.PHP.DisallowMultipleAssignments,Squiz.PHP.DisallowSizeFunctionsInLoops,WordPress.WP.I18n.MissingTranslatorsComment,WordPress.PHP.NoSilencedErrors,Universal.NamingConventions.NoReservedKeywordParameterNames,WordPress.PHP.YodaConditions -- inherits engine coding standard.
defined( 'ABSPATH' ) || exit;
final class TMDO_Entity_Health {
/** Entity types managed by this class (post is handled by Feature_Flags FSM). */
public const MANAGED_TYPES = array( 'user', 'term', 'comment' );
/**
* 取得全部 entity 的健康快照(批次,供 REST polling 用)。
*
* @return array<string, array>
*/
public static function get_all(): array {
$result = array();
foreach ( self::MANAGED_TYPES as $type ) {
$result[ $type ] = self::get_one( $type );
}
return $result;
}
/**
* 取得單一 entity 的健康快照。
*/
public static function get_one( string $entity_type ): array {
$mode = class_exists( 'TMDO_Mode_Manager' ) ? TMDO_Mode_Manager::get( $entity_type ) : 'disabled';
$entered_all = get_option( TMDO_Mode_Manager::OPT_ENTERED_AT, array() );
$entered_at = isset( $entered_all[ $entity_type ] ) ? (int) $entered_all[ $entity_type ] : 0;
$mode_days = $entered_at > 0 ? (int) floor( ( time() - $entered_at ) / DAY_IN_SECONDS ) : 0;
$groups = self::get_groups_health( $entity_type );
$shadow_diffs = 0;
if ( class_exists( 'TMDO_Shadow_Diff_Logger' ) ) {
$diff_counts = TMDO_Shadow_Diff_Logger::count_by_entity();
$shadow_diffs = (int) ( $diff_counts[ $entity_type ] ?? 0 );
}
$auto_promote = self::get_auto_promote_status( $entity_type, $mode, $entered_at, $shadow_diffs );
$backfill_active = self::is_backfill_active( $entity_type );
$native_counts = self::get_native_counts( $entity_type );
return array(
'entity_type' => $entity_type,
'mode' => $mode,
'entered_at' => $entered_at,
'mode_days' => $mode_days,
'groups' => $groups,
'shadow_diffs' => $shadow_diffs,
'auto_promote' => $auto_promote,
'backfill_active' => $backfill_active,
'native_entity_count' => $native_counts['entity_count'],
'native_meta_count' => $native_counts['meta_count'],
'native_entity_table' => $native_counts['entity_table'],
'native_meta_table' => $native_counts['meta_table'],
'recommendation' => self::get_recommendation( $entity_type, $mode, $groups, $shadow_diffs ),
'next_mode' => self::get_next_mode( $mode ),
'prev_mode' => self::get_prev_mode( $mode ),
);
}
/**
* 取得 native EAV 表的總筆數(entity 本體 + meta)。
*/
private static function get_native_counts( string $entity_type ): array {
global $wpdb;
$adapter = class_exists( 'TMDO_Entity_Registry' ) ? TMDO_Entity_Registry::get_adapter( $entity_type ) : null;
if ( ! $adapter ) {
return array(
'entity_table' => '',
'meta_table' => '',
'entity_count' => 0,
'meta_count' => 0,
);
}
$meta_table = $adapter->get_native_meta_table();
$entity_table = self::get_native_entity_table( $entity_type );
$entity_count = $entity_table ? (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$entity_table}`" ) : 0;
$meta_count = $meta_table ? (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$meta_table}`" ) : 0;
return array(
'entity_table' => $entity_table,
'meta_table' => $meta_table,
'entity_count' => $entity_count,
'meta_count' => $meta_count,
);
}
/**
* 取得 entity 本體資料表名稱(非 meta 表)。
*/
private static function get_native_entity_table( string $entity_type ): string {
global $wpdb;
$map = array(
'user' => $wpdb->users,
'term' => $wpdb->terms,
'comment' => $wpdb->comments,
'post' => $wpdb->posts,
);
return $map[ $entity_type ] ?? '';
}
// ─────────────────────────────────────────────────────────
// 群組覆蓋率
// ─────────────────────────────────────────────────────────
private static function get_groups_health( string $entity_type ): array {
if ( ! class_exists( 'TMDO_Entity_Registry' ) || ! class_exists( 'TMDO_Schema_Manager' ) ) {
return array();
}
global $wpdb;
$groups = TMDO_Entity_Registry::get_groups_for_type( $entity_type );
$adapter = TMDO_Entity_Registry::get_adapter( $entity_type );
$result = array();
foreach ( $groups as $group_name ) {
$fields = TMDO_Entity_Registry::get_group_fields( $entity_type, $group_name );
$table = TMDO_Schema_Manager::get_table_name( $entity_type, $group_name );
$table_exists = TMDO_Schema_Manager::table_exists( $table );
$flat_rows = 0;
$eav_rows = 0;
if ( $table_exists && $adapter ) {
$flat_rows = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$table}`" );
$managed_keys = array_column( $fields, 'key' );
if ( ! empty( $managed_keys ) ) {
$meta_table = $adapter->get_native_meta_table();
$id_col = $adapter->get_entity_id_column();
$phs = implode( ',', array_fill( 0, count( $managed_keys ), '%s' ) );
$eav_rows = (int) $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(DISTINCT `{$id_col}`) FROM `{$meta_table}` WHERE `meta_key` IN ({$phs})",
...$managed_keys
)
);
}
}
$coverage_pct = 0.0;
if ( $eav_rows > 0 ) {
$coverage_pct = round( min( $flat_rows / $eav_rows, 1.0 ) * 100, 1 );
} elseif ( $flat_rows > 0 ) {
$coverage_pct = 100.0;
}
$migration_info = self::get_migration_status( $entity_type, $group_name );
$result[] = array(
'name' => $group_name,
'fields_count' => count( $fields ),
'table' => $table,
'table_exists' => $table_exists,
'flat_rows' => $flat_rows,
'eav_rows' => $eav_rows,
'coverage_pct' => $coverage_pct,
'migration_status' => $migration_info['status'],
'last_id' => $migration_info['last_id'],
'total_migrated' => $migration_info['total_migrated'],
'started_at' => $migration_info['started_at'],
'completed_at' => $migration_info['completed_at'],
);
}
return $result;
}
private static function get_migration_status( string $entity_type, string $group_name ): array {
global $wpdb;
$table = $wpdb->prefix . TMDO_TABLE_PREFIX . 'migration_status';
$row = $wpdb->get_row(
$wpdb->prepare(
"SELECT status, last_id, total_migrated, started_at, completed_at FROM `{$table}` WHERE entity_type = %s AND group_name = %s",
$entity_type,
$group_name
),
ARRAY_A
);
return array(
'status' => $row['status'] ?? 'not_started',
'last_id' => (int) ( $row['last_id'] ?? 0 ),
'total_migrated' => (int) ( $row['total_migrated'] ?? 0 ),
'started_at' => $row['started_at'] ?? null,
'completed_at' => $row['completed_at'] ?? null,
);
}
// ─────────────────────────────────────────────────────────
// Auto-promote 資格評估
// ─────────────────────────────────────────────────────────
private static function get_auto_promote_status(
string $entity_type,
string $mode,
int $entered_at,
int $shadow_diffs
): array {
$enabled = (bool) get_option( TMDO_Auto_Promoter::OPT_ENABLED, false );
$min_days = (int) get_option( TMDO_Auto_Promoter::OPT_MIN_DAYS, TMDO_Auto_Promoter::DEFAULT_MIN_DAYS );
$days_elapsed = $entered_at > 0 ? (int) floor( ( time() - $entered_at ) / DAY_IN_SECONDS ) : 0;
if ( $mode !== TMDO_Mode_Manager::MODE_SHADOW_READ ) {
return array(
'enabled' => $enabled,
'min_days' => $min_days,
'eligible' => false,
'reason' => 'not_shadow_read',
);
}
if ( $days_elapsed < $min_days ) {
return array(
'enabled' => $enabled,
'min_days' => $min_days,
'days_elapsed' => $days_elapsed,
'eligible' => false,
'reason' => "need_{$min_days}_days",
);
}
if ( $shadow_diffs > 0 ) {
return array(
'enabled' => $enabled,
'min_days' => $min_days,
'days_elapsed' => $days_elapsed,
'eligible' => false,
'reason' => "has_{$shadow_diffs}_diffs",
);
}
return array(
'enabled' => $enabled,
'min_days' => $min_days,
'days_elapsed' => $days_elapsed,
'eligible' => true,
'reason' => 'ready',
);
}
// ─────────────────────────────────────────────────────────
// 建議與模式轉換
// ─────────────────────────────────────────────────────────
private static function get_recommendation(
string $entity_type,
string $mode,
array $groups,
int $shadow_diffs
): string {
switch ( $mode ) {
case TMDO_Mode_Manager::MODE_DISABLED:
return '啟用 Hook Bus 並切換到 dual_write,讓系統開始對 flat table 雙寫。';
case TMDO_Mode_Manager::MODE_DUAL_WRITE:
foreach ( $groups as $g ) {
if ( $g['coverage_pct'] < 99.0 ) {
return '執行 Backfill 遷移歷史資料,待所有群組覆蓋率達到 100% 後再升級到 shadow_read。';
}
}
return '所有群組覆蓋率已達 100%,可以升級到 shadow_read 進行驗證期觀察。';
case TMDO_Mode_Manager::MODE_SHADOW_READ:
if ( $shadow_diffs > 0 ) {
return "發現 {$shadow_diffs} 筆 shadow diff,請先調查差異原因(Settings → Shadow Diffs)後再考慮升級。";
}
$min_days = (int) get_option( TMDO_Auto_Promoter::OPT_MIN_DAYS, TMDO_Auto_Promoter::DEFAULT_MIN_DAYS );
$entered_all = get_option( TMDO_Mode_Manager::OPT_ENTERED_AT, array() );
$entered_at = isset( $entered_all[ $entity_type ] ) ? (int) $entered_all[ $entity_type ] : 0;
$days = $entered_at > 0 ? (int) floor( ( time() - $entered_at ) / DAY_IN_SECONDS ) : 0;
if ( $days < $min_days ) {
return "繼續觀察中(已 {$days}/{$min_days} 天)。無 diff 且穩定後可升級到 aeav_only。";
}
return '已達穩定期,可以升級到 aeav_only(最終生產模式)。';
case TMDO_Mode_Manager::MODE_AEAV_ONLY:
return '遷移完成。所有讀寫均走 flat table,效能最佳。';
default:
return '';
}
}
private static function get_next_mode( string $mode ): ?string {
$idx = array_search( $mode, TMDO_Mode_Manager::ALL_MODES, true );
if ( $idx === false || $idx >= count( TMDO_Mode_Manager::ALL_MODES ) - 1 ) {
return null;
}
return TMDO_Mode_Manager::ALL_MODES[ $idx + 1 ];
}
private static function get_prev_mode( string $mode ): ?string {
$idx = array_search( $mode, TMDO_Mode_Manager::ALL_MODES, true );
if ( $idx === false || $idx <= 0 ) {
return null;
}
return TMDO_Mode_Manager::ALL_MODES[ $idx - 1 ];
}
private static function is_backfill_active( string $entity_type ): bool {
return (bool) wp_next_scheduled( 'wpdo_entity_backfill_batch', array( $entity_type ) );
}
}