Indexer Performance for Very Large Catalogs
AI generated
_doc
_index
Elasticsearch · Magento · Indexer
Indexer Performance for Very Large Catalogs
batch size, partial reindex and the Bulk API

On catalogs with several hundred thousand SKUs, the catalogsearch_fulltext indexer quickly becomes an operational bottleneck: full reindex runs take hours, the cron job blocks other jobs, and PHP workers hit their memory limits. This article shows how batch size, partial reindex strategies and the Elasticsearch Bulk API work together to noticeably improve indexer performance on large catalogs.

17 min read Batch size · Partial reindex · Bulk API Magento 2.4 · Elasticsearch 7/8 · OpenSearch

1. Where indexer performance becomes a problem on large catalogs

As long as a Magento store carries a few thousand products, poor indexer performance barely stands out. A full reindex finishes in a few minutes, the cron job disturbs nobody, and the default batch size value from the factory configuration is enough. On catalogs with several hundred thousand SKUs, common in B2B marketplaces or spare parts catalogs, this picture flips completely: a full reindex run of the catalogsearch_fulltext indexer can take several hours, tying up database resources and competing with other cron jobs for CPU and network bandwidth.

Indexer performance does not depend on a single lever, but on the interplay between PHP-side batch processing, database queries for product data, and the efficiency with which documents are transferred to Elasticsearch. Anyone who optimizes only one spot, for example raising PHP memory limits without adjusting batch size, merely shifts the bottleneck instead of removing it. This article systematically walks through every relevant lever, from configuration to ongoing monitoring.

A practical starting point for any optimization is measurement: how long does a full reindex currently take, how many products are processed per second, and where in the process does the biggest wait occur. Without this baseline, the effect of any change to indexer performance cannot be judged objectively.

2. Batch size tuning for catalogsearch_fulltext

The central lever for indexer performance is batch size, configured in app/etc/env.php under the key indexer.batch_size. Magento does not process products for the fulltext index one at a time, but in batches that are loaded from the database, enriched, and then sent to Elasticsearch in bulk. A batch size that is too small generates many small database queries and many small bulk requests, driving up overhead from network latency and connection setup. A batch size that is too large, on the other hand, pushes PHP processes toward their memory limit and can overwhelm Elasticsearch with oversized bulk payloads.

The default value for catalogsearch_fulltext is 100 documents per batch, which is adequate for small to medium catalogs but often too conservative for very large ones. In practice, values between 300 and 1000 frequently show better throughput, depending on average document size. Products with many attributes, long descriptions, and numerous images produce far larger Elasticsearch documents than simple SKUs, which is why the optimal batch size depends heavily on the product data model and should not be copied blindly from another project.


// app/etc/env.php - indexer batch size configuration
'indexer' => [
    'batch_size' => [
        'catalogsearch_fulltext' => [
            'partial' => 500,
            'full' => 500,
        ],
        'catalog_product_price' => [
            'partial' => 500,
            'full' => 500,
        ],
    ],
],

# After changing env.php, run a full reindex once to validate memory usage
bin/magento indexer:reindex catalogsearch_fulltext

3. Update on Save vs. Update by Schedule

Besides batch size, the indexer mode massively affects perceived indexer performance. In "Update on Save" mode, reindexing runs synchronously on every product change, which is fine for individual edits but catastrophic for bulk imports: a CSV import of ten thousand products would trigger ten thousand individual, synchronous reindex operations. "Update by Schedule" mode (mview) decouples changes from reindexing: product changes mark affected records in a changelog table, and a cron job processes them in controlled batches in the background.

For catalogs with frequent bulk updates, "Update by Schedule" is almost always the right choice. The relevant cron group is called indexer, and the frequency of the associated indexer_update_all_views job can be adjusted through the crontab configuration. It is important not to let the cron job run too infrequently, because otherwise the changelog piles up and a single run suddenly has to process tens of thousands of records, which drastically degrades indexer performance for that one run.


# Switch catalogsearch_fulltext to scheduled (mview) mode
bin/magento indexer:set-mode schedule catalogsearch_fulltext

# Verify current mode for all indexers
bin/magento indexer:show-mode

# Example output
# Title                  Mode
# Catalog Search          Schedule
# Product Price           Schedule
# Product EAV              Update on Save

4. Partial reindex strategies in detail

A full reindex reprocesses every product in the catalog, regardless of whether it changed. On a catalog with 500,000 SKUs, of which only a few thousand are updated daily, that is enormously inefficient. Partial reindex in scheduled mode solves exactly this problem: through the mview system (materialized view), only products whose underlying data changed since the last run are re-indexed. The central table for this is catalogsearch_fulltext_cl (change log), where every relevant change lands as a row with the affected entity ID.

For strong indexer performance during partial reindexes, what matters most is which changes actually generate a changelog entry. Price changes, stock changes, and attribute changes flagged as "used in search" trigger an entry. Attributes that are not configured as searchable or filterable, on the other hand, generate no reindex trigger at all, which makes unused attribute flags a simple but often overlooked lever for better indexer performance: consistently marking attributes as searchable only where actually needed noticeably reduces the number of changelog entries.

Catalog type Recommended batch size Indexer mode Rationale
Up to 10,000 SKUs 100 (default) Update on Save Full reindex finishes in seconds, immediate effect desired
10,000 to 100,000 SKUs 300 Update by Schedule Balance between throughput and memory usage
100,000 to 500,000 SKUs 500 to 750 Update by Schedule Fewer bulk requests, more throughput per call
Over 500,000 SKUs 1000, plus parallelization Update by Schedule, multiple cron consumers A single process is no longer enough for acceptable runtimes

5. What happens under the hood: the Elasticsearch Bulk API

Whether full or partial, Magento never transfers documents to Elasticsearch one at a time, always through the _bulk API. Each batch from the PHP indexer is assembled into a single HTTP request with multiple lines in NDJSON format, where every action (typically index) is directly followed by the document data. This bundling is the main reason batch size has such a large effect on indexer performance in the first place: it directly determines the size of each individual bulk request.

Elasticsearch processes incoming bulk requests through a dedicated thread pool with a bounded queue size. If too many bulk requests are sent concurrently, for example because multiple cron consumers run in parallel, the queue can fill up and Elasticsearch starts rejecting requests with TOO_MANY_REQUESTS (HTTP 429). Magento does not automatically handle this with retry logic in every version, so overly aggressive parallelization can lead to failed, incomplete index runs that are often hard to distinguish from genuine data errors in the log.


POST /_bulk
{ "index": { "_index": "magento2_default_catalogsearch_fulltext_1", "_id": "10231" } }
{ "sku": "SHOE-BLK-42", "name": "Running Shoe Black", "price": 89.90, "visibility": 4 }
{ "index": { "_index": "magento2_default_catalogsearch_fulltext_1", "_id": "10232" } }
{ "sku": "SHOE-BLK-43", "name": "Running Shoe Black", "price": 89.90, "visibility": 4 }

// Response contains per-item status; a single failed item does not
// abort the whole batch, so failures must be checked individually
{
  "took": 42,
  "errors": false,
  "items": [
    { "index": { "_id": "10231", "status": 201 } },
    { "index": { "_id": "10232", "status": 201 } }
  ]
}

6. Cluster-side tuning during bulk runs

On the Elasticsearch side, there are two levers that meaningfully improve indexer performance during large reindex runs. The first is temporarily raising refresh_interval. By default, Elasticsearch refreshes the search index every second, which under a high rate of incoming bulk requests creates significant internal overhead because every refresh creates a new Lucene segment. During a full reindex run, the interval can be set to 30 seconds or even -1 (disabled), then reset to the default afterward.

The second lever is temporarily reducing the replica count to 0 during an initial bulk load, for example when fully repopulating an index after a migration. Without replicas, Elasticsearch does not need to additionally replicate every document to other nodes, which significantly increases write throughput. After the bulk load finishes, the replica count is set back to the production value, and Elasticsearch fills the replicas in the background without blocking ongoing operations.


# Temporarily relax refresh interval and disable replicas during a bulk load
curl -X PUT "localhost:9200/magento2_default_catalogsearch_fulltext_1/_settings" \
  -H "Content-Type: application/json" -d '{
    "index": {
      "refresh_interval": "30s",
      "number_of_replicas": 0
    }
  }'

# Restore production settings after the bulk load finishes
curl -X PUT "localhost:9200/magento2_default_catalogsearch_fulltext_1/_settings" \
  -H "Content-Type: application/json" -d '{
    "index": {
      "refresh_interval": "1s",
      "number_of_replicas": 1
    }
  }'

7. Monitoring: how to spot a slow indexer

Without monitoring, any statement about indexer performance remains speculative. The simplest entry point is the cron_schedule table, which lets you track the actual runtime of the indexer_update_all_views job over time. A steadily growing runtime trend is an early warning sign that the catalog is growing faster than the current configuration can handle. In addition, bin/magento indexer:status provides the current status, but no historical runtime data, which is why an external monitoring system is needed for reliable trend statements.

On the Elasticsearch side, it is worth checking the thread pool statistics for write and bulk via the _nodes/stats/thread_pool API. A high number of rejected entries shows directly that bulk requests were rejected, often because too many arrived at once. This metric is more reliable than raw CPU usage, because it measures the actual symptom of poor indexer performance rather than a resource utilization figure that can be high for other reasons too.


# Track catalogsearch_fulltext cron runtime over time
bin/mysql magento -e "
  SELECT job_code, scheduled_at, executed_at, finished_at,
         TIMESTAMPDIFF(SECOND, executed_at, finished_at) AS duration_seconds
  FROM cron_schedule
  WHERE job_code = 'indexer_update_all_views'
    AND status = 'success'
  ORDER BY scheduled_at DESC
  LIMIT 20;
"

# Check Elasticsearch bulk thread pool for rejections
curl -s "localhost:9200/_nodes/stats/thread_pool/write,bulk?pretty" \
  | grep -A 3 '"rejected"'

8. Common pitfalls during optimization

The most common mistake is drastically increasing batch size without adjusting PHP memory limits accordingly. A PHP process holding product data for 1000 documents in memory at once needs significantly more RAM than one holding 100 documents, especially for products with many attributes. The result is a fatal error from exhausted memory limits in the middle of a reindex run, which invalidates the entire batch and forces the process to start over.

A second pitfall is running multiple indexer:reindex calls in parallel without coordination, assuming that speeds up the process proportionally. In reality, parallel processes compete for the same database connections and the same Elasticsearch bulk thread pool, and beyond a certain level of parallelism, overall throughput drops instead of rising. A third, less frequently noticed pitfall concerns the MySQL side: missing or outdated indexes on the flat tables used by the indexer can slow down data retrieval for every batch, long before Elasticsearch is even involved.

9. Configuration comparison for different catalog types

The concrete configuration for optimal indexer performance differs significantly by catalog type. A B2B spare parts catalog with very many but data-sparse SKUs behaves differently from a fashion catalog with a few thousand products but extensive descriptions, images, and variant data. The table in section 4 provides rough guideline values, but the actually optimal batch size should always be determined empirically, by testing several values under realistic load and measuring the resulting runtime.

A proven approach is to start with the default value, increase batch size in increments of 100, and log runtime as well as PHP worker memory usage after every step. The point at which runtime improvement clearly flattens or memory errors appear marks the practical ceiling for indexer performance optimization through batch size alone. Beyond that point, only parallelization or additional hardware bring further improvements.

Mironsoft

Elasticsearch and indexer performance tuning for large Magento catalogs

Reindex runs that take hours instead of minutes?

We analyze batch size, indexer mode, and Elasticsearch cluster settings, and bring your catalog's indexer performance up to a production-ready level.

Performance audit

Baseline measurement of reindex runtimes and bulk throughput

Batch size tuning

Empirical determination of the optimal batch size for your catalog

Cluster tuning

Refresh interval, replicas, and thread pool configuration for bulk runs

10. Summary

Strong indexer performance on large Magento catalogs does not come from a single setting, but from the interplay of the right batch size, the right indexer mode, efficient partial reindex use through the mview system, and an Elasticsearch configuration that is temporarily adjusted during bulk runs. "Update by Schedule" mode is almost mandatory for catalogs of medium size and above, because it decouples bulk changes instead of processing them synchronously and individually.

The right batch size cannot be recommended as a blanket rule; it must be determined empirically for the specific catalog, depending on document size and available PHP memory. Monitoring cron runtimes and Elasticsearch thread pool statistics makes degradations in indexer performance visible before they become an acute operational problem. Consistently applying these building blocks keeps reindex runtimes within a predictable range even as the catalog grows.

Indexer performance on large catalogs, the essentials at a glance

Batch size

In env.php under indexer.batch_size, determine empirically between 300 and 1000 for large catalogs.

Indexer mode

"Update by Schedule" decouples changes from reindexing and prevents synchronous mass triggers on bulk imports.

Bulk API

Every batch is transferred as a single _bulk request, whose size depends directly on the batch size.

Cluster tuning

Raise refresh_interval and temporarily set replicas to 0 during a large bulk load.

11. FAQ: Indexer Performance for Large Catalogs

1Where do I set the batch size?
In app/etc/env.php under indexer.batch_size. The default is 100 documents per batch.
2What batch size for large catalogs?
Values between 300 and 1000 are often more efficient, the optimal value should be tested empirically.
3Which indexer mode for large catalogs?
Update by Schedule collects changes and processes them in the background in controlled batches.
4How does partial reindex work?
Through the changelog table catalogsearch_fulltext_cl, only changed entity IDs are processed.
5How does Magento send documents to Elasticsearch?
Through the Bulk API, every batch is bundled into a single NDJSON request.
6What does a higher refresh_interval achieve?
Less segment creation, less overhead, higher write throughput during bulk loads.
7Why can parallelization hurt?
Competition for database connections and the bulk thread pool lowers overall throughput past a certain point.
8How do I detect rejected bulk requests?
Through the rejected counters in the _nodes/stats/thread_pool API for the bulk pool.
9Which attributes generate a reindex trigger?
Only attributes marked as searchable or filterable. Other attributes generate no changelog entry.
10How do I start a performance optimization?
With a baseline measurement of the current runtime, then incrementally increase batch size and log the effect.