-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
How Hooked on Facets delivers sub-50ms filters on 100k+ products. This is a public overview; the authoritative engineering decisions live in plan.md.
-
Activator installs
wp_hof_index— a narrow EAV table (one row per object × facet × value). Two hot-path indexes back it:facet_lookup(facet_name, facet_value, object_id)for set-intersect filters andfacet_numeric_range(facet_name, facet_numeric, object_id)for range filters. -
Indexer populates rows on
save_post/set_object_termsand on full rebuilds. Numeric values are pre-cast intofacet_numeric DECIMAL(20,6)so the query path never parses strings. Full rebuilds run as a chunked, self-chaining background job (Action Scheduler preferred,wp_cronfallback). -
QueryHook (the Auto-Hook Engine) intercepts the WP_Query loops it detects,
joins through
wp_hof_index, and substitutes the result IDs back viapost__in. No template surgery. -
Frontend POSTs filter state to
/wp-json/hof/v1/filterand re-renders the results region in place.
The resolver builds an INTERSECT chain of per-facet SELECTs
(MySQL 8.0.31+). Three things make the 7× speedup work:
-
A trailing
object_idcolumn on both indexes, so each leg scan is covering (index-only, no heap fetch). -
A
USE INDEX (facet_numeric_range)hint on range legs, soBETWEENpredicates don't fall back to a post-filtered heap fetch. -
INTERSECTinstead ofUNION ALL+GROUP BY HAVING COUNT(DISTINCT)— native set semantics instead of materialize-then-aggregate.
A version-invalidated result-set cache (keyed by index version + filter state) collapses repeat hits to sub-millisecond; the Indexer bumps the version on every write for O(1) invalidation.
100k products, 500k index rows, 5-facet intersect (Docker MySQL 8):
-
resolve_ids()p95: ~54.5 ms -
resolve()full (IDs + counts) p95: ~63 ms uncached - Full reindex of 100k: ~19.4 s
-
includes/class-hof-*.php— core engine classes (Activator,Indexer,QueryHook,Deactivator), classmap-loaded, WordPress filename convention. -
src/**— everything else under PSR-4HookedOnFacets\: the service container, facet types, REST controllers, admin services, integrations, licensing, CLI.
Performance rules on the filter hot path: no N+1 queries (counts are one grouped
query per facet), no string→number casts at query time (use facet_numeric), and
nothing bypasses the index table — if a facet can't be served from wp_hof_index,
the fix is in the indexer.
hooked on facets · Filtering, finally fun. · GitHub · hookedonfacets.com
Filtering, finally fun.
📖 docs
🧠 concepts
🎛️ facet types
- All Types
- Checkbox
- Radio
- Dropdown
- Range Slider
- Date Range
- Search
- Hierarchy
- Color Swatch
- Swipe Deck
- Spin the Wheel
- Intersection Matrix
- Ask
- Visual DNA
- Toggle
- Saved Bin
- Pagination
🔧 develop
🗺️ project