Cuando Odoo “se pone lento” bajo carga, casi nunca es “misterio”: suele ser concurrencia mal dimensionada entre:
Workers de Odoo (cuántas requests puedes atender en paralelo)
Conexiones cliente a PgBouncer (cuántas conexiones simultáneas recibe el pooler)
Conexiones servidor a PostgreSQL (cuántas conexiones reales abre PgBouncer hacia Postgres)
Entender esa cadena te permite escalar sin caer en “Connection Pool Is Full” o colas infinitas.
1) El mapa mental: 3 niveles de concurrencia
Nivel A — Odoo: workers y crons
En producción (Linux), Odoo recomienda el modo multi-proceso y da una regla de pulgar para workers:
workers ≈ (#CPU * 2) + 1. Odoo+1
Además, Odoo aclara que en multi-processing se levanta un worker dedicado para LiveChat/WebSocket en --gevent-port (y que el proxy debe redirigir /websocket/ a ese puerto). Odoo
Traducción: aunque configures workers = N, en la práctica hay más actividad paralela (web + cron + gevent).
Nivel B — Odoo: db_maxconn (pool de conexiones “cliente”)
Odoo usa un pool de conexiones a la DB y, cuando se queda corto, aparece el clásico:
psycopg2.pool.PoolError: The Connection Pool Is Full GitHub+1
En multi-proceso, el punto que más confunde es este: el “pool” no es global a todos los workers; cada proceso tiene su dinámica, y con crons/longpolling puedes saturarlo incluso con valores que “parecen” suficientes (hay varios casos reportados por la comunidad). GitHub+1
Nivel C — PgBouncer: max_client_conn y default_pool_size
PgBouncer separa claramente:
clientes (Odoo → PgBouncer)
servidores (PgBouncer → PostgreSQL)
Y organiza pools por par (usuario, base de datos); ahí es donde default_pool_size define el máximo de conexiones servidor por pool. PgBouncer+1
En transaction pooling (lo típico con Odoo), PgBouncer asigna una conexión servidor solo durante la transacción y luego la devuelve al pool. PgBouncer
2) La relación clave (en una frase)
Más workers de Odoo ⇒ más concurrencia potencial ⇒ más conexiones “cliente” hacia PgBouncer ⇒ más presión para abrir conexiones reales a PostgreSQL (hasta el límite de default_pool_size).
Si dimensionas mal cualquiera de estos escalones, aparece:
cola en PgBouncer (clientes esperando)
o saturación en Postgres (demasiadas conexiones activas)
o el “pool is full” del lado de Odoo
3) Qué parámetro controla qué (sin humo)
Odoo
workers: cuántas requests web simultáneas puedes ejecutar (aprox.) Odoo+1
max_cron_threads: cuántos crons corren en paralelo (DB-active muchas veces)
db_maxconn: “cuántas conexiones a DB puede tener disponibles el proceso” (si te quedas corto, explota) GitHub+1
PgBouncer
max_client_conn: cuántas conexiones cliente acepta en total (Odoo → PgBouncer)
default_pool_size: cuántas conexiones servidor máximo por pool (db/user) PgBouncer+1
transaction pooling: conexión servidor asignada solo durante la transacción PgBouncer
4) Cómo dimensionar PgBouncer a partir de workers de Odoo (paso a paso)
Paso 1 — Estima tu pico de “procesos DB-activos”
Punto de partida:
Web workers (N)
Cron threads (C)
Gevent/WebSocket (G ≈ 1 en multi-processing) Odoo
DB-activos potenciales ≈ N + C + G
Ojo: esto es “peor caso”. En la realidad, no todos están en DB al mismo tiempo, pero para sizing inicial es útil.
Paso 2 — Estima conexiones cliente hacia PgBouncer
Cada proceso puede pedir varias conexiones si su pool lo permite (db_maxconn). En picos (o cuando hay longpolling, crons pesados y tráfico web), el uso se acerca al límite y aparecen los PoolError cuando se queda corto. Odoo+1
Máximo teórico de clientes hacia PgBouncer:
max_client_conn_ideal ≥ (N + C + G) * db_maxconn + margen
El “margen” es para picos, conexiones admin, healthchecks, herramientas, etc.
Paso 3 — Dimensiona conexiones servidor a Postgres (lo que importa de verdad)
Acá manda default_pool_size, que es por (db, user). PgBouncer+1
En transaction pooling, default_pool_size se comporta como:
límite de transacciones concurrentes por db/user PgBouncer+1
Regla práctica:
default_pool_size debe ser lo bastante alto para que no haya cola en horas pico,
pero lo bastante bajo para no ahogar Postgres con demasiadas consultas simultáneas.
Paso 4 — Agrega “burst” controlado: reserve_pool_size
En vez de inflar default_pool_size, usa reserve_pool_size para picos cortos (10–20% del pool suele ser un inicio razonable). Esto te salva de picos sin dejar Postgres “a la intemperie”.
5) Ejemplo real (para entender la matemática)
Servidor con 8 CPU:
Odoo sugiere workers ≈ (8*2)+1 = 17 Odoo
max_cron_threads = 2
gevent worker = 1 Odoo
db_maxconn = 32
5.1 PgBouncer max_client_conn
Peor caso:
procesos DB-activos = 17 + 2 + 1 = 20
clientes potenciales = 20 * 32 = 640
Recomendación inicial: max_client_conn 1000–2000 (según tu entorno).
5.2 PgBouncer default_pool_size
Si tienes 1 db y 1 user (lo normal en Odoo), tienes 1 pool.
Un inicio “sano” suele estar entre 40 y 120 (depende de la potencia de Postgres y la calidad de queries). Luego ajustas mirando colas y latencia.
6) Señales de que lo dimensionaste mal
Te quedaste corto (cola en PgBouncer)
Usuarios lentos “en oleadas”
PgBouncer muestra clientes esperando (cl_waiting sube)
Postgres NO está al 100%
→ Sube default_pool_size o ajusta crons/queries.
Te pasaste (Postgres sufre)
CPU/IO altos sostenidos
más waits/locks
latencia por query sube
→ Baja default_pool_size y optimiza (índices/queries) antes de “darle más conexiones”.
Odoo explota con “The Connection Pool Is Full”
Indica que el pool de Odoo es insuficiente para el patrón real de concurrencia (web + cron + longpolling), algo reportado en escenarios con workers>0 y crons paralelos. GitHub+1
7) Recomendación final (el enfoque que más evita incidentes)
Dimensiona workers con la regla de Odoo como punto de partida. Odoo
Mantén db_maxconn moderado (32–64 suele ser un rango operativo común; sube solo con evidencia).
Pon max_client_conn alto (capacidad) y controla el backend con default_pool_size (protección). PgBouncer+1
Usa transaction pooling (Odoo-friendly) y mide colas. PgBouncer