Skip to content

fix(plugin-search): locale race condition during multi-collection reindex#15738

Open
tolszak wants to merge 1 commit intopayloadcms:mainfrom
Kravento:fix/plugin-search-reindex-locale-race-condition
Open

fix(plugin-search): locale race condition during multi-collection reindex#15738
tolszak wants to merge 1 commit intopayloadcms:mainfrom
Kravento:fix/plugin-search-reindex-locale-race-condition

Conversation

@tolszak
Copy link

@tolszak tolszak commented Feb 24, 2026

Summary

Fixes a race condition in the search plugin's reindex handler where req.locale gets corrupted when multiple collections are reindexed concurrently via Promise.all.

Root Cause

generateReindexHandler processes all collections concurrently using Promise.all. Each collection iterates through locales, calling syncDocAsSearchIndex which triggers Payload local API calls (findByID, create, update). These calls invoke createLocalReq, which mutates req.locale in-place before yielding at await getLocalI18n().

When two collections are at different locale phases (e.g., collection A processing en while collection B processing pl), concurrent createLocalReq calls overwrite each other's req.locale. This causes the Drizzle adapter to write documents under the wrong locale in search_locales — for example, an English title stored as _locale='pl', leaving the en row empty.

The bug is non-deterministic: it depends on which async operations interleave at yield points, so it manifests as random documents missing titles in specific locales after a full reindex.

Fix

Restructure the reindex to process one locale phase at a time, with all collections running in parallel within the same locale:

// Before (buggy): collections × locales run concurrently
Promise.all(collections.map(reindexCollection))  // each iterates locales internally

// After (fixed): locale phases are sequential, collections parallel within each
for (locale of allLocales) {
  Promise.all(collections.map(col => reindexForLocale(col, locale)))
}

Since all concurrent operations within a phase set req.locale to the same value, the in-place mutation becomes idempotent and the race condition is eliminated — while preserving collection-level parallelism.

…ndex

The reindex handler uses Promise.all to process multiple collections
concurrently, but all concurrent operations share the same req object.
The Payload local API mutates req.locale in-place inside createLocalReq
before each async DB operation. When collections are at different locale
phases, concurrent createLocalReq calls overwrite req.locale with
different values mid-operation, causing documents to be stored under the
wrong locale in the search_locales table.

Fix by grouping operations by locale phase: process all collections in
parallel within the same locale, then move to the next locale. Since all
concurrent operations set req.locale to the same value, the in-place
mutation becomes idempotent and the race condition is eliminated.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant