Why Odoo Slows Down at Scale — and How We Diagnose It

An Odoo instance that was fast at launch and is slow two years later did not get slow because Odoo is slow. It got slow because of a specific, findable set of causes. Here is the diagnostic order we work through.

A common shape of inbound enquiry: "Odoo was fast when we launched. Two years in, it's slow. Is Odoo just slow at scale?"

Almost never. An Odoo instance that degraded over two years did not degrade because the framework hit a ceiling. It degraded because of a specific, findable set of causes that accumulated while nobody was watching the performance surface. This is the diagnostic order we work through — roughly cheapest-to-check first.

1. Database: missing indexes and stale statistics

The first stop is always PostgreSQL. Odoo is a database application; most "Odoo is slow" problems are "a query is slow" problems.

Turn on slow-query logging and let it run through a representative day. The queries that surface are usually filtering or joining on columns that have no index — a custom field added years ago, searched constantly, never indexed. Odoo indexes fields declared with index=True, but custom fields and inherited search patterns frequently miss it.

The companion problem is stale planner statistics. PostgreSQL's query planner makes decisions from table statistics; on a large table that has grown faster than autovacuum kept up, the planner can choose a catastrophically wrong plan. ANALYZE and a look at autovacuum tuning belong in this first pass.

2. Computed fields that should be stored — or should not be

Computed fields are the most common self-inflicted Odoo performance wound. The trap runs both directions.

A non-stored computed field is recomputed every time it is read. Put one in a list view over a thousand rows and every page load triggers a thousand computations — each of which may run its own queries. The fix is store=True with a correct @api.depends.

But stored computed fields have the opposite failure mode: when a field in the depends chain changes, every dependent record recomputes. A poorly-scoped dependency on a high-write model produces a recomputation storm on every save. The diagnosis is reading the depends graph and asking, for each stored computed field, how many records recompute when its dependencies change.

3. The ORM N+1

The N+1 query pattern — one query to fetch a set, then one query per record in a loop — is framework-agnostic and Odoo is not immune. It hides inside innocent-looking code: a loop over records that touches a related field, a report that walks a one2many per line, an automated action that does a search inside a loop.

Odoo's ORM prefetches aggressively and hides much of this, which is exactly why N+1 in custom code is easy to miss — it works fine on ten records in testing and falls over on ten thousand in production. Diagnosis is query-count profiling on the slow action: if the query count scales with the data volume, you have found it. The fix is reading the related data in one batched query before the loop.

4. Attachment and filestore bloat

Every emailed PDF, every imported document, every generated report can land in ir.attachment. Years in, the filestore is enormous and nobody decided it should be. The direct performance cost is usually modest; the indirect costs are real — backups that take hours, a database that no longer fits in RAM, a filestore that has outgrown its volume.

The audit here: total attachment volume, growth rate, what is generating the bulk, and whether a retention policy exists. Usually it does not.

5. Cron contention and automated-action cascades

Scheduled jobs are invisible until they collide. A common pattern: several heavy crons all scheduled at midnight, all contending for the same workers and the same table locks, turning a quiet hour into the slowest hour of the day. Staggering them is often a five-minute fix for a problem that looked structural.

Automated actions are the subtler version. An automated action that fires on write, and whose logic itself writes records, can trigger a cascade — action A writes, which triggers action B, which writes, which re-triggers A. The cascade is fine at low volume and quietly murderous at scale. Diagnosis is mapping which automated actions write to models that other automated actions watch.

6. Workers, infrastructure, and the real ceiling

Only after the above is it worth questioning infrastructure. Worker count mismatched to CPU. A database server starved of RAM so the working set no longer fits in cache. A long-running request type sharing a worker pool with interactive traffic and blocking it. These are real, but they are last on the list because "add more hardware" applied to an unindexed query just buys a slightly slower arrival at the same wall.

The pattern behind the order

The diagnostic order is deliberate: it runs from specific and cheap to fix toward general and expensive. A missing index is a one-line fix with an enormous payoff. More hardware is expensive and often only postpones the real problem.

"Is Odoo slow at scale" is almost always the wrong question. The right question is "which of these six is happening in this instance" — and the answer is findable, usually within the first or second stop.