diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a7757c..227350e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,8 @@ Versioning follows [Semantic Versioning](https://semver.org/). - meta orderby:HivePress `_sort`(NOT EXISTS + EXISTS)形狀、具名 clause、 `meta_key` + `meta_value_num`。(PR-062) - routed 搜尋維持 WordPress 的 split query(ID-first)。(PR-062b) + - 依扁平欄位排序且有篩選的搜尋改由扁平表驅動(`STRAIGHT_JOIN`,posts.ID tie-break 改讀等值的 + `post_id`),不再以 temporary table 排序;filter `tmdo_router_flat_first` 可關閉。(PR-062c) - `composer phpcs` 改用 `phpcs.xml`(與 CI 相同)。 ### Fixed @@ -48,6 +50,7 @@ Versioning follows [Semantic Versioning](https://semver.org/). `text_overflow`。(PR-055) - `bridge-restore-native` 覆蓋較新的原生值(WC 庫存)、替每個 user 捏造預設值列。(PR-058) - Cold / Hot 表不存在時,`save_post`、`loop_start`、刪除文章仍查詢它們(DB error)。(PR-054、PR-054b) +- 刪除 post / user / term / comment 後,扁平表留下全 NULL 的孤兒列(PR-057 的 NULL 同步會重建已刪的列)。(PR-057b) - Router:Q05(wp_core 與另一 group 的 key 並用)`Unknown column`;OR / 多 post type / 無 type 數值比較 / 具名 orderby 的結果與原生不同。(PR-060〜062) diff --git a/docs/BENCHMARK-SCALE.md b/docs/BENCHMARK-SCALE.md index 3de0d5a..dd0e712 100644 --- a/docs/BENCHMARK-SCALE.md +++ b/docs/BENCHMARK-SCALE.md @@ -71,16 +71,19 @@ SQL 的形狀與實際程式一致:原生是 WP_Query 的 meta_query(每個 | URL | 原生 ms | 1.0.x ms | 2.1 routed ms | ids 與原生相同 | |---|---:|---:|---:|---| -| listing 價格區間 | 606 | 496 | **311** | 是(found 差 1 為扁平漂移) | -| listing 價格區間 + `_sort=price__asc` | 949 | 1,062 | **428** | 是 | -| listing 時薪區間 + `_sort=hourly_rate__desc` | 843 | — | 873 | 是 | -| listing `_sort=rating__desc` | 1,717 | 1,720 | **870** | 是 | -| listing 關鍵字 + 價格區間 | 748 | — | 743 | 是 | -| vendor 時薪區間 | 102 | — | 86 | 是(found 差 21 為扁平漂移) | -| WC 商店價格篩選 / 價格排序 | 90 / 107 | 88–94 | 102 / 118 | 是(WC 用 lookup 表,不經 router) | +| listing 價格區間 | 614 | 496 | **323** | 是 | +| listing 價格區間 + `_sort=price__asc` | 975 | 1,062 | **435** | 是 | +| listing 時薪區間 + `_sort=hourly_rate__desc` | 850 | — | **367** | 是 | +| listing `_sort=rating__desc` | 1,697 | 1,720 | **866** | 是 | +| listing 關鍵字 + 價格區間 | 754 | — | 764 | 是 | +| vendor 時薪區間 | 102 | — | 97 | 是 | +| WC 商店價格篩選 / 價格排序 | 94 / 111 | 88–94 | 109 / 88 | 是(WC 用 lookup 表,不經 router) | + +(R-2.1 驗收,扁平表已從原生回填、含 PR-062c;兩輪量測見 `notes/tmdo-acceptance-2.1-2026-09-26.md`。) - HivePress 搜尋仍帶 tax_query(排除一個 term),WordPress 因此加上 `GROUP BY posts.ID`,排序需要 temporary + filesort;這是目前 311–428 ms 的主要成本,尚未達到 R-2.1 的 300 ms 目標。 - PR-062b 之前,routed 搜尋因 `posts_request` 改寫而失去 WordPress 的 split query,改撈整列 `posts.*`:同一條 SQL 撈 `*` 要 590 ms,只撈 ID 為 312 ms。 +- PR-062c 之前,依扁平欄位排序的 routed 查詢由 `wp_posts`(type / status 索引,約 56k 列)驅動、逐筆查扁平表再以 temporary table 排序;拿掉 GROUP BY 或只換 tie-break 都無效(約 300 ms),改由扁平表驅動並把 tie-break 改讀 `post_id` 後只剩單表 filesort:`meta_value_num` 263→147 ms、具名排序 250→142 ms、HP 時薪篩選 + 排序 300→36 ms(只計 SQL)。依日期排序的搜尋由扁平表驅動反而變慢(74→137 ms),所以 flat-first 只用在扁平欄位排序。 - 量測 gotcha:symlink 切換(`plugins/` → `plugins-dev`)後,PHP-FPM 的 opcache(`revalidate_path=0`)仍執行舊目標的程式,WP-CLI 不受影響;前台數字要先確認實際載入的檔案。 **WP_Query**(`tests/live/tools/qbench.php`,`fields=ids`,20 筆一頁):價格區間 296→76 ms、價格 + verified 360→53 ms、rating ≥ 4.5 248→33 ms、rate + `_thumbnail_id` EXISTS 24→9 ms;OR、多 post type、無 type 數值比較等維持原生,結果與原生相同。 diff --git a/docs/adr-004-search-index-mode-for-hp-wc.md b/docs/adr-004-search-index-mode-for-hp-wc.md index 1137b62..3aaad77 100644 --- a/docs/adr-004-search-index-mode-for-hp-wc.md +++ b/docs/adr-004-search-index-mode-for-hp-wc.md @@ -31,6 +31,7 @@ - 扁平表在 `added_ / updated_ / deleted_{type}_meta` action(priority 1)裡**從原生重新同步**:直接 SQL 讀原生第一筆 row(`ORDER BY meta_id LIMIT 1`,不經 filter),扁平值 = `to_db( maybe_unserialize( v ) )`,原生沒有 row 時設為 NULL(`TMDO_Hook_Bus::sync_from_native()`)。 - `delete_metadata( …, $delete_all )`:`deleted_` action 拿到的 object id 是 0,所以在 `delete_{type}_meta` 先記下 object ids。 - `update_metadata_by_mid()` 改 key:新舊兩個 key 都重新同步。 +- 原生沒有值時只把既有列的欄位設為 NULL,**不新增列**:刪除實體時 adapter 先刪扁平列,WordPress 才逐筆刪 meta。 - **aeav_only** 保留原本的 pre-filter short-circuit(非 HP / WC 站仍可使用)。 ### 讀取:shadow_read = 讀原生、抽樣比對扁平 @@ -71,6 +72,7 @@ - **具名 clause** 作為唯一的 orderby。 - **`meta_key` + `meta_value_num`**(字串欄位用 `meta_value`),加上 `col IS NOT NULL`(原生是 INNER JOIN)。 - 欄位排序不等於 `CAST( meta_value AS type )` 時退回原生。 +- **flat-first**:依扁平欄位排序、且 routed 條件已要求扁平列存在(同一 group、非 NOT EXISTS)、沒有 `post__in` / `author` 等高選擇性條件時,改寫成 `SELECT STRAIGHT_JOIN … FROM flat INNER JOIN wp_posts`,ORDER BY 的 `wp_posts.ID` 改讀等值的 `post_id`,排序只在一張表完成。依日期排序的搜尋不套用(由 posts 驅動較快)。filter `tmdo_router_flat_first` 可關閉。 - **維持 WordPress 的 split query**:`posts_request` 移除 `SQL_CALC_FOUND_ROWS`(總數改由快取的 COUNT 回答)會讓 WordPress 以為 request 被改過而撈整列。`split_the_query` 對 routed 查詢恢復 ID-first,`posts_request_ids` 再移除一次 `SQL_CALC_FOUND_ROWS`。 HivePress AddOn 0.2.0 在 post router 啟用時不再綁定舊的 hot-zone `optimize_query` / `optimize_search`。 @@ -90,7 +92,7 @@ HivePress AddOn 0.2.0 在 post router 啟用時不再綁定舊的 hot-zone `opti - **漂移**:繞過 meta API 的寫入(直接 SQL、Hook Bus 關閉期間的匯入)會讓扁平表過期,Router 會回傳過期的篩選結果。2.2 的 `drift-check` 與重新啟用時的增量 backfill(E01 / E02)處理;在那之前,大量匯入後要重新 backfill。 - **decimal 欄位搭配 NUMERIC / DECIMAL**:原生先 CAST 成整數(`DECIMAL` 預設 (10,0)),扁平表精確比較,因此帶小數的值在區間邊界上可能不同。dev36 資料沒有這種值。 - **text 欄位超過 255 字元**存 NULL,這類值的篩選結果會與原生不同(有 `text_overflow` 記錄)。 -- HivePress 搜尋 p50 目前最快 311–428 ms(原生 606–949 ms),尚未達到 R-2.1 的 300 ms 目標。剩餘成本主要是 WordPress 因 tax_query 加的 `GROUP BY posts.ID` 與 filesort。 +- HivePress 搜尋 p50 為 321–866 ms(原生 600–1,713 ms),尚未達到 R-2.1 的 300 ms 目標(驗收:`notes/tmdo-acceptance-2.1-2026-09-26.md`)。價格篩選頁的 SQL 只剩約 75 ms、其餘為頁面渲染;純排序(如 `rating__desc`)必須 LEFT JOIN 保留沒有值的文章,無法 flat-first。 ## 不在範圍內 diff --git a/readme.txt b/readme.txt index 0c0b3f6..2ef9863 100644 --- a/readme.txt +++ b/readme.txt @@ -114,7 +114,8 @@ The Classifier tab analyzes your existing `wp_postmeta` data (access frequency, * dual_write / shadow_read: flat values follow native after every write (added_/updated_/deleted_ meta actions, delete_all, key change by meta_id) * Arrays in text fields are stored serialized, never "Array"; `wp tmdo repair-array-literals` * `wp tmdo bridge-restore-native`: overwrite in aeav_only, only-missing otherwise; no invented default rows -* Post query router: routes only when the result equals native (OR, several post types, untyped numeric compares stay native), LIKE wildcards, one join per group, no GROUP BY, HivePress `_sort` / named / meta_value_num ordering, WordPress split query kept +* Post query router: routes only when the result equals native (OR, several post types, untyped numeric compares stay native), LIKE wildcards, one join per group, no GROUP BY, HivePress `_sort` / named / meta_value_num ordering, WordPress split query kept, flat-sorted searches driven from the flat table (filter `tmdo_router_flat_first`) +* Deleting an entity no longer leaves an all-NULL flat row behind * Zone tables that were never created are never queried = 1.0.0 =