Read Replicas and 10,000 Users: Odoo 20’s Performance Architecture

Every growing Odoo deployment eventually meets the same ceiling. It does not arrive as a crash. It arrives as a slow suffocation: dashboards that take seven seconds instead of two, a month-end report that used to finish over a coffee and now needs a lunch break, a search bar with just enough lag to irritate your best users. The cause is rarely the application server. It is the database — one PostgreSQL instance being asked to do two very different jobs at once. Odoo 20 attacks that ceiling directly with native read-replica support, and Odoo positions the release at deployments of more than 10,000 concurrent users. This article explains what read replicas are, what Odoo actually changed, and who should care.

Diagram showing Odoo 20 read-replica architecture routing dashboard, report, and API reads to PostgreSQL replicas while writes stay on the primary

The single-database problem

An ERP runs on a simple premise: one database holds the truth. Sales orders, stock moves, journal entries, employee records — everything lives in one PostgreSQL database. When a warehouse worker scans a barcode, that is a write to the database. When the finance lead opens a profit-and-loss report, that is a read from the same database. When an integration pulls order data through the API, same database again.

The writes are not the problem. Even a busy instance processes a manageable number of writes per second: invoices created, orders confirmed, entries posted. The problem is reads. Dashboards, reports, searches, list views, kanban board aggregations, pivot tables and graph views all generate read queries, and a single dashboard can fire a dozen of them. Multiply that by hundreds of users with dashboards open, add scheduled reports running in the background, and the read load dwarfs the write load. Those reads compete with writes for the same CPU, memory, disk I/O and connection slots. One heavy report scanning a year of journal entries can starve the write that is trying to confirm a sales order. No amount of query tuning fully fixes that, because the constraint is architectural: reads and writes are fighting over the same physical resources.

What Odoo 20 changes: read/write splitting in the framework

Read replicas are the standard answer, and they have been standard practice in large web applications for decades. One primary database handles all writes and remains the canonical source of truth. One or more replica databases receive a continuous stream of those changes through PostgreSQL's built-in streaming replication, staying a few milliseconds behind the primary. The replicas are read-only copies.

PostgreSQL could always do the replication part. What was missing in Odoo — and what version 20 adds — is the application-level intelligence to route queries to the right place. When a user confirms a sales order, that write goes to the primary. When the same user opens a dashboard a moment later, those read queries can be served by a replica. The routing is transparent: users neither know nor care which database answered, because the framework decides based on the type of operation. The primary, unburdened of the heaviest read load, processes writes with lower latency. The replicas, freed from write locks and transaction overhead, serve dashboards and reports at full speed.

Consistency: which reads stay on the primary

Read/write splitting only works if it respects consistency. If you confirm an order and the screen immediately shows you yesterday's data, the feature is broken no matter how fast it is. Odoo 20's routing accounts for this:

  • Read-after-write stays on the primary. After a user changes data, that user's immediate follow-up reads need to see the change they just made. Those queries keep hitting the primary, so nobody ever sees their own work missing.
  • Write-then-read jobs stay on the primary. Background jobs and scheduled actions that write data and then query it in the same flow keep those queries on the primary too, avoiding a whole class of stale-read bugs in automations.
  • Heavy, tolerant reads move to replicas. Dashboard aggregations, scheduled report generation, searches in list and kanban views, pivot computations, and external API reads are the natural candidates. A revenue dashboard that is fifty milliseconds behind the latest posted invoice is not wrong in any way a user can perceive.

That split is where the practical win lives. In a single-database deployment, the finance team's month-end reporting could degrade interactive performance for everyone. With reporting routed to a dedicated replica, the consolidated profit-and-loss can grind away while the sales team keeps quoting without noticing.

The 10,000-user number, in context

The headline figure attached to Odoo 20 is 10,000 concurrent users — roughly double the ceiling that well-optimised Odoo 19 deployments were understood to reach, according to partner analyses of the release. Treat it as an architectural target rather than a benchmark promise: your mileage will depend on your modules, your custom code and your hardware. But the direction matters more than the exact number, because replicas change the shape of scaling itself.

Scaling a single database server up has diminishing returns. Doubling RAM and CPU does not double throughput, because lock contention, context switching and I/O bottlenecks get in the way. Adding another read replica, by contrast, roughly doubles read capacity, because the work spreads across independent instances with their own memory and processors. That is the difference between scaling up and scaling out, and it is why every large database-backed application eventually adopts replication.

It also changes where the hardware money goes. Instead of one ever-larger database server, a large deployment can run one primary and two replicas of modest size — often cheaper in total, and with better fault tolerance, since a replica is also a warm copy of your data. Each replica can even be tuned for its job: more memory for query caching on the reporting replica, connection throughput on the API replica.

Deployment notes: hosted and self-hosted

For Odoo.sh deployments, replica provisioning is handled at the platform level — it becomes a configuration choice rather than an infrastructure project. Self-hosted deployments need PostgreSQL streaming replication set up between the primary and the replicas, which is a well-documented, widely understood practice, and Odoo 20's baseline requirements already assume a modern stack: PostgreSQL 16 and Python 3.12 or newer.

Two companion changes in the Odoo 20 technical notes help the same cause. Tracking values in the chatter are no longer stored; tracking messages are now generated on the fly, which removes a steady stream of writes that older versions pushed into the database (administrators who still need stored tracking values can install a dedicated module). And many-to-one fields are now synced with the backend, with adding and removing values improved even on very large lists — the kind of unglamorous fix that heavy users feel every day.

Who actually needs this

Honesty check: most Odoo customers will never need a read replica. A forty-person company on Odoo Online is nowhere near the single-database ceiling, and replicas are not a remedy for slow custom code — a badly written computed field will be slow on a replica too. The audience for this feature is the top end: large manufacturing groups, retail chains with hundreds of locations, service organisations with thousands of field staff online at once, and any deployment where BI tools, e-commerce storefronts or warehouse systems poll the API hard enough to hurt interactive users. For those organisations, the absence of native replication was a legitimate objection in enterprise evaluations against SAP or Oracle. Odoo 20 removes it from the checklist — not with a screenshot-worthy feature, but with the kind of infrastructure work that decides how big a company can grow before it outgrows its ERP.

Further reading

Leave a Comment