Skip to content
Adarsh Trivedi
Contents
  1. Executive summary
  2. Why the query shape mattered
  3. What changed in the selector
  4. What I checked
  5. Scope
  6. Takeaways

Performance

Making a CMS Page Selector Fast with MongoDB B-tree Indexes

A page selector in an internal CMS was slower than it needed to be. The useful fix was not a cache or a new service — it was shaping the search query so MongoDB could walk the existing B-tree index.

Adarsh Trivedi · July 10, 2025 · 7 min read

Takeaway: An index only helps when the query can use it. For known prefix search, a bounded range on the indexed field can be much cheaper than a broad pattern match.

Executive summary

This was a small performance fix in a CMS admin UI. Editors needed to select pages by UID. The generic selector was too slow for that flow, especially when searching through a larger pages collection.

The change replaced the default relationship selector with a custom page selector. It fetched only the fields the UI needed, debounced input, and changed the search from a broad match to an indexed prefix range.

AreaBeforeAfter
UIGeneric relationship selectorPurpose-built page selector
ResponseMore data than the dropdown neededProjection for only the option label and value
SearchBroad UID matchLowercase UID prefix range

Why the query shape mattered

A MongoDB B-tree index stores values in sorted order. That makes equality and range scans cheap: the database can seek to a starting point in the index and walk forward until the range ends.

That property is useful for prefix search when the identifier format is constrained. For normalized lowercase ASCII-style UIDs under simple binary collation, if a user types par, matching UIDs can be queried as a range:

uid >= "par"
uid <  "par￿"

The upper bound uses a high Unicode sentinel. That is a convenient bound for this restricted identifier alphabet, not a universal Unicode prefix-search rule. The important part is the intent: this dropdown is searching from the start of a normalized UID, not anywhere inside it.

That distinction is important. A contains-style regex can be convenient for users, but it often prevents the database from using the sorted index efficiently. The index is ordered by the beginning of the value; it cannot jump directly to “contains this substring somewhere.”

What changed in the selector

The selector kept the UI simple. It debounced search input, queried the pages collection, and asked only for the option value and label the dropdown needed.

fields: ["id", "uid"]
limit: 10

The search condition changed to a bounded range:

where: input
  ? {
      and: [
        { uid: { greater_than_equal: input.toLowerCase() } },
        { uid: { less_than: input.toLowerCase() + "\uFFFF" } },
      ],
    }
  : {}

The PR also kept a separate lookup for selected IDs. That mattered because a selected value may not be present in the current search results. The dropdown still needs to render the saved label when the form loads.

What I checked

The useful part is the method: compare the query plan before and after, not just the UI feeling faster. On representative data, validate the winning plan, keys examined, documents examined, and response shape.

The checks worth keeping are:

  • Does the winning plan use the UID index?
  • How many keys were examined?
  • How many documents were examined?
  • Is the query returning only fields the UI needs?
  • Does the selected value still render when it is not in the current search result?

Scope

I still kept the scope small. This was not a generic search framework. It was a specific selector for a known access pattern: editors search page UIDs by prefix and choose one or more pages.

Takeaways

  • Do not assume an indexed field is fast. Check whether the query shape can use the index.
  • Prefix search and contains search are different database problems.
  • Admin dropdowns should fetch the smallest useful projection, especially on every keystroke.
  • A small custom control can be better than a generic relationship field when the access pattern is known.
  • Keep the UX honest: if you optimize for prefix search, design the placeholder and behavior around prefix search.