Download the PHP package simplemage/module-category-product-indexer without Composer

On this page you can find all versions of the php package simplemage/module-category-product-indexer. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package module-category-product-indexer

SimpleMage - Category/Product Indexer Rewrite

Magento 2.4 PHP 8.2-8.5

High-performance drop-in replacement for Magento 2's catalog_category_product indexer.

Solves the classic "Could not acquire lock for index: catalog_category_product" hang on large catalogs (500k+ products) and delivers 2.6×-7.7× faster reindex measured on real-world production databases - without schema changes, without breaking compatibility.

The problem

Magento 2's stock catalog_category_product indexer runs a single mega-INSERT...SELECT with 13 JOINs, including:

Plus a WHERE clause with IFNULL(store_value, default_value) = X on three EAV columns - which defeats the query optimizer and forces a nested-loop join across the full Cartesian product.

On any non-trivial catalog this causes:

If you've ever seen catalog_category_product stuck at "Processing" in indexer:status while the schedule shows "suspended" - this is for you.

The solution: Snapshot Pattern

Instead of resolving EAV values on every reindex via IFNULL(store, default) joins, this module pre-materializes the EAV state into snapshot tables once:

Snapshot table Replaces Built when
simplemage_product_eav_snapshot 4× JOIN to catalog_product_entity_int (status + visibility) Before every full reindex; incrementally refreshed on partial (including is_salable_composite)
simplemage_category_eav_snapshot 2× JOIN to catalog_category_entity_int (is_active) Same
simplemage_category_ancestor_map Recursive walk over catalog_category_entity.path Before every full reindex; rebuilt on category-trigger partials (moves/creates)
simplemage_category_product_anchor_snapshot Anchor expansion (parent category inherits products from descendants) - store-aware: per-store is_active of the assignment category, same semantics as core's per-store temp tree Before every full reindex; refreshed per affected product on partials

The actual reindex INSERT...SELECT then drops from 13 JOINs → 2 JOINs (PK lookups against snapshot tables, with proper indexes), and the WHERE clause becomes a plain equality without IFNULL - letting the query optimizer pick a sane plan.

Measured impact

All numbers below come from full-reindex benchmarks on copies of real production databases. In every run the index output was verified byte-for-byte identical to core Magento via full-content MD5 snapshots (every row, every column, every store view - no sampling).

Magento 2.4.7-p7 - 111k products, 700+ categories

Metric Core Magento This module Improvement
Wall-clock time 124.4 s 16.2 s 7.7× faster
SQL queries 6 906 2 442 2.8× fewer
Slow queries 391 35 11.2× fewer
Output identical to core - ✅ MATCH (130 742 rows)

Magento 2.4.6-p14 - 447k products, 3 store views, anchor-heavy taxonomy

Metric Core Magento This module
Wall-clock time hangs (> 24 h, killed) 2 min 41 s
Row locks held simultaneously 1 056 250 (growing) ~few thousand (chunked)
Tables locked simultaneously 7 of 19 in use 4-5
JOINs in main INSERT...SELECT 13 2
EAV resolution per-batch, via IFNULL × 8 once, materialized

Mage-OS 2.2.1 (Magento 2.4.8-p4) - 503k products, 4 store views, anchor-heavy taxonomy

Metric Core Magento This module Improvement
Wall-clock time 42 min 8 s 16 min 21 s 2.6× faster
Peak memory 84 MB 52 MB 1.6× less
Slow queries 48 20 2.4× fewer
Output identical to core - ✅ MATCH (18 728 407 rows)

Adobe Commerce 2.4.7-p10 - 116k products, 2 store views, live scheduled updates

Correctness-only verification of the staging support added in 1.1.0 - no timings were recorded for this run.

Check Result
Full reindex output vs core ✅ MATCH (row count + per-store CRC32 fingerprints)
Partial (mview) reindex output vs core ✅ MATCH
Product with 3 live staging versions ✅ exactly one snapshot row per store

Contributed verification, #1.

Your mileage will vary by catalog size, taxonomy shape, and MySQL/MariaDB tuning - but the architectural advantage holds across all measured workloads.

Installation

Via composer (recommended once published)

Manual installation (until published)

Usage

After installation the module takes over automatically - no configuration needed. Existing reindex commands work exactly the same:

If you had a stuck indexer prior to installing this module, also run:

This clears any stale lock state from prior failed runs.

Verifying it's active

Should report:

Disabling (fallback to core)

If you ever need to revert to core Magento indexing (debugging, comparison, etc.):

With the module merely disabled, the snapshot tables (simplemage_*) are kept on disk - harmless, and instantly reusable if you re-enable. A full bin/magento module:uninstall SimpleMage_CategoryProductIndexer drops them via Setup/Uninstall.php; after a manual (file-delete) removal, drop them yourself:

What gets overridden

This module installs three <preference> rewrites in etc/di.xml:

Core class Replacement
Magento\Catalog\Model\Indexer\Category\Product\Action\Full SimpleMage\CategoryProductIndexer\Model\Indexer\CategoryProduct\FullAction
Magento\Catalog\Model\Indexer\Category\Product\Action\Rows SimpleMage\CategoryProductIndexer\Model\Indexer\CategoryProduct\RowsAction
Magento\Catalog\Model\Indexer\Product\Category\Action\Rows SimpleMage\CategoryProductIndexer\Model\Indexer\CategoryProduct\ProductCategoryRowsAction

All three replacements extend their core counterparts. Every overridden hook is gated on a per-run snapshotReady flag: when the snapshot build/refresh fails (lock wait timeout, disk full, DB error, etc.), the flag stays false, the failure is logged, and the entire run executes core's original EAV-JOIN implementations - so a failed snapshot never breaks indexing or corrupts output, it just runs slower.

Compatibility

Version-adaptive semantics

Core changed catalog_category_product behavior between releases; the module mirrors the installed core so output stays byte-identical on every version:

Core change Introduced How the module adapts
Non-anchor + anchor selects filter category is_active 2.4.8 CoreBehavior::filtersInactiveCategories() - version_compare() on ProductMetadataInterface::getVersion() (Mage-OS reports the Magento-compatible version, e.g. 2.2.x → 2.4.8-p4)
_tmp operations must use the adapter that created the TEMPORARY table 2.4.9 Feature detection: method_exists($tableMaintainer, 'getSameAdapterConnection')

For forks or backports where getVersion() is unreliable (e.g. git-dev installs reporting UnknownVersion), pin the behavior explicitly in di.xml:

Architecture

Class layout

Reindex flow (Full)

Why "snapshot tables" are permanent (not temp)

Unlike temp_catalog_category_tree_index_* which Magento drops/rebuilds on every run, our snapshot tables stay alive between reindexes. Reasons:

  1. Partial-reindex reuse - RowsAction and ProductCategoryRowsAction refresh only the affected rows in the existing snapshot via refreshForCategories() / refreshForProducts() instead of rebuilding the whole thing. A temp table would be gone between calls.
  2. Forked-worker compatibility - the snapshot must survive the pcntl_fork() that Magento's ProcessManager performs in batch mode. Per-connection temp tables wouldn't be visible in forked workers.

Why a trait for the Rows path

Magento\Catalog\Model\Indexer\Category\Product\Action\Rows and Magento\Catalog\Model\Indexer\Product\Category\Action\Rows share Select-building logic via their common AbstractAction parent - but at a protected granularity that we cannot easily wrap or proxy.

SnapshotAwareSelectsTrait is composed into both Rows replacement classes and provides shared snapshot-aware Select rewriters. The trait pattern lets us keep zero code duplication while still extending the right core classes individually.

Testing

The integration suite verifies that:

  1. The di.xml preference resolves to our FullAction
  2. A full reindex through the snapshot path terminates and produces non-empty per-store index tables
  3. Two consecutive full reindexes produce bit-identical output (order-stable MD5 fingerprints per store)

The module-vs-core bit-identical comparison (toggling the DI preference between runs) requires a separate process per DI configuration and lives in the companion SimpleMage_IndexerBenchmark module (simplemage:bench:snapshot -d <label>) - results for real catalogs are listed under Measured impact. The unit suite additionally pins the SnapshotBuilder public contract, guards etc/db_schema.xml against drifting from the runtime DDL, and asserts every product-snapshot INSERT populates is_salable_composite.

Known limitations

License

Released under the MIT License - see LICENSE for the full text.

Contributing

Pull requests welcome. For substantial changes, please open an issue first to discuss what you'd like to change.

When submitting:

Reporting bugs

If you hit "Could not acquire lock" or any other reindex failure with this module installed, please attach:

  1. bin/magento indexer:status catalog_category_product catalog_product_category
  2. Output of SHOW ENGINE INNODB STATUS \G from the time of failure
  3. Approximate catalog size (SELECT COUNT(*) FROM catalog_product_entity), number of store views, and whether you use anchor categories
  4. Magento version and PHP/MySQL versions

Issues: https://github.com/simplemage/magento2-category-product-indexer/issues


All versions of module-category-product-indexer with dependencies

PHP Build Version
Package Version
Requires php Version ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0
magento/framework Version >=103.0.6
magento/module-catalog Version >=104.0.6
magento/module-indexer Version >=100.4.6
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package simplemage/module-category-product-indexer contains the following files

Loading the files please wait ...