parkktech / fastmagento
FastMagento makes OpenSearch the serving layer for Magento 2 — products, category pages, search, and layered navigation are served from OpenSearch instead of MySQL, with MySQL kept as the source of truth. Drop-in, base-Magento-only (no Hyvä/Breeze required), cache/Varnish-safe, multi-store, and self
Package info
github.com/parkktech/FastMagento
Type:magento2-module
pkg:composer/parkktech/fastmagento
Requires
- php: >=8.1
- magento/framework: >=103.0.0
This package is auto-updated.
Last update: 2026-07-24 18:18:00 UTC
README
OpenSearch Serving Layer & Large-Catalog Toolkit for Magento 2
Millisecond pages. Real-time index. Search so good it's almost unfair.
🐌➡️🚀 Magento 2 is powerful, extensible, and — on a big catalog — perfectly happy to run 20,000 SQL queries to render a homepage and loop your inventory tables thirty times just because you dared to add something to the cart. FastMagento is what happened when we got tired of watching the spinner.
What it is. FastMagento makes OpenSearch the primary serving layer for the Magento 2
storefront. Product data, prices, stock, the category tree, PDP, search and layered navigation are
served from OpenSearch instead of Magento's EAV/ORM — while MySQL stays the source of truth
and every change is pushed into OpenSearch in real time. Reads stay transparent to third-party
code (a real Magento\Catalog\Model\Product / Category object is hydrated from the index), it
runs on base Magento only, and it is Full-Page-Cache / Varnish safe.
🚀 Quick install
composer require parkktech/fastmagento
bin/magento module:enable ParkkTech_FastMagento
bin/magento setup:upgrade
bin/magento setup:di:compile # production mode only
bin/magento cache:flush
bin/magento indexer:reindex fastmagento_product fastmagento_category
Requirements: Magento 2.4.6+, PHP 8.1+, and a configured OpenSearch/Elasticsearch engine. Full options (VCS install before Packagist, manual install, per-indexer notes) → Installation.
Why it exists (the honest origin story)
It started as one simple goal: make Magento faster. Then we tried to run a real store on it — a lingerie catalogue with 50,000+ colour and colour-combination options and configurable products with 7,500+ variations each — and Magento, bless it, fell over. There had to be a way to serve that data fast and efficiently. This extension is that way.
So a "make it faster" project quietly became "make Magento handle huge catalogs", and along the way it grew into a set of standalone features — each one solving a real large-catalog pain point on its own (see the feature sections below). Adopt the whole serving layer, or just the one piece you came here for.
Checkout is now lightning fast. Native Magento loops the inventory tables ~30 times just to add a product to the cart — then loops them again when the cart page loads. FastMagento strips those redundancies and offloads the heavy database work to OpenSearch, which indexes updates instantly and serves even very complex configurable pages in milliseconds. At that point the only real overhead left is Magento bootstrapping itself.
🔗 See it in action: www.diyoffroad.com — a live storefront running this serving layer.
🎬 See the magic (live demos)
Watch it happen — no page reloads, no spinners, on a catalog this size. (GIFs recorded from the live storefront; drop-in replacements at the paths below.)
💡 GIF paths: docs/img/demo-autocomplete.gif, docs/img/demo-instant-serp.gif,
docs/img/demo-shop-by.gif. For crisper, smaller files GitHub also autoplays looping
muted <video> (MP4/WebM) — swap the <img> tags if preferred.
🎸 And here's the kicker
All of this is pure backend performance — no Hyvä, no Swissup Breeze, no theme surgery required. FastMagento makes Magento serve data in milliseconds on the stock Luma frontend. Now pair it with a fast frontend theme like Hyvä or Swissup Breeze, and you've got a genuine banger of a site — lightning back end and front end. They optimize the paint; we obliterate the data cost underneath it. 🤝
Problems it solves (problem → solution)
Every one of these is a real Magento 2 pain on a large catalog — and a standalone reason to install:
- Problem: a homepage/PDP fires thousands of EAV queries and cold pages crawl. Solution: products are served from OpenSearch as real product objects → 0 product/EAV SQL on the hot path.
- Problem: adding to cart loops the inventory tables ~30×, and the cart page loops them again. Solution: Fast Checkout serves lines from the index and drops the redundant stock revalidation → cart render flat (~4 s → ~0.4 s on a 7-item cart), can't oversell.
- Problem: a configurable with thousands of variants melts PDP/cart with a per-child price/stock N+1. Solution: per-child price/stock/rule data is pre-indexed → the ~660/7,500-child N+1 becomes 0 price SQL.
- Problem: the category menu, breadcrumbs and layered nav fan out into
catalog_category_entity_*UNIONs and a per-category URL-rewrite N+1. Solution: the whole tree is served from one OpenSearch read → 0 category EAV reads, URL-rewrites batched to one query. - Problem: Magento search is slow, reloads the whole page to filter, and ranks poorly. Solution: instant autocomplete + a live SERP with live layered navigation (no reload) + AI-tuned relevance.
- Problem: an attribute with 50k options (colors) makes the admin attribute page hang or crash. Solution: a paginated option manager — one page at a time, search by name/ID, single-row edit/delete.
- Problem: the frontend and the admin fight over the same database, so the back office is slow under load. Solution: the heavy read path moves to OpenSearch → the admin gets faster on the same hardware.
- Problem: going fast usually means core patches or ripping out third-party modules. Solution: base Magento only, real product objects, FPC/Varnish-safe, every feature toggle-able → drop-in.
Built for large, attribute-heavy, complex-configurable catalogs
This extension targets the catalogs where native Magento hurts the most — many attributes and
configurables with hundreds of variants — and it is architected so per-page product/EAV
cost stays flat as the catalog grows. Every PDP, PLP and search request resolves to a bounded
OpenSearch read (one search + one mget), never a scan of the catalogue, so page latency does
not climb with product count the way native EAV hydration does.
Capacity. Because the per-request cost is independent of catalog size, the practical ceiling
is your OpenSearch cluster, not Magento's EAV. OpenSearch comfortably indexes millions of
documents on modest hardware, and the serving reads (bounded search + mget by id) stay
constant-time regardless of how large the catalogue gets. In practice this is designed to serve
catalogues from a few thousand to 1M+ products without per-page performance degradation;
beyond that, OpenSearch sharding scales further. The one operation that grows with catalog size —
reindexing — streams documents in bulk chunks for flat memory use, so it scales too.
Complex configurables. Native Magento prices, hydrates and stock-checks a configurable by iterating every child, so cost scales with variant count — a configurable with hundreds of variants is a worst case that makes cold PDP, cart and checkout crawl. The serving layer turns those into flat index reads, and is validated against configurables carrying several hundred variants across multiple axes. If your catalogue is large, attribute-heavy, or full of big configurables, this is exactly the workload it targets; a small, simple catalogue still gets the query reductions but feels less wall-clock benefit (see the note under Benchmarks).
Measured on a production-sized catalog:
| Page | Before | After |
|---|---|---|
| Cold homepage | ~21,610 SQL | ~1,084 SQL |
| PDP (any type) | hundreds of EAV queries | 0 product/EAV SQL |
| Category page | ~58+ category/EAV queries | 0 product/EAV SQL |
| Search results | 342 SQL | 118 SQL |
📊 Performance at scale — 500,000 products (with vs without)
Measured on a purpose-built 500,000-product stress catalog (513k simple products across 4,187 configurables, a 50,000-option color attribute, every bra/apparel size) in an isolated database. Apples-to-apples: the module is fully disabled (
module:disable, real native MySQL) vs enabled, replaying the same URLs and the same 10-configured-item cart. Cold render (every request a unique URL → Full-Page-Cache miss, median of 3) — the honest comparison, because on an FPC hit both states serve identical cached HTML. Cart/checkout use the REST totals endpoints (never FPC-cached). The store runs its real third-party stack (Webkul Marketplace, Stripe, …) on every page in both columns.
Page load / render time @ 500k products (cold render)
| Surface | Without (native) | With FastMagento | Speed-up |
|---|---|---|---|
| Home / CMS | 2,911 ms | 775 ms | 3.8× |
| PDP · simple | 555 ms | 480 ms | 1.2× |
| PDP · configurable (660-variant) | 1,371 ms | 827 ms | 1.7× |
| Category / PLP | 543 ms | 622 ms | ~1× |
| Search results (SERP) | 631 ms | 338 ms | 1.9× |
| Cart · collectTotals (10 configured) | 1,485 ms | 1,009 ms | 1.5× |
Checkout · totals-information |
1,510 ms | 1,110 ms | 1.4× |
SQL queries per cold render @ 500k products — the scale-invariant metric:
| Surface | Without (native) | With FastMagento | Reduction |
|---|---|---|---|
| Home / CMS | 10,090 | 80 | −99% |
| PDP · simple | 413 | 256 | −38% |
| PDP · configurable | 388 | 231 | −40% |
| Category / PLP | 246 | 131 | −47% |
| Search results | 220 | 23 | −90% |
| Cart · collectTotals (10 configured) | 1,210 | 265 | −78% |
Checkout · totals-information |
1,250 | 295 | −76% |
How to read it (honestly). The query-count collapse is the headline — the homepage alone goes 10,090 → 80 queries (~126×), and the never-cached cart/checkout path drops ~4–5× (1,210 → 265). Wall-clock wins are biggest where data-loading is the bottleneck (home, search, configurable PDP, cart); it's roughly flat on light pages (simple PDP) and can even be a hair slower on a small category, because there the time is dominated by PHP rendering + third-party modules (Webkul Marketplace alone fires ~180 queries/PDP) — identical in both columns — and the OpenSearch round-trip adds a few ms. On a production DB (millions of EAV rows, 1–5 ms/query, networked/replicated) that same query collapse is seconds of latency and a large drop in DB load. The design goal — product/EAV SQL stays flat as the catalog grows — holds; native does not. These page-time figures are server response (TTFB-class); the real browser gap is wider, because native Magento also ships a heavier DOM (more blocks, more inline data) that costs the browser more to parse and render — a full-page-load (DOMContentLoaded / fully-loaded) comparison amplifies the difference beyond what server time alone shows.
Cached (FPC hit) @ 500k — the returning-user experience:
| Surface | With or without FastMagento |
|---|---|
| Any FPC-cached page (home, PDP, category) | ~231 ms (identical — served from cache) |
| Search results (not FPC-cached) | ~665 ms |
On an FPC hit both states are identical (~231 ms) — the extension's win is on cache miss (first views, high-churn catalogs) and the never-cached paths: search, cart, checkout.
Admin @ 500k — the 50,000-option attribute:
| Metric | Without (native) | With FastMagento |
|---|---|---|
Attribute-edit page (50k-option color) |
crashes / hangs | opens instantly (paginated, 50/page) |
🔎 Extension Efficiency Monitor — find which extension is slowing you down
Third-party extensions don't advertise their cost. FastMagento's Extension Efficiency Monitor (Admin → FastMagento → Extension Efficiency) profiles a real page render — data loads, full PDP / category / search HTML, and the checkout totals path — then attributes every SQL query to the extension that fired it. It answers the question every store owner actually has: "which app is making my store slow, and where do I tell my developer to look?"
The headline is the N+1 hotspot table — a developer can read one row and know exactly where to go: Extension · Page · Class::Method · Loops · Table. On this 500k-product store with its real third-party stack it surfaces, automatically:
| Extension | Page | Class :: Method | Loops | Table |
|---|---|---|---|---|
| Webkul Marketplace | PDP | Data::getSellerCollectionObj |
×87 | marketplace_userdata |
| Webkul Marketplace | PDP | SellerIdAttribute::getAllOptions |
×84 | marketplace_userdata |
| Mageplaza Productslider | Home | AbstractSlider::getProductParentIds |
×11 | sales_bestsellers… |
| Jadog StructuredData | PDP | BreadcrumbSchema::bestCategory |
×9 | catalog_category_entity |
| StripeIntegration Payments | Checkout | Product::getProduct |
×4 | catalog_product_entity |
Plus at-a-glance customer-experience load times per page (Home / PDP / PLP / Search / Checkout,
colour-coded), a per-hot-path core-vs-extension query "tax" chart, and a severity-ranked table of
every extension's total database overhead. Run it on demand, on a schedule (cron), or from the CLI
(bin/magento fastmagento:efficiency:scan). It works for any extension — no per-extension config.
The reference numbers below are from a mid-sized (~14.6k-product) catalog; the 500k tables above are the headline "does it hold at scale" proof.
Benchmarks — with vs without the extension
Measured on this storefront by toggling ParkkTech_FastMagento on and off and replaying the
same requests. Cold render = Full-Page-Cache disabled — the real product-render path (an
FPC hit renders no product blocks, so it hides the cost). Environment: local dev, a mid-sized
catalogue with active catalog price rules across customer groups and configurables up to
several hundred variants, with Webkul Marketplace and other third-party modules running on every
page.
SQL queries per cold render
| Surface | Product type | Without (native) | With FastMagento | Reduction |
|---|---|---|---|---|
| PDP | Simple | 822 | 540 | −34% |
| PDP | Downloadable | 784 | 512 | −35% |
| PDP | Configurable (660 variants) | 774 | 494 | −36% |
| PLP | Category listing | 452 | 224 | −50% |
| Search | Results page | 436 | 48 | −89% |
| Cart / Checkout | Configurable + simple | 524 | 252 | −52% |
Why wall-clock isn't quoted here. On a local-dev box MySQL answers each query in ~0.1 ms, so removing a few hundred queries only saves tens of milliseconds — swamped by PHP and the third-party modules that run on every page (Webkul Marketplace alone fires ~180 queries/PDP, identical with the extension on or off). Local wall-clock therefore understates the benefit, so we don't present it as a headline number. The query-count reduction above is the scale-invariant metric: the reads FastMagento removes are the product / EAV / catalog-rule queries whose cost grows with catalog size and DB round-trip latency. On a production store — millions of EAV rows, a networked / replicated database at 1–5 ms per query — the same reduction becomes seconds of latency and a large drop in DB load. The design goal: product / EAV SQL stays flat as the catalog grows; the native path does not. (Scale-tested wall-clock at 500k products appears earlier in this README.)
Cart / checkout is the clearest real-world win: wall-clock there is dominated by configurable line-item hydration and grows with basket size. With the default-on Fast Checkout, a realistic 7-item configurable cart goes from ~4 s to ~0.4 s (~10×) — see the Fast Checkout table below.
N+1 query patterns eliminated
The patterns that make checkout and large-catalog pages fall over:
- Configurable price resolution. Native Magento prices a configurable by iterating every
child (660 here) through the price model; on the serving-layer path this produced one
catalogrule_product_priceand onecatalog_product_entity_tier_pricequery per child. Child prices (per customer group) are now served from the index → 0 rule/tier SQL on the configurable PDP. - Cart / checkout reprojection. Writing stock for a single configurable child reprojected
the whole parent inline, calling
getRulePrice()once per child (~660 queries) on the revenue path. The indexed base price is now rule-neutral (no per-child price observer) and reprojection is deferred off the response and skipped when stock is unchanged → 671 → 8 catalog-rule queries on a cart view, and the shopper never waits on the reindex. - Search. Relevance + layered-nav facets served from the index → 436 → 48 SQL.
Catalog price rules are resolved per customer group (guest, Wholesale, Retailer, …) across PDP, PLP, cart and checkout, served from a per-group map in the index rather than a per-child SQL lookup.
Fast Checkout — configurable-cart render time
The cart/checkout HTML render cost is dominated by hydrating each configurable line item
(parent + used-products + per-source MSI salability), so native checkout time grows with the
number of configurable line items. Fast Checkout (Enable Fast Checkout, on by default)
serves those line products from the index and flattens it. Warm render, this storefront:
| Cart | Native | Fast Checkout |
|---|---|---|
| 1 simple | 0.93 s | 0.41 s |
| 1 configurable | 1.08 s | 0.42 s |
| 7-product cart (real-world mix) | ~4.0 s | ~0.4 s |
| 10 configurable variants | 3.34 s | 0.41 s |
Native cart/checkout render grows with every configurable line (~230 ms each), so a realistic basket compounds fast — a 7-product cart takes ~4 s natively. Fast Checkout is flat at ≈0.4 s regardless of cart size or configurable count, so that same 7-item cart renders in ~0.4 s — roughly a 10× cut. Order placement still re-checks salable quantity by SKU (MSI reservations), so it cannot oversell; any product missing/partial in the index falls back to the native path automatically.
Features at a glance
Every capability below is a standalone feature — run the whole serving layer, or just the one you came for:
| Feature | What it fixes |
|---|---|
| 📦 OpenSearch product serving (PHP loads from OS) | 0 product/EAV SQL on PDP, cart, listings |
| 🗂️ Category & navigation serving | 0 category EAV reads for menu / breadcrumbs / nav |
| 🔗 Related, up-sell, cross-sell & sliders | per-item EAV + URL-rewrite N+1 |
| 🧩 All product types served | simple, configurable, downloadable, virtual |
| ⌨️ Autocomplete search (as-you-type) | slow native autocomplete |
| ⚡ Instant search results page (live SERP) | full page reload to search |
| 🎛️ Live layered navigation on search | full reload to filter / "Shop By" |
| 🤖 AI-powered search relevance & keywords | poor Magento relevance on big catalogs |
| 🛒 Fast Checkout | 30× inventory loops on add-to-cart / cart |
| 🔄 Real-time stock & price sync | stale index after orders / rule changes |
| 💲 Per-customer-group & B2B pricing | per-group price N+1 |
| 🎨 Paginated attribute options (50k+) — filter & bulk-clean unused | attribute page crashing at 50k+ options; thousands of unused options |
| 🏷️ Option labels served from OpenSearch | residual eav_attribute_option reads on PDP / PLP / search |
| 🏎️ Faster admin on the same hardware | admin slow while frontend hits the DB |
| 🛟 Read-path resilience & fallback | OpenSearch outage safety |
| 🔌 Drop-in & third-party transparent | going fast without core patches |
Feature: OpenSearch product serving (zero EAV reads)
Zero. That's how many product/EAV queries your PDP fires now. Not fewer — zero. Go ahead, count them. We'll wait. 😏
Every product read on the storefront — PDP, cart, related/up-sell, search hydration, product
sliders — is answered by a ShellNoEavProduct: a real Magento\Catalog\Model\Product subclass
whose load() is a no-op and whose getters return from the indexed _source. This removes the
per-product catalog_product_entity_{varchar,int,decimal,text,datetime} EAV multi-table load and
its option-label lookups, and serves price, special/tier/catalog-rule prices (per customer group),
stock, media gallery, category names, URLs and every custom attribute — with select/multiselect
values pre-resolved to labels in the index. Because it stays a real product object, third-party
blocks, plugins and SEO modules keep working unchanged.
Feature: Category & navigation serving
Your mega-menu used to cost 100+ queries every single page load. Now the entire category tree arrives in one search and lives in memory. Breadcrumbs, nav, "Shop By" parents — all free.
A dedicated fastmagento_category indexer (magento2_categories) projects the whole tree; a
request-scoped provider pulls it in one search and answers the mega-menu, top navigation,
breadcrumbs and layered-navigation category data from memory — eliminating the
catalog_category_entity_{varchar,int,text} UNION attribute loads (0 category EAV reads). A
batched URL finder collapses the per-category url_rewrite N+1 (~108 queries → one per store).
(The category page's product grid still renders natively today; the search results page is the
fully OS-served, live listing — server-side OS-serving of the category grid is on the roadmap.)
Feature: Related, up-sell, cross-sell & product sliders
Link blocks and slider widgets hydrate from OpenSearch (one link-graph query + one mget), and
child→parent lookups resolve from the indexed parent_ids — removing the per-item EAV load, the
per-card url_rewrite N+1, and the catalog_product_super_link parent N+1 that make product
sliders quietly expensive on every page.
Feature: All product types served
- Simple & Virtual — fully served.
- Downloadable — links and samples are indexed and hydrated into the native downloadable blocks (title, price, per-link samples), rendered with 0 downloadable SQL.
- Configurable — swatch
jsonConfig, per-option prices and swatch config are served from OpenSearch; the PDP renders the full swatch UI, even for configurables with thousands of variants. Out-of-stock option combinations still render greyed, matching Magento's default. - Grouped / Bundle — indexed; add-to-cart hardening in progress.
Feature: Autocomplete search (as-you-type dropdown)
Blink and you'll miss it. The shopper starts typing — and the right products are already sitting there, images, prices and all. Faster than they can finish the word.
An Algolia-style autocomplete dropdown on the header search box: as the shopper types, a
debounced AJAX call returns product cards (image, name, price, in-stock) and matching category
suggestions, rendered instantly with keyboard navigation. Results come from OpenSearch — relevance
from the analyzed fulltext index, display data hydrated from the serving index in one mget — so
suggestions appear in milliseconds and every keystroke aborts the previous request. Endpoint:
/fastmagento/search/suggest.
Feature: Instant search results page (live SERP, no reload)
A search results page that never reloads. On Magento. Read that again. We'll give you a second.
The search results page is a fully live SERP: the product grid and pagination re-render in
place from OpenSearch with no page reload as the shopper types or pages, and the URL updates via
history.replaceState (shareable, back-button friendly). It replaces Magento's native
server-rendered search results outright and returns hits in milliseconds. Endpoint:
/fastmagento/search/instant.
Feature: Live layered navigation on search (instant "Shop By")
Tick a filter. Watch the entire page rearrange itself. No reload. Facet counts, grid, pagination — all instant. Your customers will think you rebuilt the store in React.
The layered-navigation "Shop By" facets on the search page update live — tick a filter and the
grid, pagination and facet counts all re-render in place from OpenSearch aggregations, no page
reload. Facet option labels come straight from the index (no DB/EAV lookup on the request
path), and the native Magento layered nav is replaced outright. Configure which attributes become
facets via FastMagento > Search > Facet Attributes.
Feature: Fast Checkout — no more 30× inventory loops
Imagine a Magento where your customers aren't punished for adding more to their cart — or for you offering richer sales-rule flexibility. Our cart paints the DOM in under a second whether there's 1 item or 20 highly-configured products — something natively impossible in Magento without slowing checkout to a crawl.
Here's why native Magento does punish a full cart: it hydrates each line from a ~217-query product collection (EAV ≈119 + MSI stock ≈71 + downloadable ≈27) and re-checks salable quantity ~30 times per line on add-to-cart and again on cart load — so every extra item, every extra configurable variant, and every extra sales/catalog rule piles on more work. Cost grows with the size and complexity of the cart, exactly when the shopper is closest to buying.
Fast Checkout (on by default) serves those line products — including configurable variants — from the index and drops the redundant revalidation, so cart/checkout render is flat regardless of cart size (a realistic 7-item cart goes from ~4 s to ~0.4 s; a 20-line highly-configured cart stays under a second). One master toggle turns on the whole fast pipeline (OS-serve + optimistic stock + fast stock sync); order placement still gates stock by SKU so it cannot oversell, and anything not fully in the index falls back to native automatically.
Feature: Real-time stock & price sync
Sell it, refund it, restock it — the index already knows. Updates land in OpenSearch before the response even finishes, so your storefront is never stale and the shopper never waits.
Order placement, refunds/returns and MSI inventory-API writes reproject the affected products (and
their configurable/grouped/bundle parents) into the index immediately — after
fastcgi_finish_request, so the shopper never waits — keeping quantity and in-stock status live
between reindexes. Fast stock sync patches only the stock fields via one mget + bulk update
instead of a full EAV reprojection. Catalog-rule recalculations (rule save / nightly apply-all)
patch per-group rule prices into the docs automatically, so the OS-served cart is correct with no
manual reindex.
Feature: Paginated attribute options — manage 50,000+ options without crashing
The one screen that made Magento give up. 50,000 color options used to hang the attribute page. Now it opens instantly, you search by name or ID, and every edit saves a single row. The impossible admin page — solved.
Keywords: Magento paginated attribute options, attribute option pagination, manage thousands of swatch options, 50k color attribute. A well-known Magento 2 shortcoming: the product-attribute Manage Options grid (and the swatch variants) renders and re-serialises the entire option set on one page. Past a few thousand options the attribute-edit page slows to a crawl or crashes outright — a hard blocker for large apparel/lingerie catalogs where a single color attribute can carry tens of thousands of combinations.
FastMagento replaces that screen with a paginated option manager:
- Loads one page at a time (AJAX) — the edit page opens instantly even at 50,000 options.
- AJAX search by name or ID — find one specific option among 50k to edit or delete.
- Single-row writes — add / edit / delete is one AJAX call touching only that option's rows; the whole set is never rewritten. A guard plugin stops the native "save the whole array" path from ever running, so a Save Attribute click can't wipe your options.
- Every option type — dropdown, multiple-select, visual swatch (color/image), text swatch.
- "Assigned to a product" column + filter — see at a glance which options are actually used, and
filter to Any / Yes / No. On the 50k
colorattribute this instantly surfaces the 23,201 unused options. - Bulk delete — check rows and delete the selection, or "Delete All Matching" to remove the entire filtered set across all pages in one action (chunked). Cleaning out thousands of dead options — filter → Unassigned → delete — is one click; the confirm warns if any are still in use.
On by default. See Admin: paginated attribute options for details.
Feature: Option labels served from OpenSearch (no MySQL on the hot path)
Every select/multiselect option label (PDP "Additional Information", layered-nav Shop By
facets, swatches, search) is projected into an OpenSearch option dictionary and served from it,
so a served page resolves labels with zero eav_attribute_option MySQL reads — dropping the
residual per-page option lookups to 0 on PDP, PLP and search. Native fallback on any miss.
Feature: Fast, non-blocking reindex
Reindex a 500k catalog in minutes, and never take the store down doing it. ⚡
The product indexer is set-based and batched: it loads each chunk of products in one pass
instead of one getById() per product, so it doesn't drag every installed third-party module
through a per-product load (a single mis-behaving extension can otherwise add millions of wasted
queries to a reindex) and it builds each OpenSearch document from explicit batched queries. On a
500,000-product catalog this cut a full reindex from ~2.6 hours to ~50 minutes (≈3× overall,
and ~8× on the simple-product volume) — with no extra hardware and no parallelization (which
would just fight the app server for CPU); it's simply less work per product.
Products never disappear while it's reindexing. Search and product pages keep serving throughout a rebuild: any product not yet in the (rebuilding) serving index is loaded natively once and read-through-indexed on the spot (warm-on-miss), so a shopper never sees an empty results grid or a missing PDP mid-reindex — the store stays fully live from the first second of the rebuild to the last.
Feature: Read-path resilience & fallback
Warm-on-miss (a product missing from the index is loaded natively once, projected, then served from OpenSearch thereafter — self-healing like a cache miss) and automatic native fallback whenever OpenSearch is unavailable. Serving happens beneath Full-Page-Cache and Varnish, so it is cache-transparent.
Feature: Faster admin on the same hardware
But wait… there's more! 📺 Move the storefront off MySQL and your admin gets faster too — imports, indexers, order processing, all of it — on the exact same hardware. That's not an add-on. That's a free bonus you didn't even ask for.
A quiet but real win: because the frontend no longer hammers MySQL for product/EAV/price/stock reads (that work now lands on OpenSearch), the database and PHP-FPM pool spend far less time serving storefront traffic — so the Magento admin, imports, indexers and integrations get noticeably faster on the exact same hardware. The frontend and the back office stop fighting over the same database. No extra servers, no config: it's a side effect of moving the heavy read path off MySQL.
Feature: Per-customer-group & B2B pricing served from the index
Catalog price rules, tier prices and special prices are indexed per customer group and served
straight from the document on PDP, PLP, cart and checkout — so a store with many customer groups
(guest, wholesale, retailer, VIP…) resolves the right price with no per-child, per-group SQL
lookup. This is what kills the classic per-child catalogrule_product_price + tier-price N+1 on a
big configurable, and keeps B2B pricing fast at scale.
Feature: Drop-in & third-party transparent (base Magento only)
No core hacks. No ripped-out modules. And no Hyvä or Breeze required. It's all backend — pair it with a fast theme for a real banger, or run it on stock Luma and still fly. Turn any piece off and it falls straight back to native. Your extensions never even notice.
FastMagento runs on base Magento — no core patches. Because every read returns a real
Magento\Catalog\Model\Product / Category object (just hydrated from OpenSearch instead of the
DB), third-party extensions, SEO modules, themes and page builders keep working unchanged. It sits
beneath Full-Page-Cache / Varnish, and any feature can be toggled off to fall straight back to
native behaviour. Adopt it incrementally.
Breeze (Swissup) compatible
The bulk of FastMagento is backend, so it's theme-agnostic. Its only storefront JavaScript — the
as-you-type autocomplete and the live instant-search results page — is plain jQuery
bootstrapped through text/x-magento-init / data-mage-init, with no Knockout, jQuery-UI, or
mage/* widget dependencies. That's the ideal shape for Breeze's
Better Compatibility mode: view/frontend/layout/breeze_default.xml registers the module so
Breeze reuses the exact same web/js files and requirejs-config.js aliases and runs them with real
jQuery — nothing is forked into a js/breeze/ bundle. On native Luma the breeze_default handle
never fires, so that file is inert and the RequireJS path is untouched. One codebase, both stacks.
What it serves from OpenSearch — and the SQL it removes
On the storefront hot path, product and category reads resolve from an OpenSearch document instead
of Magento's EAV/ORM. Each mechanism replaces a specific native query pattern — the ones whose cost
grows with catalog size, attribute count and configurable variant count. The read path is
intercepted at the single Magento\Catalog\Model\Product::load choke point (and the product
repository, product/link collections, category collection, price models, stock registry and the
quote-item collection), so third-party code keeps receiving a real product/category object. (The
write path still runs set-based SQL at index time; the elimination is on the read/serving
path.)
| Serving mechanism | What it answers from the index | Native SQL it removes |
|---|---|---|
ShellNoEavProduct — no-op load(), getters from _source |
Every product attribute read on PDP / cart / listing | catalog_product_entity_{varchar,int,decimal,text,datetime} EAV joins + option-label lookups |
Per-customer-group price maps on the doc + TierPrice/CatalogRulePrice/SpecialPrice overrides |
Catalog-rule, tier and special price, resolved per customer group | per-child catalogrule_product_price + catalog_product_entity_tier_price N+1 — the ~660-query cost on a 660-variant configurable |
Configurable child shells in registry + LowestPriceOptionsProvider override |
Configurable "from" price, options, add-to-cart variant match | the native used-product collection load + its per-child rule/tier N+1 |
| Category tree pulled in one search + attribute-load plugin | Mega-menu, top-nav, breadcrumbs, layered-nav category data | catalog_category_entity_{varchar,int,text} UNION attribute loads → 0 category EAV reads |
Indexed request_path (products) + batched category URL finder |
Product-card and category URLs | per-item url_rewrite finder N+1 (~108 queries) → one batched query per store |
OpenSearchStockRegistry + indexed stock_item |
Stock item / status on PDP and cart | per-product cataloginventory_stock_item loads; per-line WHERE sku=? MSI preload |
Indexed parent_ids + ParentIdResolver |
Child→parent resolution for sliders / widgets | catalog_product_super_link / catalog_product_link parent N+1 |
Indexed swatch_options + configurable_options_<id> |
PDP swatch jsonConfig |
eav_attribute_option / _value / _swatch per-option lookups |
| Indexed downloadable links / samples | Downloadable PDP blocks | downloadable_link / downloadable_sample loads |
| OS-served quote-item collection (Fast Checkout) | Cart / checkout line hydration | the native ~217-query _assignProducts build (EAV ≈119 + MSI stock ≈71 + downloadable ≈27) |
| Search relevance + facets from the native fulltext index | Instant search results + live layered nav | the layered-navigation attribute/option SQL and category/EAV reads behind native search |
Every mechanism is guarded: a doc that is missing, partial or unservable falls back to the native path automatically, and if OpenSearch is unavailable the whole storefront transparently reverts to EAV. A product missing from the index is warmed on first access — loaded natively once, pushed to the index, then served from OpenSearch thereafter.
AI-powered search relevance (search so good it's almost unfair)
We'll say it: on a real, messy catalog this is a better search experience than most billion-dollar storefronts — because it doesn't just index your words, it learns your catalog's language with AI and tunes itself. Install the extension, add a Claude API key, run the AI mapping tool once, and get near-best-possible search with almost no hand-tuning.
The AI reads your own content — attribute labels, category names and a sample of product names — and builds the language layer for you:
- AI synonym & thesaurus discovery from your content — finds the equivalents shoppers actually
type and folds them into the thesaurus (e.g. it learns
front end ↔ frontend,rear end ↔ back end,u-bolt ↔ ubolt,rock racer ↔ rockracerstraight from your product names). No hand-curation. - AI per-product keyword discovery — fills a hidden
fm_search_keywordsfield per product with the buyer terms and aliases your copy never mentions (UTV ↔ side-by-side ↔ SxS, brand/fitment nicknames), so the right product surfaces for words that aren't on the page. - Grammatical & compound-word variants — spacing/hyphenation/plural pairs (
t-shirt ↔ tshirt ↔ tee) discovered automatically, with a guardrail against building groups around common words. - Similar words & misspellings — typo tolerance plus AI-discovered misspelling/variant pairs, so a fat-fingered query still lands.
- Smart stop words & exceptions — stop words are stripped for recall but preserved where they matter (a phrase like "side by side" survives instead of collapsing to "side").
- Symmetric synonyms — a term and its synonyms are scored as true equals, so
frontendandfront end(orsxsandutv) return identical results in the same order.
Everything below is the ranking engine those AI layers feed into.
Real search wins (across niches)
Native Magento treats each of the left-hand queries as a different word and returns junk (or nothing). FastMagento learns they're the same thing — from your own catalog — and returns the right products, ranked the same either way:
| A shopper types… | Native Magento | FastMagento (AI-learned) | Niche |
|---|---|---|---|
frontend vs front end |
different results / 1 hit | identical results | off-road / auto |
top end vs top-end |
split, inconsistent | identical — same engine parts | performance auto |
side by side / sxs |
matches the word "side" | UTV / side-by-side rigs | powersports |
t-shirt / tshirt / tee |
three different result sets | one result set | apparel |
34E / 34DD |
miss | same cup size surfaced | lingerie |
ebike / e-bike / electric bike |
split | identical | cycling |
usb-c / usb c / type-c |
split | identical | electronics |
allen key / hex key / allen wrench |
miss | same tool | hardware / tools |
a-arm / control arm |
miss | same suspension part | off-road |
u-bolt / ubolt |
split | identical | trailer / auto |
The magic: you don't type any of these in by hand. The AI discovers them from your products, so the wins match your niche — whatever you sell.
Relevance engine
The query builder (Model/Search/InstantSearch::buildQuery) ranks results with:
- Exact / phrase boosting — a product whose name/keywords contain the query as a contiguous phrase outranks docs where the words are merely scattered, so "skid plate" ranks the actual skid plates above everything that just contains "plate".
- All-terms precision boost — products matching every term (across name + keywords + description) rank above single-term hits, without ever costing recall.
- Multi-term operator —
FastMagento > Search > Multi-Term Operator: Any (OR, broadest), Most (75%), or All (AND, most precise). - Symmetric synonyms — a query and its synonyms are scored as true equivalents, so
frontendandfront end(orsxsandutv) return identical results in the same order. Phrase-level expansion means single-word ⇄ multi-word variants work (frontend ⇄ front end,a-arm ⇄ control arm), which token-level swapping cannot do. - Typo tolerance, in-stock boost, and a custom-ranking tie-breaker (all admin toggles).
Two synonym homes — used automatically
- Global synonym thesaurus (
Search > Synonyms) — for distinctive terms. Ships with a bundled starter database (etc/thesaurus/starter-synonyms.json, imported on install, merge-safe) covering compound/grammatical variants (front end, back end, rear end, t-shirt), colours, sizes, materials and misspellings. - Per-product AI keyword layer (
fm_search_keywords) — a hidden, high-weight searchable attribute the AI fills per product with buyer terms and aliases (UTV ↔ side-by-side ↔ SxS, fitment/brand nicknames), so a product surfaces for terms its visible copy never mentions. Buyer phrases built from common words (e.g. "side by side") live here, where they match precisely, rather than as global synonyms that would over-broaden.
AI tools (Claude) — scrape your content, optimise search
Add a Claude API key under FastMagento > AI Assistant, then:
-
Generate Thesaurus (admin button) — scrapes your attribute labels, category names and a sample of product names, and discovers the grammatical/compound variants your copy actually uses (e.g.
rear end ↔ back end ↔ backend,u-bolt ↔ ubolt,rock racer ↔ rockracer), merging them intoSearch > Synonyms. It is guard-railed against building groups around common words. -
Generate Search Keywords (CLI) — populates
fm_search_keywordsfor the catalogue, off the request path, in resumable best-effort batches:bin/magento fastmagento:search-keywords:generate [--from --to --batch --limit --force --dry-run] bin/magento indexer:reindex catalogsearch_fulltext # make the new keywords searchableEnable via
FastMagento > Search > AI Search Keywords. Use a fast model (e.g. Haiku/Sonnet) underAI Assistant > Modelfor large catalogues.
Measuring relevance
docs/tools/search-relevance.php runs a set of golden queries through the real query builder
and prints the ranked top-N with scores plus pass/fail checks, so ranking changes are measured,
not guessed:
php app/code/ParkkTech/FastMagento/docs/tools/search-relevance.php # golden-queries.json php app/code/ParkkTech/FastMagento/docs/tools/search-relevance.php "front end" "sxs"
Admin: paginated attribute options (large-catalog fix)
A well-known Magento 2 shortcoming: the product-attribute Manage Options grid (and the swatch variants) renders every option on the page and re-serialises the whole set on save. Past a few thousand options the attribute-edit page slows to a crawl or crashes outright — a hard blocker for large apparel/lingerie catalogues where a single colour attribute can carry tens of thousands of combinations (solids, two-colour patterns, prints).
FastMagento replaces that screen with a paginated option manager:
- Loads one page at a time (AJAX) — the edit page opens instantly even at 50,000 options, instead of materialising the entire array into the DOM.
- Search across option labels, server-side (bounded query).
- Single-row writes — adding, editing or deleting an option is one AJAX call that touches only
that option's rows (
eav_attribute_option+ per-store value + swatch), never the whole set. A guard plugin also stops the native "save the whole array" path from ever running, so a Save Attribute click can't wipe or overwrite the option set. - All option types — dropdown, multiple-select, visual swatch (colour/image) and text swatch.
- "Assigned to a product" column + filter — each row shows whether the option is used by any
product (resolved efficiently from the product's backend table —
int/varchar/text, so it is correct for select and multiselect). Filter the grid to Any / Yes / No to see exactly which options are dead weight (on the stress catalog's 50kcolor, that's 23,201 unused). - Bulk delete — a checkbox column with select-all-on-page and Delete Selected, plus Delete All Matching which removes the entire filtered set across all pages in one chunked action (so filter → Assigned: No → delete clears thousands of unused options at once). Both confirm with a count; if the set still includes in-use options the confirm warns before deleting.
On by default (FastMagento > … attribute_pagination/enabled); no configuration needed.
Requirements
- Magento 2.4.x (Open Source / Commerce), base install.
- OpenSearch 1.x/2.x (the engine already configured under
Stores → Configuration → Catalog → Catalog Search). - PHP matching your Magento version.
Installation
Requirements: Magento 2.4.6+ (Open Source or Commerce), PHP 8.1+, and a configured OpenSearch (or Elasticsearch) search engine — the same engine Magento already uses for catalog search. No Hyvä/Breeze required; works on base Luma or any theme.
Composer (recommended)
composer require parkktech/fastmagento
Published on Packagist — the one-liner
pulls the latest release tag. Pin a version with parkktech/fastmagento:^1.0, or track the tip of
development with parkktech/fastmagento:dev-master.
Installing before it appears on Packagist, or from a fork
The package lives at github.com/parkktech/FastMagento (public). Add it as a VCS repository, then require it:
composer config repositories.parkktech-fastmagento vcs https://github.com/parkktech/FastMagento.git composer require parkktech/fastmagento
Then, either install path:
bin/magento module:enable ParkkTech_FastMagento
bin/magento setup:upgrade
bin/magento setup:di:compile # production mode only
bin/magento cache:flush
⚡
setup:upgradealso auto-installs large-catalog performance indexes (declarative schema,etc/db_schema.xml) — composite(attribute_id, store_id, value)indexes on the EAV value tables Magento leaves unindexed (_varchar,_decimal,_datetime). On a 500k catalog these turn an attribute-value filter from a 571,866-row scan into a 1-row seek (layered navigation, attribute filters, admin grids). No manual SQL, and they're removed cleanly if you uninstall.
Manual (no Composer)
mkdir -p app/code/ParkkTech/FastMagento cp -R <module-source>/* app/code/ParkkTech/FastMagento/ bin/magento module:enable ParkkTech_FastMagento bin/magento setup:upgrade bin/magento cache:flush
Then build the indexes:
bin/magento indexer:reindex fastmagento_product bin/magento indexer:reindex fastmagento_category
Configuration reference
All settings live under Stores → Configuration → FastMagento. Every value is store-view
scoped unless noted; sensible defaults ship in etc/config.xml so the extension is usable
immediately after install.
Indexing
| Setting | Default | What it does |
|---|---|---|
| Enable Real-time Indexing | On | Update OpenSearch docs synchronously on catalog save (mview). |
| Enable Cron Indexing | On | Let the mview cron flush pending changes on a schedule. |
| Product Index Prefix | products |
Index name prefix for the product serving index. |
| Category Index Prefix | categories |
Index name prefix for the category serving index. |
Instant Search & Relevance
| Setting | Default | What it does |
|---|---|---|
| Searchable Attributes & Weights | name, sku, descriptions | The fields searched and their relative weights (higher = ranks stronger). One grid instead of per-attribute Search Weight. |
| Typo Tolerance | On | Fuzzy matching (fuzziness: AUTO) so misspellings still match. |
| Boost In-Stock Products | On | Rank in-stock above out-of-stock as a tie-breaker. |
| Custom Ranking Attribute / Direction | — | Optional numeric secondary sort after text relevance (e.g. bestseller, rating). |
| Multi-Term Operator | Any (OR) | How multiple words combine: Any (broadest), Most (75%), All (AND, most precise). |
| Exact / Phrase Match Boost | 4 | How hard a contiguous-phrase match outranks scattered-word hits. 0 disables. |
| Synonyms / Thesaurus | starter DB | Equivalence groups (one per line). Ships with a bundled starter database; extend by hand or with the AI tool. |
| Stop Words | common English | Words ignored in queries. |
| Facet Attributes | — | Comma-separated attribute codes that build the search-results facets (single-select). |
| AI Search Keywords | Off | Search the per-product AI keyword layer (fm_search_keywords). Enable after populating it (below). |
| AI Keyword Weight | 8 | Search weight of the AI keyword field when enabled. |
| AI Keyword Source Attributes | facet attrs | Attribute codes whose labels give the AI product context when generating keywords. |
AI Assistant (Claude)
| Setting | Default | What it does |
|---|---|---|
| Claude API Key | — | Stored encrypted. Enables the AI thesaurus + keyword tools. Leave blank to disable AI. |
| Model | claude-opus-4-8 |
Claude model id. Use a fast model (Haiku/Sonnet) for large keyword runs. |
| Max Catalogue Terms | 1200 | Upper bound on vocabulary sent to the model (keeps prompts bounded on big catalogs). |
| Generate Thesaurus | button | Scrapes your attribute labels, categories and product names → discovers synonym/compound groups → merges into Synonyms. |
Fast Checkout
| Setting | Default | What it does |
|---|---|---|
| Enable Fast Checkout | On | Master toggle: serve cart/checkout line products from the index + optimistic stock. Cannot oversell (SKU-gated at placement); falls back to native for anything not fully indexed. |
| OS-Serve Quote Items (advanced) | Off | Serve the quote-item collection from OpenSearch by itself. |
| Optimistic Stock (advanced) | Off | Skip the redundant per-load MSI preload; rely on the placement-time gate. |
| Fast Stock Sync (advanced) | Off | Patch only stock fields of affected docs instead of a full reprojection. |
| Configurable Line Name | parent | Show the configurable (parent) or purchased child name on cart/checkout lines. |
Architecture
How it works end-to-end
MySQL stays the source of truth. Two background paths keep an OpenSearch document current for every product and category; the storefront read path then answers from those documents instead of EAV, with an automatic native fallback.
flowchart LR
subgraph WRITE["Write path — keeps the index live"]
direction TB
C["Catalog / price / stock /<br/>category change"] --> IDX["Indexers via mview:<br/>fastmagento_product<br/>fastmagento_category"]
ORD["Order · refund ·<br/>MSI stock write"] --> SS["StockSyncer<br/>after fastcgi_finish_request"]
RULE["Catalog-rule reindex"] --> CRS["CatalogRuleSyncer"]
end
IDX --> OS[("OpenSearch<br/>magento2_products<br/>magento2_categories")]
SS --> OS
CRS --> OS
subgraph READ["Read path — serves the storefront"]
direction TB
REQ["Storefront request<br/>PDP · cart · search · menu"] --> INT{"Interception<br/>layer"}
SHELL["ShellNoEavProduct<br/>real Product · no EAV load"] --> RESP["Rendered page / JSON"]
EAV[("MySQL EAV<br/>native fallback")] -.->|"warm-on-miss"| OS
EAV --> RESP
end
INT -->|"hit"| OS
OS -->|"_source doc"| SHELL
INT -.->|"miss / OS down"| EAV
Loading
Write path. Catalog/price/stock/category edits reproject through the fastmagento_product /
fastmagento_category mview indexers. Stock changes from orders, refunds and MSI inventory
writes are reprojected by StockSyncer after the response is flushed. Catalog-rule
recalculations patch per-group rule prices into the docs via CatalogRuleSyncer — no manual
reindex needed.
Read path. Every storefront read is intercepted and answered from an OpenSearch document by a
ShellNoEavProduct (a real Magento\Catalog\Model\Product subclass whose load() is a no-op and
whose getters read the indexed _source). Because it is a genuine Product/Category, third-party
blocks, plugins and SEO modules keep working unchanged. A missing/partial doc, or an OpenSearch
outage, transparently falls back to native EAV — and a product missing from the index is warmed
on first access (loaded natively once, projected, then served from OpenSearch thereafter).
Two serving indexes
| Indexer id | Index | Each document carries |
|---|---|---|
fastmagento_product |
magento2_products |
name, sku, type, visibility, status; regular / special / tier price + a per-customer-group catalog-rule price map; is_in_stock + qty + full stock_item; media gallery; category_ids / category_names; canonical request_path + url_key/url_path; every custom attribute with option ids pre-resolved to labels; configurable swatch_options + configurable_options_<id>; batched composite child_products (each with its own price/stock/rule map); downloadable links/samples; parent_ids |
fastmagento_category |
magento2_categories |
tree structure (parent_id, path, level, position, children_count); menu flags (include_in_menu, is_active, is_anchor, display_mode); url_key / url_path / request_path |
Both track incremental changes through mview and stream documents to OpenSearch in NDJSON bulk chunks of 200 for flat memory use at catalog scale.
Read path — the interception points
The read path is intercepted at these choke points, so nothing on the storefront issues the native EAV load:
Magento\Catalog\Model\Product::load— the single product-load interception (PDP, cart).ProductRepositoryInterface::getById— repository / REST / GraphQL reads.Product\CollectionFactoryand the link (related / up-sell / cross-sell) collection.Category\Collection::_loadAttributesandUrlFinderInterface— category data + URLs.TierPrice/CatalogRulePrice/SpecialPriceprice models,LowestPriceOptionsProvider, the configurable type model, andStockRegistryInterface.Quote\Item\Collection::_assignProducts— the Fast Checkout quote-item serving (global, so it coverswebapi_rest/graphqlcheckout; admin and cron always stay native).
Real-time stock sync (detail)
MSI mutates stock through SKU-keyed tables (inventory_source_item / reservations) that a
product-entity mview cannot map, so stock is additionally kept live by:
sales_order_place_afterobserver — reservation / decrement on order.sales_order_creditmemo_save_afterobserver — back-to-stock on return/refund.catalog_product_delete_afterobserver — drops the deleted product's document.SourceItemsSaveInterface/SourceItemsDeleteInterfaceplugins — admin grid, imports, ERP integrations andbin/magento inventory:*.
Each reprojects only the affected products (and their configurable/grouped/bundle parents), skips
no-op saves (compares live stock against the indexed values), and runs after
fastcgi_finish_request — best-effort and logged, so a sync failure can never break checkout, a
refund or a stock save. The stock write remains native Magento (StockManagement); the sync only
keeps the index current, and order placement re-checks salable quantity by SKU, so it can never
oversell.
Operations & maintenance
Reindexing
bin/magento indexer:reindex fastmagento_product # product serving index bin/magento indexer:reindex fastmagento_category # category serving index bin/magento indexer:reindex catalogsearch_fulltext # native search index (after AI keyword runs)
Day to day, real-time + cron indexing keep both indexes current; a full reindex is only needed after a bulk import or a mapping change. Reindexing streams documents in bulk chunks, so memory stays flat regardless of catalog size.
AI search-mapping tools
The intended setup flow — install → add a Claude key → run the mapping tools → best search:
-
Thesaurus (admin):
FastMagento > AI Assistant > Generate Thesaurus. Scrapes your own content (attribute labels, categories, product names) and merges discovered synonym/compound groups intoSearch > Synonyms. Re-runnable; merge-safe. -
Per-product keywords (CLI): populate
fm_search_keywords, then reindex fulltext.# dry-run a sample first (no API calls, no writes) bin/magento fastmagento:search-keywords:generate --dry-run --limit=25 # generate for the whole catalogue (resumable; re-run continues where it left off) bin/magento fastmagento:search-keywords:generate --batch=25 # scope / control # --from N --to N entity_id range # --limit N cap this run # --force regenerate products that already have keywords bin/magento indexer:reindex catalogsearch_fulltext
Then enable
Search > AI Search Keywords. For a 14k+ catalog, set a fast model underAI Assistant > Model(Haiku/Sonnet) — keyword extraction does not need the largest model.
Measuring search relevance
# run the golden queries (docs/tools/golden-queries.json) through the real query builder php app/code/ParkkTech/FastMagento/docs/tools/search-relevance.php # ad-hoc: compare specific queries with scores php app/code/ParkkTech/FastMagento/docs/tools/search-relevance.php "front end" "sxs" "skid plate"
Prints the ranked top-N with _score and pass/fail checks, so any relevance/synonym change is
measured before and after — not guessed.
Verifying it works
Confirm pages are served from OpenSearch with minimal SQL using the bundled profiler:
bash app/code/ParkkTech/FastMagento/docs/tools/query-profile.sh enable
bash app/code/ParkkTech/FastMagento/docs/tools/query-profile.sh /your-product.html
bash app/code/ParkkTech/FastMagento/docs/tools/query-profile.sh disable
A PDP/category page should report 0 product/EAV/catalog queries.
OpenSearch quick reference
# Count product docs curl -s "http://localhost:9200/magento2_products/_count?pretty" # Count category docs curl -s "http://localhost:9200/magento2_categories/_count?pretty" # Fetch a single doc by id curl -s "http://localhost:9200/magento2_products/_doc/<entity_id>?pretty" # View the mapping curl -s "http://localhost:9200/magento2_products/_mapping?pretty"
(Index names use the store's opensearch_index_prefix; the defaults are shown.)
Supported product types
| Type | Read (PDP/PLP/search) | Add to cart / order |
|---|---|---|
| Simple / Virtual | ✅ served from OpenSearch | ✅ |
| Downloadable | ✅ links + samples served | ✅ |
| Configurable | ✅ swatches + jsonConfig served | ✅ (option→child matched from OpenSearch) |
| Grouped / Bundle | ✅ indexed | ✅ served — additional confirmation needed |
Known limitations / roadmap
- The category page's product grid still renders natively (MySQL); the fully OS-served, live product listing today is the search results page. OS-serving the category/PLP grid is the next serving-layer milestone.
- Grouped and bundle add-to-cart is served; broader edge-case confirmation is still pending.
- The serving index projects the default store view; per-store serving is tracked separately for multi-store setups.
- Storefront layered-nav & PDP option labels (select and multiselect) are now served from
the OpenSearch option dictionary (
fastmagento_attribute_option), so Shop By / Additional Information resolve labels with 0 MySQL. The instant search-results-page facets still read the native fulltext index for buckets, where a doc's option-id and option-label arrays sort independently — extending those facets to read the same dictionary is the remaining step to full multi-select facet labels on the live SERP. - Fast Checkout is on by default but changes checkout stock behaviour (optimistic stock relies on the placement-time SKU gate), so validate it with a real order — guest + a logged-in group, plus a deliberately over-qty line — after go-live; it also assumes a fresh price/rule projection (the always-ready rule sync keeps it current after the first reindex). Turn the master toggle off to fall back to a fully native cart.
Test tooling
docs/tools/search-relevance.php+docs/tools/golden-queries.json— relevance harness: runs golden queries through the real query builder and prints ranked hits with scores + pass/fail.docs/tools/create-downloadable-test.php— creates a downloadable product with multiple purchasable links and product-level samples to exercise the full downloadable render path.docs/tools/query-profile.sh— DB-query profiler used to verify OpenSearch serving.
Credits
FastMagento is built and maintained by ParkkTech.
- Package — packagist.org/packages/parkktech/fastmagento
- Source — github.com/parkktech/FastMagento
- Website — parkktech.com
Need Magento performance work or a custom extension? Talk to ParkkTech »




