L1 記憶體快取 */ private static array $l1_cache = array(); /** @var int L1 快取計數上限(防記憶體爆炸) */ private const L1_MAX_ITEMS = 1000; /** @var int L2 TTL 秒數 */ private const L2_TTL = HOUR_IN_SECONDS; public static function init(): void { // 註冊 L1 清理(避免長命令列任務記憶體膨脹) add_action( 'shutdown', array( self::class, 'clear_l1' ) ); } /** * 產生快取 key */ private static function make_key( string $type, int $id, string $group ): string { return "{$type}:{$id}:{$group}"; } // ───────────────────────────────────────────────────────── // 讀取 // ───────────────────────────────────────────────────────── public static function get_row( string $type, int $id, string $group ) { $key = self::make_key( $type, $id, $group ); // L1 if ( array_key_exists( $key, self::$l1_cache ) ) { return self::$l1_cache[ $key ]; } // L2 $cached = wp_cache_get( $key, TMDO_CACHE_GROUP ); if ( $cached !== false ) { self::set_l1( $key, $cached ); return $cached; } return false; } // ───────────────────────────────────────────────────────── // 寫入 // ───────────────────────────────────────────────────────── public static function set_row( string $type, int $id, string $group, array $data ): void { $key = self::make_key( $type, $id, $group ); self::set_l1( $key, $data ); wp_cache_set( $key, $data, TMDO_CACHE_GROUP, self::L2_TTL ); } private static function set_l1( string $key, $data ): void { // 防 L1 無限膨脹 if ( count( self::$l1_cache ) >= self::L1_MAX_ITEMS ) { // 簡易 FIFO:砍掉最舊的 20% $keep = (int) ( self::L1_MAX_ITEMS * 0.8 ); self::$l1_cache = array_slice( self::$l1_cache, - $keep, null, true ); } self::$l1_cache[ $key ] = $data; } // ───────────────────────────────────────────────────────── // 失效 // ───────────────────────────────────────────────────────── public static function invalidate( string $type, int $id, string $group ): void { $key = self::make_key( $type, $id, $group ); unset( self::$l1_cache[ $key ] ); wp_cache_delete( $key, TMDO_CACHE_GROUP ); } public static function flush_entity( string $type, ?int $id = null ): void { // 若沒給 id → flush 整個 entity type(L1 中所有該 type 的 key) if ( $id === null ) { $prefix = 'uae:' . $type . ':'; foreach ( array_keys( self::$l1_cache ) as $key ) { if ( strpos( $key, $prefix ) === 0 ) { unset( self::$l1_cache[ $key ] ); } } // Object cache 沒法做 prefix flush,用版本號 bump wp_cache_set( 'wpdo_cache_version', time(), TMDO_CACHE_GROUP ); return; } $groups = TMDO_Entity_Registry::get_groups_for_type( $type ); foreach ( $groups as $group ) { self::invalidate( $type, $id, $group ); } } public static function flush_all(): void { self::$l1_cache = array(); // WP Object Cache 無跨群組 flush,改用版本號方式 wp_cache_set( 'wpdo_cache_version', time(), TMDO_CACHE_GROUP ); } public static function clear_l1(): void { self::$l1_cache = array(); } // ───────────────────────────────────────────────────────── // 批次預熱(核心效能優化:解決清單頁 N+1) // ───────────────────────────────────────────────────────── /** * 批次預載一組 entity IDs 的資料至快取 * 用於清單頁渲染前呼叫,避免每個 item 都去查 DB * * @param string $type 實體類型 * @param array $ids entity ID 陣列 * @param string $group 欄位群組 */ public static function warm_batch( string $type, array $ids, string $group ): void { if ( empty( $ids ) ) { return; } // 找出未快取的 IDs $uncached_ids = array_filter( $ids, function ( $id ) use ( $type, $group ) { return self::get_row( $type, (int) $id, $group ) === false; } ); if ( empty( $uncached_ids ) ) { return; } global $wpdb; $adapter = TMDO_Entity_Registry::get_adapter( $type ); if ( ! $adapter ) { return; } $table = TMDO_Schema_Manager::get_table_name( $type, $group ); $id_col = $adapter->get_entity_id_column(); if ( ! TMDO_Schema_Manager::table_exists( $table ) ) { return; } $ids_in = implode( ',', array_map( 'intval', $uncached_ids ) ); $rows = $wpdb->get_results( "SELECT * FROM `{$table}` WHERE `{$id_col}` IN ({$ids_in})", ARRAY_A ); if ( ! is_array( $rows ) ) { return; } // 建立 id → row 索引 $indexed = array(); foreach ( $rows as $row ) { $indexed[ (int) $row[ $id_col ] ] = $row; } // 填入快取(未找到者存空陣列避免重查) foreach ( $uncached_ids as $id ) { $id_int = (int) $id; self::set_row( $type, $id_int, $group, $indexed[ $id_int ] ?? array() ); } } // ───────────────────────────────────────────────────────── // 統計(供後台顯示) // ───────────────────────────────────────────────────────── public static function get_l1_stats(): array { return array( 'count' => count( self::$l1_cache ), 'max' => self::L1_MAX_ITEMS, 'usage_pct' => self::L1_MAX_ITEMS > 0 ? round( count( self::$l1_cache ) / self::L1_MAX_ITEMS * 100, 1 ) : 0, ); } }