E40C [plugin-search]: Reindex All loses locale data due concurrent collection processing race condition · Issue #15884 · payloadcms/payload · GitHub
[go: up one dir, main page]

Skip to content

[plugin-search]: Reindex All loses locale data due concurrent collection processing race condition #15884

@kasparsuvi1

Description

@kasparsuvi1

Describe the Bug

When using the "Reindex All" button to reindex multiple collections simultaneously, not all locales are saved correctly in the search collection. Reindexing collections individually works as expected — all locales are persisted correctly.

To Reproduce

Configure the search plugin with multiple collections and localization enabled (3 locales)
Click the "Reindex" button and select all collections at once
Observe that some search documents are missing locale data for 1 or 2 of the configured locales
Expected: All search documents have data for all configured locales.

Actual: Some search documents are missing locale data. The issue is non-deterministic — different documents may be affected each time.

Root Cause

In src/utilities/generateReindexHandler.ts, all collections are reindexed concurrently via Promise.all:

const promises = collections.map(async (collection) => {
  try {
    await deleteIndexes(collection)
    await reindexCollection(collection)
  } catch (err) {
    // ...
  }
})

await Promise.all(promises)

The reindexCollection function processes each document by:

First locale → operation = 'create' → calls payload.create() to insert the search doc
Subsequent locales → operation = 'update' → calls payload.find() to locate the search doc, then payload.update()
When multiple collections run concurrently via Promise.all, they all write to the same search collection within a single shared transaction (initiated by initTransaction(req) before the Promise.all). This causes race conditions:

  • Concurrent create and find/update calls on the search collection's locale table interleave within the same transaction
  • A payload.find() call in the update path for one collection may not see a recently created document from another collection's create path, or may conflict with concurrent writes
  • The shared req.context.syncedDocsSet is mutated concurrently across all parallel collection promises, potentially causing deduplication logic to skip legitimate syncs

This explains why reindexing collections individually works correctly — there is no concurrency, so all create/find/update operations execute sequentially without conflicts.

Suggested Fix
Replace Promise.all with sequential execution:

for (const collection of collections) {
  try {
    await deleteIndexes(collection)
    await reindexCollection(collection)
  } catch (err) {
    const message = t('error:unableToReindexCollection', { collection })
    payload.logger.error({ err, msg: message })
  }
}

This serializes collection reindexing, eliminating the race condition while maintaining the same error handling behavior. The performance difference is negligible since reindexing is an infrequent admin operation and each collection still processes its documents in batches.

Link to the code that reproduces this issue

kasparsuvi1@83ef59d

Reproduction Steps

I made small changes to official test/plugin-search:

  1. localize Pages and Posts collections excerpt - To allow localized search
  2. localize search Collection excerpt and allow for admin to update search collection

Then run "pnpm dev plugin-search"

Then create documents for testing:

  1. Atleast 2 Pages with all different locales that have different excerpt defined per locale. For example "Some page 1 - en"
  2. Atleast 2 Posts with all different locales that have different excerpt defined per locale
  3. Navigate to search results -> reindex -> All collections
  4. Check the created search collection documents -> Some of them could have wrong data for some of the languages.

Which area(s) are affected?

plugin: search

Environment Info

@payloadcms/plugin-search: 3.75.0
payload: 3.75.0
Database: MongoDB 
Locales configured: 3

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      0