65c75dfc8d
- register_group() accepts $args['indexes'] (name => ordered keys); indexes on unknown keys are dropped. Kept outside the field definition, so schema hashes are unchanged. - Schema_Manager::ensure_group_indexes() adds missing cidx_* indexes to the existing flat table in place; the applied definition is remembered in tmdo_entity_indexes so later requests skip SHOW INDEX. - Bench (1M listings, scenario 2): Entity p50 337 ms -> 278 ms with (hp_verified, hp_price), on par with Hot (269 ms). - Verify: core unit 656 / integration 459 green, phpcs clean - PR: PR-027
5.3 KiB
5.3 KiB
規模效能基準:原生 postmeta vs Hot 表 vs Entity 表
量測日期:2026-09-25 分支:hardening/2.0 目標規模:vendor 100,000、listing 1,000,000
如何重現
# DB 帳密沿用整合測試的 TMDO_TEST_DB_* 環境變數;資料庫名稱必須以 _bench 結尾
TMDO_TEST_DB_NAME=wp_wpdo_bench php tests/bench/seed.php --vendors=100000 --listings=1000000 # 約 7.5 分鐘
TMDO_TEST_DB_NAME=wp_wpdo_bench php tests/bench/run.php --iterations=20 # 約 20 分鐘
環境與資料
| 項目 | 值 |
|---|---|
| 資料庫 | MariaDB 10.11.14(本機,InnoDB 預設設定) |
| 主機 | 8 核、31 GB RAM、NVMe |
| 資料 | 100,000 vendor(users + hp_vendor posts)、1,000,000 listing(90% publish) |
| 原生 meta | postmeta 9,549,796 筆(951 MB)、usermeta 600,000 筆 |
| Entity 表 | wpdo_post_hp_listing_core 1,000,000 筆(379 MB) |
| Hot 表 | wpdo_hot_hp_listing 1,000,000 筆(229 MB) |
SQL 的形狀與實際程式一致:原生是 WP_Query 的 meta_query(每個條件一個 INNER JOIN、數值用 CAST、SQL_CALC_FOUND_ROWS),Hot 是 TMDO_Query_Router,Entity 是 TMDO_Post_Query_Router。每個情境 20 次,另有 3 次暖機不計入,參數隨機但可重現(--seed=7)。寫入在交易內執行後 rollback。
20 次取樣下 p95 等於最大值,僅供參考。
結果
| 情境 | 形式 | p50 ms | p95 ms | 結果筆數 | 使用索引 |
|---|---|---|---|---|---|
| 1. 價格區間搜尋(命中約 20%) | 原生 | 14,695 | 14,890 | 180,292 | postmeta:post_id |
| Hot | 558 | 3,605 | 180,329 | h:ALL | |
| Entity | 652 | 3,817 | 180,300 | e:ALL | |
| 2. 價格區間 + verified=1 | 原生 | 17,094 | 17,250 | 54,197 | postmeta:post_id ×2 |
| Hot | 277 | 299 | 54,109 | idx_hp_verified_hp_price(複合) |
|
| Entity | 337 | 340 | 54,180 | e:ALL | |
| 3. 某 vendor 名下的 listing | post_parent(HivePress 實際做法) |
0.19 | 0.25 | 7 | posts:post_parent |
原生 meta hp_vendor |
14,432 | 14,599 | 8 | postmeta:post_id | |
| Hot | 3,405 | 3,651 | 10 | 無可用索引 | |
| Entity | 0.28 | 0.33 | 8 | idx_hp_vendor |
|
| 4. 單筆 listing 讀全部 meta | 原生 | 0.12 | 0.15 | 9 列 | postmeta:post_id |
| Hot / Entity | 0.09 | 0.11–0.14 | 1 列 | 主鍵 | |
| 5. 列表頁預載 20 筆 meta | 原生 | 0.59 | 0.61 | 181 列 | postmeta:post_id |
| Hot / Entity | 0.21–0.22 | 0.23–0.26 | 20 列 | 主鍵 | |
| 6. 單一 vendor 讀使用者 meta | 原生 | 0.11 | 0.14 | 6 列 | usermeta:user_id |
| Entity | 0.08 | 0.11 | 1 列 | 主鍵 | |
| 7. 寫入:更新一筆價格 | 原生 | 0.12 | 0.16 | — | — |
| Entity | 0.10 | 0.13 | — | — | |
| dual_write(兩邊都寫) | 0.22 | 0.26 | — | — |
結論
- 原生 meta_query 在百萬規模下無法使用。 任何以 meta 為條件的搜尋都要 14–17 秒,扁平化是必要的,不是錦上添花。
- 扁平表的價值集中在「搜尋與篩選」。 單筆或批次讀 meta(情境 4–6)三種形式都在 1 ms 內,差距可以忽略,而且實務上還有 object cache。
- 寬範圍搜尋仍有瓶頸(情境 1,p50 約 0.6 秒、p95 約 3.7 秒)。 命中 18 萬筆時,優化器選擇全表掃描;再加上
SQL_CALC_FOUND_ROWS要算總數、ORDER BY post_date要排序,還要 JOIN 回posts過濾狀態。p50 和 p95 差很多,是因為不同的價格區間讓優化器選了不同的執行計畫。 - Hot 表的索引規則有漏洞。
add_covering_indexes()只替 decimal、tinyint,以及名稱以_time結尾的 bigint 建索引,所以hp_vendor沒有索引,情境 3 要 3.4 秒,比 Entity 慢約 12,000 倍。 - Entity 表只有單欄索引。 情境 2 輸給有複合索引的 Hot 表(337 ms 對 277 ms)。
- PR-027 之後:群組可用
register_group( ..., array( 'indexes' => ... ) )宣告複合索引。在 1M 列的 Entity 表加上cidx_verified_price (hp_verified, hp_price)花 8.1 秒;重跑情境 2(--iterations=10),Entity p50 從 337 ms 降到 278 ms(p95 285 ms),Hot 是 269 ms(p95 280 ms),差距在量測誤差內。執行計畫改走e:cidx_verified_price。
- PR-027 之後:群組可用
- dual_write 的寫入成本約為兩倍(0.22 ms 對 0.12 ms),絕對值很小,不是瓶頸。
- Hot 表和 Entity 表儲存了同一份 listing 資料(合計約 608 MB),兩者功能重疊。
優化候選(待使用者確認後排入 backlog)
| # | 候選 | 針對 | 預期效果 |
|---|---|---|---|
| O-1 | Entity 表把 post_status、post_date 一併存進來,並建 (hp_status, post_date) 等複合索引,讓搜尋不必 JOIN 回 posts 排序 |
情境 1、2 | 寬範圍搜尋改為走索引排序,避免 filesort |
| O-2 | Entity 表支援群組層級的複合索引宣告,例如 (hp_verified, hp_price)、(hp_featured, hp_price) |
情境 2 | 追上或超過 Hot 表 |
| O-3 | Hot 表的索引規則補上外鍵型 bigint 欄位(例如 hp_vendor) |
情境 3 | 3.4 秒降到 1 ms 以下 |
| O-4 | 提供總數快取,或讓路由後的查詢可以不跑 SQL_CALC_FOUND_ROWS(MySQL 8 已棄用) |
情境 1、2 | 省去每次搜尋都要算出全部符合列的成本 |
| O-5 | 評估 Hot 表與 Entity 表的整併,避免同一份資料存兩份 | 容量、寫入 | 省下約 229 MB 並減少一次寫入 |