Docdoo Home
DevOps & Infrastructure

Enterprise Architecture
The 100M+ Rows Standard

Examine the definitive engineering structure utilized exclusively for deploying mission-critical Odoo applications globally.

Designing Architecture for Odoo Enterprise

For Odoo in a large enterprise with massive data, a good design is never just “1 Odoo server + 1 PostgreSQL server”. It requires a layered architecture: edge, app pool, background pool, connection pooling, database high availability (HA), filestore strategy, and observability.
Odoo officially recommends multi-processing mode for production in Linux, deploying separate instances for web workers, cron workers, and event-driven workers (gevent) designed specifically for LiveChat and WebSockets. When sitting behind a reverse proxy, requests to /websocket/ must be explicitly routed to gevent workers and proxy_mode must be enabled.
Odoo provides a rule-of-thumb: 1 worker ≈ 6 concurrent users and a theoretical boundary per node of roughly (#CPU × 2) + 1. Consequently, for an enterprise scale, it is far healthier to span across multiple moderately-sized Odoo nodes rather than one monolithic giant server.

The Recommended Multi-Tier Architecture

Layer 1: Edge & Auth

WAF / CDN
Nginx / HAProxy Pair

Layer 2: Application Pools

Interactive Nodes

Node 1 ... Node N

Cron / Batch Nodes
Websocket Gevent

Layer 4: Data Tier

PgBouncer Pair
Primary
Standby

Layer 3: Shared Filestore

NFS / EFS Volume

Why this structure is the safest

Odoo possesses three profoundly distinct workload patterns:
  • Interactive HTTP for web users and APIs
  • Cron / Batch for schedulers, heavy recomputes, large imports, and integrations
  • Websocket / Gevent for long-lived connections (bus/livechat)
Odoo documentations clarify that isolating ordinary workers, crons, and websockets is indispensable. Mixing all of them on the identical node under enterprise volume frequently causes standard user requests to get bogged down by heavy batch jobs.

The Core Blueprint Layers

Layer 1 — The Edge

Stationed at the forefront, you employ:
  • WAF / CDN / DNS
  • Load balancer pairs
  • Reverse proxy (Nginx / HAProxy)
  • HTTPS termination
Odoo mentions that authentication behaves transparently in cleartext unless encrypted, meaning secure deployments must enforce HTTPS and utilize a reverse proxy when Odoo resides behind one.

Layer 2 — Odoo Application Pools

It is highly recommended to partition the application tier into three distinct pools.
1) Interactive nodes (App Pool) Dedicated exclusively to:
  • Web UI users
  • JSON-RPC / XML-RPC / REST APIs
  • Lightweight form, list, kanban, and reporting reads
2) Cron / batch nodes Dedicated exclusively to:
  • Scheduled actions
  • Massive data imports
  • Mass recalculations
  • Synchronized integrations and heavy jobs
3) Websocket / gevent nodes Dedicated exclusively to:
  • The /websocket/ routes
  • Livechat, bus, and persistent long-lived connections
This is not a mere preference. Odoo documents that /websocket/ necessitates routing to gevent workers, while ordinary HTTP requests should persist pointing strictly universally to standard web workers.

Layer 3 — Shared Filestore

In a multi-node cluster, all Odoo nodes must mount and interface with the exact same filestore. Odoo stores attachments inside the filestore, and access remains governed by Odoo via database lookups. Once access is authorized, Odoo advocates that the file delivery should be offloaded to the web server utilizing X-Sendfile / X-Accel headers, so Odoo does not squander precious Python CPU threads streaming binary files.
Practical implementation:
  • A shared POSIX storage or consistent clustered filestore.
  • Nginx X-Accel configured to offload attachment streaming.
  • Static module assets served directly by Nginx.

Layer 4 — Database Tier

For sizable enterprises, do not treat the database as "just a single PostgreSQL VM". Ensure you configure:
  • 1 PostgreSQL Primary dedicated to all Odoo writes.
  • 1 Standby HA primed for rapid failover.
  • 1 Read Replica / Reporting Replica offloading BI, analytical audits, and huge queries.
  • WAL archive + Continuous PITR backup.
PostgreSQL denotes that high availability and load balancing inherently rely on a primary/standby paradigm. A standby accepting read-only queries acts as a hot standby. Synchronous replication mitigates the risk of data loss during a failover, while asynchronous replication yields superior performance but risks minor transaction loss upon an abrupt switchover. For a transactionally-heavy ERP like Odoo, preserving a single write source is paramount.

Layer 5 — Connection Pooling

For vast estates, placing a PgBouncer pair proxying the PostgreSQL database is vital. PgBouncer offers session pooling, transaction pooling, and statement pooling.
  • Session pooling represents the safest default configuration for Odoo because it supports the entire spectrum of PostgreSQL features.
  • Transaction pooling is drastically more aggressive but fragments session-based behaviors. Always validate it against your specific add-ons and query patterns.

Healthy Request Pipeline Topology

🌐
01

Browser / API Request

User / Kasir mengeklik tombol 'Confirm Order' di POS atau sebuah aplikasi third-party menembak API secara masif ke server Odoo.

🛡️
02

Reverse Proxy & Load Balancer

Nginx menerima request. Mengurai HTTPS, dan mengecek rute. Jika itu minta file JPG/CSS, Nginx langsung kirim tanpa ganggu Odoo. Jika rute '/websocket/', ditendang ke Node Gevent. Jika normal, ditebar rata (Round Robin) ke kolam App Nodes.

🧠
03

Odoo Python App Pool

HTTP Worker dari Odoo menerima request. Mulai memvalidasi hak akses (Environment), menerjemahkan ORM create/write menjadi raw SQL query, dan merakit logika bisnis ERP.

🚦
04

PgBouncer (Connection Pooling)

Penyelamat nyawa Database! Menyerap 10,000 koneksi bar-bar dari ribuan Odoo App Nodes, lalu memasukkannya ke antrean rapi (Pool), dan mengumpankannya pelan-pelan murni hanya via 100-200 koneksi rapi ke PostgreSQL.

🗄️
05

PostgreSQL Primary Database

Jantung eksekusi tunggal. Memproses query SQL, menembakkan trigger, menyimpan perubahan, lalu menyebarkan Write-Ahead Log (WAL) ke Standby Replica untuk backup HA dalam hitungan milidetik.


5 Immutable Design Principles

1) Isolate HTTP users, Cron, and Websockets

This is the most recurring architectural flaw. Many implementations cram everything into a single pool. When a massive cron job triggers, user forms crawl, logins timeout, websockets sever, and CPU spikes.

2) Database Write must remain a Single Source

Eradicate any active-active aspirations for the primary Odoo database for now. Replicating multi-master architectures brings immense consistency complexities. Consistency supersedes all else in an ERP.

3) Never let Odoo continuously serve Statics & Attachments

Offloading static files to Nginx and expediting attachment downloads via X-Sendfile/X-Accel is the cheapest optimization with the highest immediate impact.

4) Built-in Observability

Relying on pg_stat_statements is mandatory for tracing the statistical planning and execution costs of all SQL queries. Activating csvlog or jsonlog enhances aggregation capabilities.

5) Autovacuum is NEVER optional

PostgreSQL's autovacuum will autonomously launch ANALYZE when data mutates significantly. In massive datasets, autovacuum is not an accessory feature; it fundamentally prevents Table Bloat transaction collapse.

How to Destroy Your ERP

👥🤖🌐

All Traffic (Users + Cron + Websockets)

Bottleneck
💣

1 Monolithic Odoo Node

Direct Local Filestore & DB

Why this fails instantly at scale:

  • No High Availability (Node death = Global Outage).
  • Zero Workload Isolation (A heavy cron stalls frontend cashiers).
  • Attachment streaming locks up active Python HTTP threads.
  • Vertical scaling hits a severe ceiling swiftly.

Demystifying Node Separation (The Config Secret)

The wisdom lies in the role per process, not in the server's designated hostname. Therefore, "odoo1" and "odoo2" are distinct solely via their startup configurations, not because Odoo harbors an innate mode named "node 1".
A single Odoo codebase can be launched simultaneously via multiple distinct config files. In Odoo's multiprocessing realm, --workers dictates the number of HTTP workers, whereas --max-cron-threads dictates the worker count exclusively dedicated to scheduled actions.
If you desire an isolated Cron backend node, you literally instruct the node to process crons and decisively reject HTTP requests by leveraging the http_enable = False flag.

The Ultimate 2-Node Recipe

If you are constructing a 2-Node Cluster and mandate that ONLY one node consumes background crons, adhere to this blueprint:

👨‍💻App Node (Frontend)

odoo1.conf

workers = 8

max_cron_threads = 0 # CRITICAL: 0 Crons

http_enable = True

proxy_mode = True

Serving active users. Fast, purely HTTP responding, isolated from heavy background tasks.

⚙️Cron Node (Backend)

odoo2.conf

workers = 2

max_cron_threads = 4

http_enable = False # CRITICAL: Rejects HTTP

proxy_mode = True

Not registered in Nginx Load Balancer. Dedicated 100% to devouring heavy scheduled scripts.

Then, on your Nginx Upstream (Load Balancer), you exclusively inject the IP of the App Node to accept traffic. The Cron Node remains completely hidden and secluded, happily chewing through enormous background tasks without ever impacting the user's frontend experience!