RFC: make MongoDB localized index counts independent of locale count #18079
cbratschi
started this conversation in
Feature Requests & Ideas
Replies: 1 comment
Prototype resultsI implemented the first opt-in prototype locally in the MongoDB adapter. I have not opened a PR yet. The prototype:
Verified so far:
The remaining important boundaries are update atomicity and migration:
Before I turn this into a PR, which scope would be preferable?
I favour the first option because it provides a reviewable working slice without combining the adapter implementation and index-lifecycle migration in one change. |
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Decision requested
Agree on an opt-in normalized MongoDB index strategy for localized scalar
fields, initially covering
unique: trueand indexed equality queries. Thestrategy should preserve the existing public document shape while keeping the
number of MongoDB indexes constant as locales are added.
The first code contribution should be a focused integration test and prototype,
not an automatic migration or an immediate change of the default. Extending the
strategy to general
index: true, range queries, and sorting should follow onlyafter query plans and benchmarks are accepted.
Summary
Payload currently expands
index: trueandunique: trueon a localizedfield into one MongoDB index per configured locale. The number of indexes
therefore grows with both the number of localized indexed fields and the
number of locales.
This RFC proposes an adapter-level normalized index strategy whose index count
does not grow when locales are added. The first implementation should focus on
localized scalar fields with
unique: true, then extend the same representationto non-unique indexed fields once its query and sorting semantics are agreed.
The change is specific to MongoDB. Payload's PostgreSQL adapter stores
localized scalar values as rows in a locale table and creates one index on the
field and
_locale, independent of the number of configured locales.References
Problem
The MongoDB adapter currently copies a localized field's
indexanduniqueoptions to every locale-specific Mongoose schema path. For
Llocales andFlocalized indexed fields, those fields consume:
The collection's total is approximately:
MongoDB permits at most 64 indexes per collection. A collection with 25
locales and two localized unique fields already consumes 50 indexes before its
ordinary indexes and the
_idindex are counted. This makes the supportednumber of languages an accidental schema limit.
A focused probe against the current remote Payload
mainatb2379e821234f8a0475ac10e115ac4d771034916confirmed the current behavior:four locales and two localized unique fields generated eight sparse unique
indexes.
The multiplier also applies to Payload-owned fields. A draft-enabled collection
with localized content gets a localized
_statusfield withindex: true.Version collections copy the original indexed fields into
versioneven thoughuniqueness is disabled there.
An 11-locale schema probe produced these representative totals, including the
automatic
_idindex:path, localized_status, timestamps, and trashnameandpathnameThe unique-only milestone assumes one shared normalized unique index for the
main collection and one shared non-unique index for the corresponding fields in
version collections, where Payload disables uniqueness. Payload's localized
_statusremains per-locale in that milestone. The full target also normalizesordinary localized indexes such as
_status.The targets are illustrative until the representation is integrated into
Payload. Eleven locales do not by themselves exhaust MongoDB's limit, but the
version pattern already consumes half of it. Adding three more localized
indexed fields to that version schema raises the total from 32 to 65.
The existing issue was closed after a documentation-only change. A later issue
confirmed that collection-level locale paths are not accepted by Payload's
index configuration, and the documentation removed that workaround. No open
issue or pull request found during the current recheck implements a scalable
replacement. The limitation remains in the current MongoDB adapter.
Correctness requirements
Any replacement must preserve these semantics:
null, and empty values must retain the current sparse-indexbehavior.
application validation.
publish, restore, and trash flows must maintain the normalized data.
generated types, REST responses, or GraphQL schema.
than exposing an adapter-owned path.
behavior.
For non-unique
index: truefields, equality and range filters as well assorting need explicit acceptance criteria. A representation that enforces
uniqueness but makes normal localized queries fall back to collection scans is
not a complete replacement.
Proposed direction
Add an opt-in MongoDB adapter strategy, with a tentative configuration shape
such as:
'per-locale'would describe the existing behavior and provide a compatibilitypath. Whether
'normalized'becomes the 4.x default should be decided onlyafter migration and performance results are available.
For normalized localized unique fields, keep the public localized value in its
existing location and persist adapter-owned shadow entries in each MongoDB
document. One possible representation is:
pathmust be the canonical flattened Payload field path without the locale,not only the field's leaf name, so identically named nested fields cannot
collide. The adapter can then create one sparse unique compound multikey index:
Including
pathprevents values from different fields from colliding, whileincluding
localepermits the same value in different languages. The adaptermust define the shadow path in the Mongoose schema, reserve it from user field
configuration, and remove it from every public read shape. It must own
generation of this data after Payload field hooks and before the database write
so it always reflects the final persisted values.
Partial writes must update the public value and its shadow entry atomically.
In particular, changing one locale must replace exactly one matching
pathandlocale entry without discarding other locales. The prototype should determine
whether Payload's current update shapes can express that as one MongoDB update
pipeline or whether the adapter must read and rebuild the shadow entries inside
the existing request transaction.
updateMany, upsert, draft, and versionwrites need explicit coverage rather than inheriting this behavior by accident.
An equality query for a normalized field must be translated together with its
public value sanitization:
Removing the per-locale index without this query translation would preserve the
constraint but turn existing indexed lookups into collection scans. Operators
unsupported by the first milestone must continue using a compatible per-locale
index or reject normalized configuration clearly.
Non-unique localized indexed fields would require a separate internal array
and non-unique compound index. They cannot share the unique index because the
index's uniqueness applies to every entry. Queries would need to be translated
to target an entry with the selected field and locale, normally using
$elemMatch.Version schemas need the non-unique representation even during the unique-only
milestone. Payload copies indexed source fields below
versionwith uniquenessdisabled, so leaving those paths per-locale would retain the locale multiplier
for every version collection.
The first milestone should be limited to localized scalar fields with one BSON
value per locale. Arrays, blocks, JSON, rich text, points,
hasManyfields, andpolymorphic relationships should remain on the existing strategy until their
value encoding and query semantics are designed explicitly.
This representation is a design starting point, not yet a final API. A small
prototype should confirm Mongoose schema behavior, generated index keys,
collation, query plans, array update costs, and sorting semantics before the
public configuration is accepted.
Verified representation probe
A MongoDB 7.0.14 probe verified the proposed embedded-array shape with the
sparse unique compound multikey index:
path, locale, and value in a second document failed with MongoDBerror
E11000.shadow array, were accepted.
$elemMatchequality lookup used the compound index and examined oneindex key and one document for one returned document.
A partial index filtered by the existence of the first array entry also
enforced uniqueness, but MongoDB did not select it for the equality query
without a redundant filter predicate. The sparse index both preserved the
missing-value behavior in the probe and supported the translated query. Public
nulland empty-value behavior still needs comparison with Payload's currentsparse per-locale indexes in the integration test.
Performance considerations
A normalized compound index is not automatically faster for every operation.
Its main performance advantage is reducing the number of independent B-trees.
MongoDB must maintain every affected index on writes, so inserts and deletes
should benefit from touching one or two normalized index structures instead of
one structure per locale.
The total number of logical index entries does not fall by the same factor. A
document with values in 11 locales still contributes roughly 11 keys for that
field, and normalized keys also carry field and locale information. Disk usage
therefore needs measurement; it cannot be inferred from the index count alone.
For exact lookups, an index ordered by
path,locale, andvaluefollowsMongoDB's equality-prefix guidance when the query supplies all three values.
It should remain selective, but the current direct
path.deindex is narrowerand avoids an array predicate, so lower lookup latency must be demonstrated
with
explain()and a benchmark rather than assumed.Sorting is the larger risk. The normalized representation is multikey, and
MongoDB documents cases where sorting on a multikey path requires an in-memory
sort. Current locale-specific single-field indexes can directly provide sort
order. The initial implementation should therefore focus on unique/equality
lookups such as paths and names, or retain an explicit strategy for fields that
must support indexed localized sorting.
Write behavior also depends on how the shadow entries are maintained. If a
single-locale update rewrites the entire internal array, MongoDB may need to
recalculate more multikey entries than the current direct field update. The
prototype must compare single-locale updates, complete-document imports, and
concurrent conflicting writes.
For small CMS collections, these differences are unlikely to produce a visible
latency change. The immediate benefit is predictable index headroom and fewer
index structures; throughput and storage improvements are secondary and must
be benchmarked.
Alternatives and limitations
One compound index over every locale path
An index over
field.de,field.en, and every other locale creates tupleuniqueness rather than enforcing uniqueness independently for each locale. It
also has leftmost-prefix query limitations, still changes whenever locales are
added, and MongoDB permits at most 32 fields in a compound index.
Wildcard index
A wildcard index can cover dynamic locale paths, but MongoDB does not support
unique wildcard indexes. It therefore cannot preserve
unique: true.Index only selected locales
This reduces the index count but weakens correctness or continues to impose a
language limit. Payload would also need different validation behavior for
indexed and unindexed locales.
Application-only uniqueness validation
A query followed by a write has a race window. It cannot replace a unique
database constraint for concurrent requests.
Atlas-specific automation
Triggers or other Atlas services would be asynchronous, deployment-specific,
and outside the database adapter's transaction. Payload should provide the
same correctness on self-hosted MongoDB.
Transactionally maintained side collection
A side collection with one document per source document, field path, locale,
and value could use an ordinary unique compound index and avoid multikey sorting
limitations. It would, however, turn a source-document write into a
cross-collection write, require reliable transaction behavior from every
supported MongoDB-compatible backend, and require a join or two-phase lookup
for normal Payload queries. It remains a viable fallback if atomic shadow-array
maintenance or required sorting cannot be made safe, but it is a larger change
to Payload's query and transaction model.
Migration and rollout
Existing collections need an explicit, resumable migration:
per-locale indexes.
unique index.
explain().A rollback may leave the internal normalized data in place, but it must be able
to recreate the former per-locale indexes before switching queries back or
disabling normalized writes. Locale additions and removals after migration
should change document entries, not database indexes.
Migration state must be collection-specific. Application startup must not
silently drop legacy indexes or begin querying the shadow representation before
its backfill and index build are complete.
Sharded collections
MongoDB requires a unique index on a sharded collection to include the full
shard key as its prefix. Multikey and shard-key restrictions may prevent the
proposed document-array representation from providing cluster-wide uniqueness
for some sharding strategies.
The implementation must either support an explicitly documented compatible
shard-key/index shape or reject the normalized unique strategy for incompatible
sharded collections. It must not silently claim global uniqueness where MongoDB
cannot enforce it.
Suggested implementation sequence
scope in an RFC or design issue.
independent per-locale uniqueness.
unique: truefields,including concurrent writes and null/missing values.
explain()assertions in the sameprototype so the constraint does not replace an index with a collection
scan.
a follow-up.
Splitting the work after the design is accepted should make review safer than
combining schema generation, data migration, query translation, and every
localized index use case in one initial PR.
Acceptance tests
null, and empty values match the documented sparse semantics.changes.
cannot leave stale entries.
safely.
normalized index are ready.
explain().and performance boundary.
single-locale update, bulk import, and equality lookup performance with the
current per-locale strategy.
configuration validation clearly.
Open questions for maintainers
unique: true, orboth
unique: trueandindex: true?collection?
updateMany, and upserts maintain the shadowentries atomically without requiring a full source-document read?
single MongoDB index has one collation, so locale-specific collation rules
cannot be varied per shadow entry.
first release?
application, or document adapter APIs that migrations can call?
normalized indexing enabled by default only for new 4.x projects?
Implementation scope
A production-ready fix is medium-to-large rather than trivial. It crosses the
MongoDB schema builder, adapter-owned document transforms, query translation,
index lifecycle/migrations, configuration types, integration tests, and
documentation. A reproduction or schema-only prototype is a small contribution.
An opt-in implementation for localized unique scalar fields is a focused first
slice. A complete replacement for both
unique: trueandindex: truealsoneeds range/sort guarantees, migration tooling, and explicit sharding behavior.
A small reproduction/test PR is straightforward and useful, but it would not
fix the limit. The safest contribution path is to get agreement on this RFC,
then implement the normalized unique-field slice with migration boundaries
that allow the remaining query/index behavior to follow separately.
Recommendation
Proceed with the RFC and request agreement on the normalized representation and
rollout boundary before opening an implementation PR. The verified sparse
compound multikey representation makes a useful first slice technically
credible, while the unresolved migration, collation, partial-update, and sort
questions are large enough that implementing the complete feature without
maintainer direction would create avoidable review risk.
All reactions