Problem/Motivation
SearchApiAlgoliaBackend::indexItems() returns every item ID it was given, regardless of whether the records actually reached Algolia. When a saveObjects() call fails, the exception is caught and logged as a warning, and execution continues to the unconditional return.
BackendSpecificInterface::indexItems() documents the return value as:
@return string[]
The IDs of all items that were successfully indexed.
Search API uses that return value to mark items as indexed in the tracker. Because the return is unconditional, failed items are marked indexed anyway.
The relevant code (3.1.3, src/Plugin/search_api/backend/SearchApiAlgoliaBackend.php, lines 277 and 283):
catch (AlgoliaException $e) {
$this->logger->warning(Html::escape($e->getMessage()));
}
}
}
return array_keys($items);
}
Two things make the impact considerably worse than one failed item per failure:
Items are batched per language before saving. Objects are grouped into $itemsToIndex[$language] and each group is sent as a single saveObjects() call. Algolia rejects the whole call, so one bad record loses every item in that language group for that batch.
The failure is invisible in the UI. The Search API index status page, drush search-api:status, and the tracker all report 100% indexed. The only trace is a warning-level log entry, which is easy to miss and is not surfaced anywhere in the search administration UI.
In practice this means an Algolia index can be missing most of its content while Drupal reports it as fully indexed, with no signal to an administrator that anything is wrong. Subsequent indexing runs will not retry the lost items, because the tracker believes they are done. On a production site, content silently stops being findable.
Steps to reproduce
Install Search API and Search API Algolia 3.1.3, configure an Algolia server and an index.
Ensure the index contains at least one item that will exceed Algolia's 10,000-byte record limit — a node with a long body field is enough. (Any saveObjects() failure reproduces this; oversized records are just the easiest trigger.)
Run drush search-api:index.
Observe [warning] Record at the position N objectID=... is too big size=.../10000 bytes in the output.
Check drush search-api:status — it reports 100% indexed.
Compare against the actual record count in Algolia, either in the dashboard or via the API.
Actual result
Drupal reported 1768 of 1768 (100%) indexed while the Algolia index contained 178 records. Roughly 90% of the corpus was missing, and every Drupal-side indicator showed a healthy, fully-indexed index.
Verified by enumerating the Algolia index through the browse API rather than relying on the dashboard's asynchronous entry count.
Expected result
Items whose saveObjects() call failed are not returned from indexItems(), so Search API leaves them unindexed in the tracker. The index status then reflects reality, and the next indexing run retries them.
Proposed resolution
Track which items were actually saved and return only those. Roughly:
$indexed = [];
foreach ($itemsToIndex as $language => $itemsPerLanguage) {
try {
// ... existing partialUpdateObjects() / saveObjects() call ...
$indexed = array_merge($indexed, $itemIdsFor($itemsPerLanguage));
}
catch (AlgoliaException $e) {
$this->logger->error('Failed to index @count items for language @language: @message', [
'@count' => count($itemsPerLanguage),
'@language' => $language,
'@message' => $e->getMessage(),
]);
}
}
return $indexed;
Two details worth deciding in review:
Mapping objects back to item IDs. With algolia_item_splitter enabled, one item becomes several objects, so the objects no longer map 1:1 to $items keys. The per-language grouping would need to carry the originating item ID, for example by building $itemsToIndex[$language] as a list of [itemId, object] pairs, or by keeping a parallel map.
Severity of the log entry. A failure that loses content is arguably error rather than warning, so that it surfaces in status reports and monitoring.
A related consideration, out of scope for this issue but worth noting: because a single oversized record fails an entire language group, one bad item can block many good ones. Falling back to a per-record retry when a batch fails would limit the blast radius to the record that is genuinely at fault.
Remaining tasks
Agree the approach for mapping split objects back to their source item IDs.
Patch and tests.
Review.
User interface changes
None, though the index status page will begin showing an accurate (lower) indexed count where failures are occurring. That is the point of the change, but it may surprise existing sites, which will suddenly see items reported as unindexed that were previously reported as done. Worth a line in the release notes.
API changes
None. This brings the implementation into line with the documented BackendSpecificInterface::indexItems() contract.
Data model changes
None.
I searched the issue queue and could not find an existing report for this; apologies if I have missed one and this is a duplicate.
Comments