Ir al contenido

Arquitectura recomendada: Odoo + PgBouncer + PostgreSQL (patrón probado en producción)

10 de noviembre de 2025 por
Arquitectura recomendada: Odoo + PgBouncer + PostgreSQL (patrón probado en producción)
Juan Manuel De Castro
| Todavía no hay comentarios

Si vas a escalar Odoo en serio (más usuarios concurrentes, más jobs, más integraciones), el cuello de botella casi siempre termina siendo las conexiones a PostgreSQL y cómo las gestionas. La combinación Odoo + PgBouncer + PostgreSQL es el estándar de facto… pero solo si la armas bien (especialmente por el tema LISTEN/NOTIFY del bus y el /websocket/).

Aquí tienes una arquitectura clara, robusta y fácil de operar.


Objetivo de esta arquitectura

  • Reducir conexiones reales a PostgreSQL sin matar concurrencia.

  • Evitar “Connection Pool Is Full” y picos de latencia.

  • Mantener compatibilidad con notificaciones en tiempo real (bus) y WebSocket.

  • Facilitar HA / escalado horizontal del lado de Odoo.


Diagrama recomendado (simple y escalable)

            Internet
               |
         [ Nginx/HAProxy ]
        TLS + gzip + limits
        /websocket/  |   /
            |        |  /
            v        v v
   [ Odoo gevent ]  [ Odoo workers ]
     :8072             :8069
            \           /
             \         /
              v       v
         [ PgBouncer (transaction) ]
                  :6432
                    |
                    v
              [ PostgreSQL ]
                  :5432

Idea clave:

  • Odoo “normal” (ORM/HTTP) va a PgBouncer transaction pooling.

  • El canal de WebSocket va a gevent_port y se enruta separado en el proxy.


Por qué PgBouncer en transaction pooling

Odoo hace muchísimas transacciones cortas (lecturas, writes pequeños). Con pool_mode = transaction:

  • Reutilizas conexiones “servidor” (PgBouncer→Postgres) de forma eficiente.

  • Aceptas muchos clientes (workers) sin inflar max_connections en Postgres.

  • Mejoras latencia bajo carga.

Pero: transaction pooling NO sirve para cosas “de sesión” (y ahí entra el bus).


El “gotcha” real: LISTEN/NOTIFY (bus) y PgBouncer

Odoo usa notificaciones para eventos en tiempo real (bus). En PostgreSQL, LISTEN es por sesión. En PgBouncer transaction tu “sesión” cambia: hoy escuchas, mañana te mueven a otra conexión. Resultado: el bus se vuelve inestable.


Soluciones correctas (elige 1)

Opción A (recomendada): Odoo normal → PgBouncer (transaction) y bus/listen directo a Postgres

  • Más simple, menos piezas.

  • Requiere separar esa conexión (por config/módulo/ajuste según tu versión y stack).

Opción B: 2 PgBouncer

  • pgbouncer_tx (transaction) para Odoo normal

  • pgbouncer_sess (session) solo para LISTEN

    Más complejo, pero todo pasa por PgBouncer.


Configuración base por componente

1) Nginx (reverse proxy)

Separar /websocket/ es obligatorio si usas gevent/websocket:

upstream odoo_http {
  server 127.0.0.1:8069;
}

upstream odoo_ws {
  server 127.0.0.1:8072;
}

server {
  listen 443 ssl http2;
  server_name tu-dominio.com;

  # TLS config aquí...

  # WebSocket
  location /websocket/ {
    proxy_pass http://odoo_ws;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
  }

  # Resto de Odoo
  location / {
    proxy_pass http://odoo_http;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_read_timeout 600s;
    proxy_connect_timeout 30s;
    proxy_send_timeout 600s;
  }
}

En Odoo: proxy_mode = True.


2) Odoo (producción)

Puntos de oro:

  • workers > 0 (multi-process)

  • proxy_mode = True

  • DB normal apuntando a PgBouncer

  • WebSocket separado (gevent port)

Ejemplo:

[options]
proxy_mode = True
workers = 8
max_cron_threads = 2

http_port = 8069
gevent_port = 8072

db_host = 127.0.0.1
db_port = 6432
db_user = odoo
db_password = ********
db_maxconn = 64

db_maxconn es por proceso: si lo subes sin pensar y tienes muchos workers, puedes presionar PgBouncer y Postgres.


3) PgBouncer (transaction pooling)

Ejemplo práctico:

[databases]
odoodb = host=127.0.0.1 port=5432 dbname=odoodb

[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432

auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt

pool_mode = transaction

max_client_conn = 2000
default_pool_size = 50
reserve_pool_size = 20
reserve_pool_timeout = 5

server_idle_timeout = 60
server_reset_query = DISCARD ALL
ignore_startup_parameters = extra_float_digits


Notas rápidas:

  • default_pool_size = conexiones reales “servidor” por db/user.

  • reserve_pool_size ayuda en picos cortos.

  • DISCARD ALL limpia sesión cuando cambia cliente (recomendado con transaction pooling).

4) PostgreSQL

Reglas prácticas:

  • max_connections debe cubrir:

    • conexiones “servidor” que PgBouncer abrirá (pool_size + reserve)

      • conexiones directas (monitoring, mantenimiento, bus directo si aplica)

En pg_hba.conf, no te olvides de permitir el host de PgBouncer y Odoo según corresponda, y mantenerlo cerrado a Internet.


Cómo dimensionar (sin magia)

1) Odoo workers

Una guía útil:

  • workers ≈ (CPU cores * 2) + 1 (ajústalo por carga real y RAM)

2) PgBouncer default_pool_size

  • Si Postgres está en el mismo host y tu DB es rápida: 20–80 suele funcionar.

  • Si hay mucha latencia o queries pesadas: pool más pequeño y optimiza queries/índices.

3) Postgres max_connections

Piensa así:

max_connections >= (default_pool_size + reserve_pool_size) * (db/user pools) + margen

Si solo tienes 1 db y 1 user: es simple. Si tienes varios, suma.


Dos arquitecturas finales “listas para producción”

Arquitectura 1 (recomendada)

  • 1 PgBouncer (transaction)

  • Bus/listen directo a Postgres (solo esa conexión)

  • Proxy separando websocket a gevent

✅ Mejor relación simplicidad/estabilidad.

Arquitectura 2 (enterprise/strict)

  • PgBouncer TX (transaction) para Odoo normal

  • PgBouncer SESS (session) para bus/listen

  • Postgres detrás, sin bypass

✅ Todo pasa por proxy/poolers, pero aumenta complejidad.


Observabilidad: lo mínimo que debes monitorear

En PgBouncer

  • SHOW POOLS; (cola, activos, waiting)

  • SHOW STATS; (latencia, tx/s)

En PostgreSQL

  • pg_stat_activity (states, waits)

  • top queries (pg_stat_statements si lo usas)

En Odoo

  • latencia de endpoints

  • colas de cron

  • errores de pool/timeout


Checklist de “arquitectura bien hecha”

  • Nginx enruta /websocket/ al gevent_port

  • proxy_mode = True

  • Odoo normal → PgBouncer pool_mode=transaction

  • Bus/listen NO depende de transaction pooling (bypass o session pooling)

  • default_pool_size y max_connections calculados (no al azar)

  • límites y timeouts razonables en proxy y pooler

  • autenticación segura (SCRAM), y red cerrada entre componentes


Cierre

La arquitectura Odoo + PgBouncer + PostgreSQL funciona excelente cuando:

  1. separas bien HTTP vs WebSocket,

  2. usas PgBouncer transaction para el ORM,

  3. tratas el bus (LISTEN) como un caso especial,

  4. dimensionas pools con números, no con fe.

Si quieres, te dejo una propuesta exacta de valores (workers, db_maxconn, default_pool_size, max_connections) si me dices:

  • CPU/RAM del servidor

  • si Postgres está en el mismo host o remoto

  • concurrencia objetivo (usuarios simultáneos + crons pesados + integraciones)

Siguiente capítulo ->

Arquitectura recomendada: Odoo + PgBouncer + PostgreSQL (patrón probado en producción)
Juan Manuel De Castro 10 de noviembre de 2025
Compartir
Etiquetas
Archivo
Iniciar sesión dejar un comentario