Always excited to take on new projects and collaborate with innovative ideas.
+968 97716144
contact@aljulanda.info
https://aljulanda.info
Sultanate of Oman - Nizwa
Odoo feels sluggish? Before you migrate servers, check your worker mode, RAM sizing, memory/time limits, and PostgreSQL autovacuum health using Odoo's own official numbers.
Odoo itself, as a piece of software, is not the bottleneck in the vast majority of on-premise slowdowns. The bottleneck is almost always one of three things: the server is running in a mode that can't handle concurrent users, the RAM allocated per worker process doesn't match reality, or PostgreSQL has been quietly bloating for months because nobody looked at autovacuum. None of these show up as an error message. They just show up as "it's slow today," which is exactly why they get misdiagnosed as an Odoo bug and "fixed" by throwing a bigger VM at the problem instead of fixing the configuration.
Open your Odoo config file (usually /etc/odoo/odoo.conf) and look for the workers parameter. If it's absent or set to 0, you are running in threaded mode — a single process handling every request sequentially. That's fine for a developer laptop. It is not fine for five or ten people hitting the same server at once, because one long-running report or a heavy import will freeze everything else behind it. If your production instance has more than a handful of concurrent users and workers is still 0, you've likely already found your main problem before touching anything else.
Multi-processing mode splits incoming requests across separate worker processes instead of one thread trying to do everything. Odoo's own reference example for a production deployment lands on 8 HTTP workers plus 1 dedicated cron worker — that's the number worth using as your anchor point, not a guess pulled from a forum post. Watch your CPU load while your busiest users are active (top, htop, or your monitoring tool of choice) and compare it against how many workers are actually configured. If your CPU is sitting idle while users complain about lag, you're under-provisioned on workers, not on hardware. If CPU is pegged at 100% with only two or three workers configured, adding more workers on the same undersized box will just create swapping, not speed.
This is the step almost everyone skips, and it's the one that causes OOM (out-of-memory) kills — the server silently murdering a worker process mid-request, which the end user just experiences as "the page didn't load." Odoo's official sizing approach is a weighted formula:
RAM = workers × ((0.8 × light-request RAM) + (0.2 × heavy-request RAM))
In other words: most requests are light (list views, simple form loads), but a fifth of your traffic is heavy (reports, imports, large searches), and your RAM budget needs to cover both, not just the average. In Odoo's own worked example, this formula lands at roughly 3 GB for Odoo's own processes on an 8-worker setup — and that 3 GB figure is what then gets used to size the per-worker memory limits below. If your server has 4 GB total RAM and you're also running PostgreSQL, your mail relay, and maybe a Samba share on the same box, you're already underwater before a single user logs in.
Four parameters control how aggressively Odoo protects itself from a single runaway process eating the whole server. Odoo's official reference configuration sets them like this:
limit_memory_soft = 629145600 (≈600 MB) — once a worker crosses this during a request, Odoo lets that request finish, then kills and respawns the worker before it takes the next one.limit_memory_hard = 1677721600 (≈1.6 GB) — cross this and the worker is killed immediately, no grace period, to protect the rest of the system.limit_time_cpu = 600 — max CPU seconds a request gets before it's killed.limit_time_real = 1200 — max wall-clock seconds before a request is killed, covering time spent waiting on I/O too.limit_request = 8192 — number of requests a worker handles before it recycles itself, which helps against slow memory creep.If your config file still has the installer's defaults and you've never touched these, you're running blind. Undersized limits kill legitimate heavy reports mid-run. Oversized limits (or none at all) let one bad request drag your RAM usage up until the kernel's OOM killer picks a worker to sacrifice — often not even the one causing the problem.
The cron worker (max_cron_threads, set to 1 in the official example) handles scheduled actions — invoicing runs, stock valuation, automated emails. If it's missing or set to 0, your background jobs silently pile up and eventually start competing with live user traffic for resources.
Separately, if you use Odoo's LiveChat, multi-processing mode automatically starts a dedicated LiveChat worker that listens on the --gevent-port. Here's the catch: by default, HTTP requests keep hitting your normal HTTP workers instead of that LiveChat worker. You need a reverse proxy (Nginx is the usual choice) in front of Odoo that specifically redirects any path starting with /websocket/ to the LiveChat worker's port. Skip this and LiveChat either doesn't work properly or quietly degrades your normal worker pool's performance.
While you're touching the proxy config, make sure Odoo is started with --proxy-mode enabled. Without it, Odoo trusts the proxy's own headers instead of the real client's hostname, scheme, and IP — which breaks logging, security checks, and anything relying on knowing who's actually connecting.
You can get workers and RAM perfectly right and still have a slow system because the database itself is bloated. Every update and delete in PostgreSQL leaves behind dead tuples — old row versions that aren't cleaned up automatically in real time. That's autovacuum's job, and it's controlled by parameters in postgresql.conf. The autovacuum launcher daemon runs by default ("on"), but it depends on track_counts being enabled — if that's off, autovacuum effectively does nothing even though it looks "on" in the config.
Two defaults matter a lot as your database grows: autovacuum_vacuum_threshold defaults to just 50 tuples, and autovacuum_vacuum_insert_threshold (the trigger for insert-heavy tables) defaults to 1000 tuples. These thresholds were sane for small databases. Most self-hosted Odoo instances never revisit them as transaction volume grows into the millions of rows — stock moves, account move lines, mail messages — and autovacuum starts falling behind, leaving tables and indexes bloated. Every query against a bloated table scans more dead weight than it needs to, and no amount of worker or RAM tuning fixes that.
Run SELECT relname, n_dead_tup, n_live_tup FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 15; and see what comes back. A dead-tuple count that dwarfs live tuples on your biggest tables is your answer.
workers is set and greater than 0 — no multi-processing means no real concurrency.limit_memory_soft, limit_memory_hard, limit_time_cpu, and limit_time_real are explicitly set, not left blank.max_cron_threads is set and your proxy correctly routes /websocket/ traffic if LiveChat is in use.track_counts is on and query pg_stat_user_tables for dead tuple counts on your largest tables.Go through that list before you touch your budget for new hardware. Most of the time, the fix is a config file edit and a database vacuum, not a bigger server.
Image: dylanroscover — BY-SA (via Openverse)
Your email address will not be published. Required fields are marked *
Cookie preferences