Tuning Search API for faster indexingfor Drupal 11 , 10 , 9 , and 8

Last updated :  

Indexing 100,000 nodes one at a time, on cron, the same way you'd index 100, is a recipe for a cron run that never finishes within its time budget. Search API's own architecture already batches indexing — this lesson is about tuning the knobs that control how much work happens per batch, and where the real bottleneck usually is.

What you'll learn in this lesson

  • The cron indexing limit, and why bigger isn't automatically better
  • Why indexing performance is usually a "how much per item" problem, not a "how many items" one
  • Track changes so cron doesn't have to check every item every run

The cron batch size setting

// /admin/config/search/search-api/index/{index}/edit — "Cron limit"
// Or programmatically:
$index = \Drupal\search_api\Entity\Index::load('default_index');
$index->setOption('cron_limit', 50);
$index->save();

This caps how many items get indexed per cron run, not per site — same principle as the Batch API's chunking from the Cron and Queue Optimization section. Too low, and a large content backlog takes many cron cycles to fully index; too high, and one cron run risks exceeding your PHP execution time limit partway through.

The real bottleneck is usually per-item cost, not item count. An index item that triggers several expensive field-value computations (rendering a full node body through Twig just to extract plain text for indexing, say) costs far more per item than one that just indexes a title and a date. Before raising cron_limit to index "more, faster," profile what a single item's indexing actually costs — the fix is often reducing what gets computed per item, not processing more items per run.

Track changes, don't re-check everything

Search API's tracker system already knows which items are new, changed, or already indexed — it doesn't re-index unchanged content on every cron run by default. If you've written custom code that forces re-indexing more broadly than necessary (a batch operation that calls trackItemsUpdated() too liberally, say), you're paying indexing cost for content that never actually changed.

// Correct: only mark items that genuinely changed.
\Drupal::getContainer()->get('search_api.index_task_manager');
$index->trackItemsUpdated('entity:node', [$node->id()]);
// Not: forcing a full re-track of every item in the index on unrelated saves.

Quick check: raising cron_limit from 50 to 500 made indexing take noticeably longer per cron run without meaningfully reducing the total backlog over several days. What's more likely: the limit itself is wrong, or per-item indexing cost is the real problem? If you said per-item cost, you've understood why this lesson emphasizes profiling before tuning the batch size blindly.

Key takeaways

  • cron_limit controls items indexed per cron run — size it against your PHP execution time limit, not arbitrarily high.
  • Per-item indexing cost usually matters more than item count — profile what a single item's indexing actually does before raising the limit.
  • Search API's tracker already avoids re-indexing unchanged content — don't force broader re-tracking than a change actually warrants.

Coming up next

Indexing performance affects how fresh your search results are. Next, query-side caching — how fast search actually answers a visitor's query.