A shop on Odoo with more than a hundred thousand products has a problem the demo database never shows: search stops being instant. Ours took eight to ten seconds with facets applied. The warehouse waited, the sales people waited, and everyone had learned to open a second tab while the first one thought.
What was actually slow
The first instinct is to blame the server, and it is almost always wrong. The box was not short of memory and the disks were not busy. The time went into a single query, and the query plan explained why.
A catalog search is not one question but several at once: find rows whose text contains a fragment, narrow them by a handful of attributes, and count how many products are left behind every facet value so the numbers next to the checkboxes stay correct. On a few thousand products this is free. On a hundred and ten thousand rows with long descriptions and several attribute tables joined in, the database has to walk almost everything - for every keystroke, for every visitor.
Indexes do not save this. A search for a fragment in the middle of a word cannot use an ordinary index at all, and the facet counts are recomputed from scratch on each request by definition: they depend on the filters the visitor has already chosen.
And the catalog was not alone on that machine. The same Odoo served thirty-nine other storefronts out of one pool of workers, so every slow search took a worker away from all of them at once.
Measure before you change
Two numbers decide what to do next: how long the database spends on the query, and how long the browser spends on everything else. If the answer were the browser we would have been optimizing the wrong half of the system for a week.
It was not the browser. The slow query log and the query plan pointed at the same place, and that made the decision easy.
What changed
Search moved out of the database and into Elasticsearch, which keeps its own index and is built for exactly this question: text, filters and counts at once. The search field on the site stays where it was, and the person typing in it sees nothing different except that the results are already there.
The important part is the direction of the arrow. Odoo remains the source of truth: products, prices and stock live there and are edited there. The index is built from the database and rebuilt when records change - never the other way round. If the index is lost, it can be thrown away and rebuilt; nothing of value is stored in it.
What it is made of
Nothing here was written from scratch for the sake of writing it. The Elasticsearch module was bought and then reworked, and on top of it sits a bridge to the shop theme, so that the search field, the shop page and the filters all speak to the index instead of the database.
Between Odoo and the storage there is a small service written in Go. It builds the filter query, asks Elasticsearch, counts the facets, and keeps the heavy fragments of a product page in Redis with a timeout measured in tens of milliseconds - if it does not answer in time, nobody waits for it.
Why a separate service at all: on a hosted Odoo you cannot run your own daemon next to it, there is no access. So the service lives outside and is reached over HTTP. On a server of your own it goes into the same subnet, and the hop costs almost nothing.
And the shop keeps working when the service does not. The same filter logic exists in Odoo in plain Python: slower, but correct. That path is not a leftover - it is the reason a deploy of the service is not an event anybody has to schedule.
What it costs
An honest answer includes the price, and it is not only the work of moving the search. There is now a second service to run, monitor and back up. Its index has to be rebuilt after bulk imports. Someone has to care when it falls behind, and the system needs a sensible behavior for the minute when it is unavailable.
That is the real trade: a permanent piece of infrastructure in exchange for search that no longer makes people wait. For a catalog of this size the trade is obviously worth it. For a catalog of five thousand products it is usually not.
When you do not need any of this
If your search is slow on a small catalog, the cause is almost never the size of the data. It is more often a query that joins more than it needs, a missing index on a field that is filtered on every page, an image that blocks rendering, or a snippet that re-renders the whole list on each keystroke. Those cost an afternoon, not a new service in production.
Which is why the first step is always the same: measure, then change. The measurement takes an hour and saves the week you would have spent optimizing the wrong half.
Still choosing the system itself? Then the arithmetic comes before the architecture: what a team of ten actually pays for Odoo, Salesforce, HubSpot and SAP.
Curious how the same systems behave day to day? Odoo in production: what holds, what breaks, what is not in the manual.