Odoo Performance Tuning: PostgreSQL and Server Optimization

Expert guide to Odoo 19 performance tuning. Covers PostgreSQL configuration, indexing, query optimization, Nginx caching, and server sizing for enterprise deployments.

E
ECOSIRE Research and Development Team
|19 de marzo de 202611 min de lectura2.3k Palabras|

Parte de nuestra serie Performance & Scalability

Leer la guía completa

Ajuste del rendimiento de Odoo: PostgreSQL y optimización del servidor

Una instancia lenta de Odoo cuesta dinero en pérdida de productividad y usuarios frustrados. La buena noticia: la mayoría de los problemas de rendimiento de Odoo se pueden solucionar sin actualizaciones de hardware. La mala noticia: diagnosticar la causa raíz requiere comprender toda la pila: Python, PostgreSQL, Nginx, Redis y la capa de red.

Esta guía cubre el ciclo de vida completo de optimización del rendimiento para Odoo 19 Enterprise: identificar cuellos de botella, ajustar PostgreSQL, optimizar la configuración del servidor Odoo, configurar el almacenamiento en caché de Nginx y ajustar el tamaño de su infraestructura para su recuento de usuarios y volumen de transacciones.

Conclusiones clave

  • El ajuste de PostgreSQL ofrece las mayores ganancias de rendimiento (50-300 % en instalaciones típicas)
  • Los buffers compartidos deben configurarse en el 25% de la RAM disponible como punto de partida
  • El ORM de Odoo genera N+1 consultas que pueden ser capturadas con pg_stat_statements
  • Los índices de los campos filtrados con frecuencia (company_id, state, date) son obligatorios
  • El almacenamiento en caché del proxy Nginx sirve activos estáticos sin acceder al servidor Odoo
  • La configuración del trabajador afecta directamente la capacidad de los usuarios simultáneos
  • El almacenamiento en caché de sesiones de Redis reduce la carga de la base de datos para la autenticación
  • Los cronogramas de vacío y análisis deben ajustarse para cargas de trabajo de Odoo de alta escritura

Diagnóstico de cuellos de botella en el rendimiento

Antes de ajustar algo, identifique dónde se está gastando realmente el tiempo. La optimización ciega desperdicia esfuerzo.

Habilitar el registro de consultas de Odoo:

# odoo.conf
[options]
log_level = info
logfile = /var/log/odoo/odoo.log

# For SQL query logging (development/staging only):
log_handler = odoo.sql_db:DEBUG

Habilite el registro lento de consultas de PostgreSQL:

# /etc/postgresql/15/main/postgresql.conf
log_min_duration_statement = 1000  # Log queries taking > 1 second
log_line_prefix = '%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h '
log_checkpoints = on
log_connections = on
log_lock_waits = on

Instale pg_stat_statements (la extensión de PostgreSQL más valiosa):

-- Enable the extension
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

-- Find the top 20 slowest queries
SELECT
    round(total_exec_time::numeric, 2) AS total_ms,
    calls,
    round(mean_exec_time::numeric, 2) AS mean_ms,
    round((100 * total_exec_time / sum(total_exec_time) OVER ())::numeric, 2) AS pct,
    left(query, 100) AS query
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 20;

-- Reset statistics after tuning to measure improvement
SELECT pg_stat_statements_reset();

Identificar consultas N+1 en Odoo:

Las consultas N+1 ocurren cuando Odoo carga una lista de registros y luego realiza una consulta por registro para recuperar datos relacionados. Busque patrones como este en pg_stat_statements:

-- This query appearing 500 times in a single page load = N+1 problem
SELECT * FROM res_partner WHERE id = $1

La solución es utilizar el mecanismo prefetch_ids de Odoo o agregar select a su consulta ORM:

# Bad: triggers N+1 for partner on each order
for order in orders:
    print(order.partner_id.name)  # One query per order

# Good: prefetch partner data in one query
orders = self.env['sale.order'].search([...])
orders.mapped('partner_id')  # Forces prefetch
for order in orders:
    print(order.partner_id.name)  # No additional queries

Ajuste de configuración de PostgreSQL

PostgreSQL se entrega con valores predeterminados conservadores diseñados para ejecutarse en cualquier hardware. Para un servidor de producción Odoo, estos valores predeterminados deben ajustarse.

Configuración de memoria (para un servidor de 32 GB de RAM):

# /etc/postgresql/15/main/postgresql.conf

# Shared buffers: 25% of RAM
shared_buffers = 8GB

# Work memory: per-operation memory for sorts/joins
# Start conservative, increase if you see disk sorts
work_mem = 64MB

# Maintenance work memory: for VACUUM, CREATE INDEX
maintenance_work_mem = 2GB

# Effective cache size: tells planner how much OS cache is available
# Set to 75% of total RAM
effective_cache_size = 24GB

# WAL settings for better write performance
wal_buffers = 256MB
checkpoint_completion_target = 0.9
checkpoint_timeout = 15min
max_wal_size = 4GB
min_wal_size = 1GB

Configuración de conexión:

# Maximum connections (Odoo workers × 2 + headroom)
max_connections = 200

# For connection pooling with PgBouncer
# If using PgBouncer, reduce to 50-100

Configuración del planificador de consultas:

# Enable parallel query execution
max_parallel_workers_per_gather = 4
max_parallel_workers = 8
max_worker_processes = 16

# SSD storage: random_page_cost should equal seq_page_cost
random_page_cost = 1.1  # Default is 4.0 (for spinning disk)
seq_page_cost = 1.0

# Increase statistics target for better query plans on Odoo's large tables
default_statistics_target = 200

Ajuste de vacío automático para cargas de trabajo de alta escritura:

Los módulos de inventario, contabilidad y mensajería de Odoo generan un gran tráfico INSERTAR/ACTUALIZAR. La configuración predeterminada de vacío automático es insuficiente:

autovacuum = on
autovacuum_max_workers = 5
autovacuum_naptime = 30s
autovacuum_vacuum_threshold = 50
autovacuum_analyze_threshold = 50
autovacuum_vacuum_scale_factor = 0.01   # Vacuum when 1% of rows are dead
autovacuum_analyze_scale_factor = 0.005  # Analyze when 0.5% of rows change
autovacuum_vacuum_cost_delay = 2ms       # Reduce I/O throttling

Índices de bases de datos críticos

Los índices faltantes son la segunda causa más común de problemas de rendimiento de Odoo después de una mala configuración. Odoo crea índices para claves primarias y algunas claves externas, pero muchos campos comúnmente filtrados carecen de índices.

Compruebe los índices que faltan usando la vista pg_missing_fk_indexes:

-- Find foreign keys without indexes
SELECT
    tc.table_name,
    kcu.column_name,
    ccu.table_name AS foreign_table_name,
    pg_relation_size(tc.table_name::regclass) AS table_size
FROM information_schema.table_constraints tc
JOIN information_schema.key_column_usage kcu
    ON tc.constraint_name = kcu.constraint_name
JOIN information_schema.constraint_column_usage ccu
    ON ccu.constraint_name = tc.constraint_name
WHERE tc.constraint_type = 'FOREIGN KEY'
AND NOT EXISTS (
    SELECT 1 FROM pg_indexes pi
    WHERE pi.tablename = tc.table_name
    AND pi.indexdef LIKE '%' || kcu.column_name || '%'
)
ORDER BY table_size DESC;

Índices esenciales para Odoo 19:

-- Sale orders (most queried table)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_sale_order_state
    ON sale_order(state);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_sale_order_company_date
    ON sale_order(company_id, date_order DESC);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_sale_order_partner
    ON sale_order(partner_id) WHERE state != 'cancel';

-- Account moves (invoicing)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_account_move_state_type
    ON account_move(state, move_type);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_account_move_company_date
    ON account_move(company_id, invoice_date DESC);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_account_move_partner
    ON account_move(partner_id) WHERE state = 'posted';

-- Account move lines (most queried for reconciliation)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_account_move_line_account_reconcile
    ON account_move_line(account_id, reconciled, date);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_account_move_line_move_date
    ON account_move_line(move_id, date);

-- Stock moves (inventory)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_stock_move_state_product
    ON stock_move(state, product_id);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_stock_quant_product_location
    ON stock_quant(product_id, location_id);

-- Mail messages (can grow very large)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_mail_message_res_model_id
    ON mail_message(res_model, res_id);
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_mail_message_date
    ON mail_message(date DESC);

-- IR rule performance (access control)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_ir_rule_model_groups
    ON ir_rule(model_id);

Configuración del trabajador de Odoo

La cantidad de trabajadores de Odoo determina cuántas solicitudes simultáneas puede manejar el servidor.

Fórmula para el recuento de trabajadores:

Workers = (CPU_cores × 2) + 1
Memory per worker: 256MB - 512MB depending on workload

Example for 8 CPU cores, 32GB RAM:
Workers = (8 × 2) + 1 = 17
Memory check: 17 × 512MB = 8.5GB (well within 32GB)

Configuración del trabajador de odoo.conf:

[options]
# Worker processes
workers = 17

# Limits to prevent runaway workers
limit_memory_hard = 2684354560  # 2.5GB hard limit (kills worker)
limit_memory_soft = 2147483648  # 2GB soft limit (triggers gc)
limit_time_cpu = 600            # CPU seconds per request
limit_time_real = 1200          # Wall clock seconds per request
limit_request = 8192            # Requests before worker restart

# Long polling (for live notifications)
longpolling_port = 8072

Comprensión de los tipos de trabajadores:

Odoo utiliza dos tipos de trabajadores:

  1. Trabajadores HTTP (workers configuración): maneja todas las solicitudes web
  2. Trabajadores cron (1 reservado): ejecuta acciones programadas en segundo plano

El trabajador cron siempre está ejecutándose pero no cuenta para su capacidad HTTP. Asegúrese de que al menos 1 trabajador cron esté disponible incluso durante la carga máxima.


Configuración de Nginx para rendimiento

Nginx se ubica frente a Odoo y maneja la terminación TLS, el servicio de archivos estáticos y, opcionalmente, el almacenamiento en caché.

Configuración de Nginx de alto rendimiento:

upstream odoo {
    server 127.0.0.1:8069 weight=1 fail_timeout=0;
}

upstream odoochat {
    server 127.0.0.1:8072 weight=1 fail_timeout=0;
}

# Cache zone for static assets
proxy_cache_path /var/cache/nginx/odoo
    levels=1:2
    keys_zone=odoo_cache:100m
    max_size=1g
    inactive=60m
    use_temp_path=off;

server {
    listen 443 ssl http2;
    server_name your-odoo.com;

    # Gzip compression
    gzip on;
    gzip_types text/plain text/css application/json application/javascript
               text/xml application/xml application/xml+rss text/javascript;
    gzip_min_length 1000;
    gzip_comp_level 6;

    # Static file caching
    location /web/static/ {
        proxy_cache odoo_cache;
        proxy_cache_valid 200 7d;
        proxy_cache_use_stale error timeout updating
            http_500 http_502 http_503 http_504;
        add_header X-Cache-Status $upstream_cache_status;
        expires 7d;
        proxy_pass http://odoo;
    }

    # Long polling for live chat/notifications
    location /web/longpolling {
        proxy_pass http://odoochat;
        proxy_read_timeout 3600s;
        proxy_connect_timeout 300s;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }

    # Main application
    location / {
        proxy_pass http://odoo;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_read_timeout 720s;
        proxy_connect_timeout 300s;
        proxy_buffering on;
        proxy_buffer_size 128k;
        proxy_buffers 4 256k;
        proxy_busy_buffers_size 256k;

        # Security headers
        add_header X-Frame-Options DENY;
        add_header X-Content-Type-Options nosniff;
        add_header Referrer-Policy strict-origin-when-cross-origin;
    }
}

Redis para sesión y caché

Redis reduce significativamente la carga de la base de datos para la gestión de sesiones y el caché ORM de Odoo.

Instalar y configurar Redis:

# Install Redis
sudo apt install redis-server

# Configure Redis for Odoo (max 4GB memory, LRU eviction)
sudo nano /etc/redis/redis.conf
# redis.conf
maxmemory 4gb
maxmemory-policy allkeys-lru
save ""  # Disable persistence for pure cache
tcp-keepalive 300

Configurar Odoo para usar Redis:

# odoo.conf
[options]
# Redis for session storage
session_redis_host = 127.0.0.1
session_redis_port = 6379
session_redis_prefix = odoo_session_

# Redis for IR rules and ORM cache
cache_redis_host = 127.0.0.1
cache_redis_port = 6379

Monitoreo y Gestión Continua del Desempeño

Configure pgBadger para el análisis de registros de PostgreSQL:

# Install pgBadger
sudo apt install pgbadger

# Generate report from PostgreSQL logs
pgbadger /var/log/postgresql/postgresql-15-main.log \
    -o /var/www/html/pgbadger/index.html \
    --format html \
    --top 20

Métricas clave a monitorear:

MétricaUmbral de advertenciaUmbral crítico
Tiempo de carga de la página> 2 segundos> 5 segundos
Tiempo de consulta de base de datos> 100 ms promedio> 500 ms promedio
Memoria del trabajador> 80% del límite> 95% del límite
Conexiones PostgreSQL> 70% del máximo> 90% del máximo
IOPS de disco> 80% de provisionado> 95% de provisionado
Proporción de aciertos de caché< 95%< 90%

Preguntas frecuentes

¿Cuál es la especificación mínima del servidor para 50 usuarios simultáneos de Odoo?

Para 50 usuarios simultáneos con un volumen de transacciones moderado: 8 vCPU, 32 GB de RAM, 500 GB de SSD (se prefiere NVMe). La base de datos PostgreSQL de Odoo es el principal cuello de botella de E/S, por lo que el almacenamiento rápido importa más que la velocidad bruta de la CPU. Configure 13 trabajadores (8 × 2-3 para espacio libre), 8 GB de búfer compartido y asegúrese de que su base de datos esté en el volumen SSD.

¿Cómo puedo diagnosticar si mi lentitud en Odoo es Python o PostgreSQL?

Utilice el generador de perfiles integrado de Odoo (Configuración → Técnico → Rendimiento → Generador de perfiles en modo desarrollador) para registrar una operación lenta. El flamegraph mostrará si se dedica tiempo al código Python o esperando los resultados de SQL. Si dominan las consultas SQL, concéntrese en el ajuste y los índices de PostgreSQL. Si Python domina, busque el almacenamiento en caché @api.depends faltante o ineficiencias en el código personalizado.

¿Debo utilizar PgBouncer para agrupar conexiones?

Sí, para implementaciones con más de 30 trabajadores de Odoo o mucho tráfico de API. PgBouncer en modo de transacción permite a muchos trabajadores de Odoo compartir un grupo más pequeño de conexiones PostgreSQL reales, lo que reduce la sobrecarga por conexión. Configure max_connections en PostgreSQL en 50-100 cuando use PgBouncer, luego configure el tamaño del grupo de PgBouncer para que coincida con su recuento de trabajadores de Odoo.

¿Con qué frecuencia debo ejecutar VACUUM ANALYZE en una base de datos Odoo?

Autovacuum maneja esto automáticamente si se configura correctamente. Después del ajuste anterior (factores de escala agresivos, más trabajadores), el vacío automático debería funcionar continuamente en las mesas activas. Ejecute SELECT schemaname, tablename, last_vacuum, last_autovacuum, last_analyze, last_autoanalyze FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 20; para verificar que las mesas se aspiren con frecuencia.

¿Cuál es el impacto de demasiados trabajadores de Odoo?

Cada trabajador de Odoo consume entre 256 y 512 MB de RAM como mínimo. Demasiados trabajadores provocan el agotamiento de la memoria, lo que provoca que los trabajadores fallen (limit_memory_hard), lo que genera errores HTTP 500 para los usuarios. Además, demasiadas conexiones PostgreSQL (trabajadores × max_db_connections) pueden saturar la base de datos. Comience con la fórmula (CPU×2+1), controle la memoria bajo carga y ajústela si es necesario.


Próximos pasos

El ajuste del rendimiento de Odoo es un proceso iterativo. Una sola sesión de ajuste ofrece ganancias significativas, pero el rendimiento sostenido requiere monitoreo continuo, análisis de índice periódico y ajustes de configuración a medida que crece el volumen de datos.

ECOSIRE proporciona auditorías de rendimiento de Odoo para implementaciones empresariales, identificando las optimizaciones de mayor impacto para su carga de trabajo, patrones de transacciones e infraestructura específicos. Nuestros ingenieros han ajustado las instalaciones de Odoo desde PYMES de 10 usuarios hasta implementaciones empresariales de 500 usuarios.

Solicitar una auditoría de desempeño de Odoo a ECOSIRE →

Comparta las especificaciones actuales de su servidor, el número de usuarios y los síntomas que está experimentando, y nuestro equipo identificará las causas fundamentales y entregará un plan de optimización priorizado.

E

Escrito por

ECOSIRE Research and Development Team

Construyendo productos digitales de nivel empresarial en ECOSIRE. Compartiendo perspectivas sobre integraciones Odoo, automatización de eCommerce y soluciones empresariales impulsadas por IA.

Chatea en whatsapp