Table of Contents
Common Performance Issues
Before diving into optimization techniques, it is important to recognize the symptoms of poor Odoo performance. These are the most common issues that indicate your Odoo instance needs tuning:
Slow Page Loads
Pages taking 3+ seconds to load, especially list views and dashboards
Report Timeouts
PDF reports and exports timing out or taking minutes to generate
High CPU Usage
Server CPU consistently above 80%, causing slowdowns across the system
Memory Exhaustion
Workers being killed due to memory limits, causing request failures
Database Lock Contention
Concurrent operations blocking each other, leading to timeouts
Slow Search Operations
Search and filter operations taking excessive time to complete
Diagnosing Bottlenecks
Before optimizing, you need to identify where the bottlenecks are. Here are the key diagnostic steps. If you're experiencing slowness specifically with Odoo.sh, check out our guide on fixing slow Odoo.sh performance.
1. Enable Odoo Profiling
Odoo 14+ includes a built-in profiler. Enable it in your configuration:
[options]
log_level = debug
log_handler = :DEBUG
# For Odoo 15+
# Enable the profiling module and access via /web/profiler2. Check PostgreSQL Slow Query Log
Enable slow query logging to identify expensive database operations:
# Log queries taking longer than 1 second
log_min_duration_statement = 1000
# Log all statements (use cautiously in production)
# log_statement = 'all'
# Enable query statistics
shared_preload_libraries = 'pg_stat_statements'3. Monitor Server Resources
Use these commands to monitor system resources in real-time:
# CPU and memory usage
htop
# Disk I/O monitoring
iotop
# Network connections
netstat -tuln | grep odoo
# PostgreSQL active connections
psql -c "SELECT count(*) FROM pg_stat_activity;"4. Identify Heavy Modules
Some modules are known to cause performance issues:
- mail/discuss: Can generate excessive database writes
- stock: Complex inventory calculations on large datasets
- mrp: Bill of materials explosions can be expensive
- account: Large journal entries and reconciliation
- Custom modules: Review for N+1 queries and missing indexes
PostgreSQL Optimization
PostgreSQL is the heart of Odoo performance. These configurations can dramatically improve query performance:
# Memory Configuration
shared_buffers = 256MB # 25% of RAM (e.g., 2GB for 8GB server)
work_mem = 64MB # Per-operation memory
maintenance_work_mem = 256MB # For VACUUM, CREATE INDEX
effective_cache_size = 1GB # 50-75% of RAM
# SSD Optimization
random_page_cost = 1.1 # Lower for SSD (default 4.0 for HDD)
effective_io_concurrency = 200 # Higher for SSD
# Query Planner
default_statistics_target = 100 # More accurate query plans
# Connection Settings
max_connections = 100 # Tune based on workers
# Use PgBouncer for connection pooling in production
# Write-Ahead Log
wal_buffers = 16MB
checkpoint_completion_target = 0.9
# Autovacuum (aggressive for Odoo)
autovacuum = on
autovacuum_vacuum_scale_factor = 0.02
autovacuum_analyze_scale_factor = 0.01Index Optimization
Identify missing indexes with this query:
-- Find tables with sequential scans (potential missing indexes)
SELECT schemaname, relname, seq_scan, seq_tup_read,
idx_scan, idx_tup_fetch
FROM pg_stat_user_tables
WHERE seq_scan > 0
ORDER BY seq_tup_read DESC
LIMIT 20;VACUUM and ANALYZE
Schedule regular maintenance for optimal performance:
# Full database maintenance (run during low traffic)
vacuumdb --all --analyze --verbose
# Specific table maintenance
psql -d odoo_db -c "VACUUM ANALYZE ir_attachment;"
psql -d odoo_db -c "VACUUM ANALYZE mail_message;"
# Cron job example (weekly)
0 3 * * 0 postgres vacuumdb --all --analyze >> /var/log/vacuum.log 2>&1Connection Pooling with PgBouncer
PgBouncer reduces connection overhead for multi-worker setups:
[databases]
odoo = host=localhost port=5432 dbname=odoo_production
[pgbouncer]
listen_addr = 127.0.0.1
listen_port = 6432
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
max_client_conn = 200
default_pool_size = 20Worker Configuration
Proper worker configuration is critical for handling concurrent users efficiently. For containerized deployments, also check our Docker deployment guide for worker configuration in Docker environments.
[options]
# Worker Formula: (CPU cores * 2) + 1
# Example for 4-core server: (4 * 2) + 1 = 9 workers
workers = 9
# Cron worker (separate from HTTP workers)
max_cron_threads = 2
# Memory Limits (in bytes)
limit_memory_hard = 2684354560 # 2.5GB - worker killed above this
limit_memory_soft = 2147483648 # 2GB - worker recycled above this
# Request Limits
limit_request = 8192 # Max requests before worker recycled
limit_time_cpu = 600 # Max CPU seconds per request
limit_time_real = 1200 # Max wall-clock seconds per request
# Longpolling (for live chat, notifications)
longpolling_port = 8072| Parameter | Description | Recommendation |
|---|---|---|
| workers | Number of HTTP worker processes | (CPU cores * 2) + 1 |
| max_cron_threads | Threads for scheduled actions | 1-2 (rarely need more) |
| limit_memory_hard | Worker killed when exceeded | 2.5GB (adjust for RAM) |
| limit_memory_soft | Worker recycled after request | 2GB (80% of hard limit) |
| limit_time_cpu | Max CPU time per request | 600s (increase for heavy reports) |
| limit_time_real | Max wall-clock time per request | 1200s (2x CPU limit) |
Memory Calculation
Ensure you have enough RAM: (workers * limit_memory_hard) + PostgreSQL memory + OS overhead. For 9 workers with 2.5GB limit, you need at least 24GB RAM.
Caching Strategies
Implementing proper caching can dramatically reduce database load and improve response times:
Redis for Session Storage
Replace file-based sessions with Redis for better performance in multi-worker setups:
# Redis session backend (requires redis Python package)
session_redis_host = localhost
session_redis_port = 6379
session_redis_db = 0
session_redis_prefix = odoo_session:Query Caching with ORM Cache
Odoo has built-in caching for computed fields. Use it effectively:
from odoo import api, fields, models
class ProductProduct(models.Model):
_inherit = 'product.product'
# Use store=True for frequently accessed computed fields
total_sales = fields.Float(
compute='_compute_total_sales',
store=True # Caches result in database
)
@api.depends('sale_order_line_ids.qty_delivered')
def _compute_total_sales(self):
for product in self:
product.total_sales = sum(
line.qty_delivered
for line in product.sale_order_line_ids
)Static Asset Caching with Nginx
Configure Nginx to cache static assets and reduce Odoo load:
# Cache static assets for 1 year
location ~* /web/static/ {
proxy_pass http://odoo;
proxy_cache_valid 200 365d;
expires 365d;
add_header Cache-Control "public, immutable";
}
# Cache attachments
location ~* /web/content/ {
proxy_pass http://odoo;
proxy_cache_valid 200 30d;
expires 30d;
}CDN for Static Assets
Use a CDN like Cloudflare or AWS CloudFront to serve static assets closer to users. This reduces latency and offloads traffic from your server.
- Configure CDN to cache /web/static/* paths
- Set appropriate cache headers (1 year for versioned assets)
- Enable compression (gzip/brotli) at CDN level
- Use CDN for images and attachments where appropriate
Frontend Optimization
Optimize the frontend to improve perceived performance and reduce server load:
Asset Bundling and Minification
Odoo automatically bundles assets in production mode. Ensure you are running with proper settings:
# Run Odoo in production mode (not --dev mode)
./odoo-bin -c /etc/odoo/odoo.conf
# Regenerate assets after updates
./odoo-bin -c /etc/odoo/odoo.conf -d your_db --update=base
# Clear asset cache via SQL if needed
psql -d your_db -c "DELETE FROM ir_attachment WHERE name LIKE 'web.assets%';"Lazy Loading and Pagination
Optimize list views and related fields:
- Set reasonable
limiton tree views (default 80 is often too high) - Use
@api.onchangesparingly - each triggers a server round-trip - Avoid loading Many2one fields with large domains
- Use
prefetch_fieldsto optimize ORM queries
Reduce Unnecessary API Calls
Common causes of excessive API calls:
- Chatter/mail thread loading on every page
- Status bar widgets with complex domain filters
- Dashboard widgets polling too frequently
- Automated actions triggering on every record change
Hardware Recommendations
Choose the right hardware based on your user count and workload. Need help calculating your exact requirements? Use our server requirements calculator to get personalized recommendations.
| Concurrent Users | RAM | CPU | Storage |
|---|---|---|---|
| 1-10 users | 4GB | 2 vCPU | 50GB SSD |
| 10-50 users | 8GB | 4 vCPU | 100GB SSD |
| 50-200 users | 16GB | 8 vCPU | 250GB SSD |
| 200+ users | 32GB+ | 16+ vCPU | 500GB+ SSD |
Additional Considerations
- SSD is mandatory - NVMe SSDs provide best PostgreSQL performance
- Separate database server - For 50+ users, consider dedicated PostgreSQL server
- Network latency - Place database and application servers in same datacenter
- Backup storage - Plan for 2-3x database size for backup retention
Monitoring Setup
Set up proper monitoring to catch performance issues before they impact users:
Prometheus + Grafana Setup
The recommended monitoring stack for Odoo deployments:
version: '3.8'
services:
prometheus:
image: prom/prometheus
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
grafana:
image: grafana/grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=secure_password
node-exporter:
image: prom/node-exporter
ports:
- "9100:9100"
postgres-exporter:
image: prometheuscommunity/postgres-exporter
environment:
DATA_SOURCE_NAME: "postgresql://user:pass@db:5432/odoo?sslmode=disable"
ports:
- "9187:9187"Key Metrics to Monitor
System Metrics
- CPU utilization (target: under 80%)
- Memory usage (watch for OOM)
- Disk I/O wait (target: under 10%)
- Network throughput
PostgreSQL Metrics
- Active connections
- Query duration (p95, p99)
- Cache hit ratio (target: over 99%)
- Replication lag (if applicable)
Odoo Metrics
- Request latency (p50, p95)
- Error rate (5xx responses)
- Worker memory usage
- Longpolling connections
Business Metrics
- Active users count
- Cron job execution time
- Email queue size
- Report generation time
Recommended Alert Thresholds
| Metric | Warning | Critical |
|---|---|---|
| CPU Usage | over 70% | over 90% |
| Memory Usage | over 80% | over 95% |
| Disk Usage | over 75% | over 90% |
| Request Latency (p95) | over 2s | over 5s |
| Error Rate | over 1% | over 5% |
Performance Checklist
Use this checklist to ensure you have covered all optimization areas:
Database
Application
Caching
Frontend
Maintenance
Monitoring
Tip: Bookmark this page to track your optimization progress
Let OEC.sh Handle Performance Optimization
All of these optimizations are handled automatically when you deploy Odoo on OEC.sh. Focus on your business, not server tuning.
Frequently Asked Questions
Why is my Odoo so slow?
Slow Odoo performance typically stems from one or more of these issues: insufficient hardware resources (RAM, CPU), unoptimized PostgreSQL configuration, too few or too many workers, missing database indexes, heavy custom modules, or lack of caching. Start by checking your server resource utilization during peak usage to identify the primary bottleneck.
How many workers should I configure for Odoo?
The recommended formula is: workers = (CPU cores * 2) + 1. For example, a 4-core server should run 9 workers. However, this depends on your workload. For CPU-intensive operations like report generation, use fewer workers. Always monitor memory usage, as each worker consumes approximately 150-300MB RAM.
What's the best PostgreSQL config for Odoo?
The best PostgreSQL configuration for Odoo includes: shared_buffers set to 25% of RAM, effective_cache_size at 50-75% of RAM, work_mem at 64MB, and random_page_cost at 1.1 for SSD storage. Also enable aggressive autovacuum settings and use connection pooling with PgBouncer for multi-worker setups. These settings significantly improve query performance and database efficiency.
Should I use Redis with Odoo?
Yes, Redis significantly improves Odoo performance, especially in multi-worker setups. Use Redis for session storage (eliminates file-based session locking), as a cache backend for computed fields and method caching, and for queue management with Odoo Queue Job module. Configure Redis with appropriate memory limits and eviction policies.
How much RAM does Odoo need?
RAM requirements depend on your user count and workload. For 1-10 users, allocate at least 4GB RAM. For 10-50 users, you need 8GB minimum. For 50-200 users, plan for 16GB. Calculate RAM as: (workers × 250MB) + PostgreSQL memory + OS overhead. For 9 workers, you need at least 8GB RAM, but 16GB is recommended for production workloads with headroom.
How to diagnose Odoo performance issues?
Start by enabling Odoo profiling with log_level = debug and PostgreSQL slow query logging with log_min_duration_statement = 1000. Monitor server resources using htop for CPU/memory, iotop for disk I/O, and check PostgreSQL active connections. Use the Odoo profiler module (Odoo 15+) to identify slow requests. Look for N+1 queries, missing indexes, and heavy custom modules.
Does Odoo need SSD storage?
Yes, SSD storage is strongly recommended for Odoo deployments. PostgreSQL performance is heavily dependent on disk I/O, especially for complex queries and report generation. SSDs provide 10-100x faster random read/write speeds compared to HDDs. Set random_page_cost to 1.1 in PostgreSQL when using SSDs.
How often should I run VACUUM on PostgreSQL?
For Odoo databases, configure autovacuum to run continuously with aggressive settings. Additionally, schedule a weekly VACUUM ANALYZE during low-traffic periods to reclaim space and update statistics. For heavily-updated tables (like ir_attachment, mail_message), consider more frequent manual VACUUM operations.