Ir al contenido

Pooling y ORM de Odoo: interacción real

7 de noviembre de 2025 por
Pooling y ORM de Odoo: interacción real
Juan Manuel De Castro
| Todavía no hay comentarios

La “interacción real” entre pooling (por ejemplo PgBouncer) y el ORM de Odoo se entiende mejor si lo mirás como tres capas que se enciman:

  1. Request / Job (HTTP o cron)

  2. ORM (env/cr/cursor + transacción)

  3. Conexión PostgreSQL (y el pooling delante)

Abajo te lo bajo a tierra con el flujo real y los puntos donde suele romperse o degradar.


1) Qué hace Odoo de verdad: cursor = transacción

En Odoo, el ORM trabaja sobre env, y env.cr es un cursor psycopg2 asociado a una conexión. En la práctica:

  • Cada request (o job cron) entra a un worker.

  • Odoo crea un cursor (cr) y empieza una transacción.

  • El ORM ejecuta SQL a través de ese cursor (con prefetch/cache a nivel ORM, pero siempre dentro de esa transacción).

  • Al final:

    • COMMIT si todo OK

    • ROLLBACK si hay excepción

  • Se cierra el cursor y la conexión vuelve al pool (pool de Odoo o pool externo).

Clave: Para Odoo, “unidad de trabajo” suele ser una transacción por request (o por job cron), salvo casos especiales (sub-transacciones con savepoints).


2) Odoo ya tiene pooling interno (aunque mucha gente lo subestima)

Odoo mantiene un pool de conexiones por proceso (cada worker). Eso significa:

  • Un worker no abre una conexión fija necesariamente: puede abrir varias hasta db_maxconn (parámetro de Odoo).

  • En producción, lo común es que cada worker use pocas conexiones, pero en picos puede crecer.

  • Si tenés workers = N, el peor caso de conexiones a Postgres puede acercarse a:

    N * db_maxconn (+ conexiones auxiliares, cron, etc.)

Por eso PgBouncer es tan popular: te “aplana” ese pico hacia Postgres.


3) PgBouncer: por qué el modo de pooling cambia TODO

PgBouncer tiene 3 modos (los que importan acá):

A) pool_mode = session (lo más compatible)

  • Un “cliente” mantiene el mismo backend Postgres durante toda la sesión.

  • Odoo “cree” que su conexión es estable (y lo es).

  • Menos sorpresas con estado de sesión.

✅ Es el modo que suele “funcionar siempre” con Odoo.

B) pool_mode = transaction (tentador, pero con trampas)

  • PgBouncer asigna un backend Postgres solo durante la transacción.

  • Al hacer COMMIT/ROLLBACK, el backend vuelve al pool y el próximo request puede caer en otro backend.

Esto puede ir bien si y solo si tu app no depende de estado de sesión fuera de la transacción.

Dónde Odoo se puede complicar en transaction pooling:

  • Advisory locks de sesión (pg_advisory_lock, pg_try_advisory_lock): si Odoo toma locks que dependen de “la sesión”, y PgBouncer te cambia el backend, ese lock puede quedar en un backend distinto al esperado.

  • Cualquier cosa que dependa de “me quedo en el mismo backend” fuera de una única transacción.

Hay despliegues que lo hacen andar, pero es donde aparecen bugs “fantasma” (crons que se pisan, locks que no se respetan, comportamiento raro bajo carga).


C) pool_mode = statement

  • Casi nunca para Odoo. Demasiado agresivo.


4) “Interacción real” ORM ↔ pooling: el timeline que importa

Request HTTP típico (con workers)

  1. Worker recibe request

  2. Odoo toma una conexión (del pool interno)

  3. Abre cr (cursor) → inicia transacción

  4. ORM hace lecturas/escrituras (prefetch/cache no cambia el hecho de que estás dentro de esa transacción)

  5. COMMIT/ROLLBACK

  6. Devuelve conexión al pool

Con PgBouncer session pooling: ese “paso 2” termina en un backend Postgres estable.

Con PgBouncer transaction pooling: el backend Postgres solo está garantizado durante 3–5. En el 6, el backend se libera.


5) El punto más importante: “estado de sesión” vs “estado por transacción”

Para que PgBouncer en transaction sea seguro, tu app debe cumplir:

“Todo lo que necesito vive dentro de la transacción; no asumo continuidad de sesión”.

Odoo, en general, está bastante cerca de eso para la mayoría del tráfico HTTP (porque request=transacción), pero hay dos zonas donde puede doler:

  • Locks: si algo usa locks de sesión y espera estabilidad, transaction pooling los vuelve impredecibles.

  • Jobs/cron: tienden a hacer operaciones largas y coordinación entre procesos; si hay locks de sesión o “coordinación por conexión”, transaction pooling puede romper invariantes.

Por eso, la recomendación práctica suele ser:

  • Si querés cero sorpresas: PgBouncer session.

  • Si necesitás exprimir al máximo la reducción de conexiones: transaction, pero testeando específicamente crons/locks/concurrencia real.


6) Números útiles: cómo dimensionar “pooling real”

Supongamos:

  • workers = 12

  • db_maxconn = 8

Sin PgBouncer (peor caso): hasta ~96 conexiones a Postgres desde Odoo.

Con PgBouncer: podés limitar “hacia Postgres” con default_pool_size/max_db_connections (según tu setup), y aceptar muchas conexiones “cliente” desde Odoo a PgBouncer, mientras PgBouncer multiplexa.

La clave es que, aunque bajes conexiones a Postgres, no podés bajar “concurrencia efectiva” sin impactar latencia: si tenés 12 workers que necesitan DB y solo 10 backends, 2 van a esperar.


7) Recomendación práctica para Odoo (sin vueltas)

  • Producción “normal” (ERP + crons + módulos varios): PgBouncer en session.

  • Si vas por transaction, hacelo sabiendo que estás comprando complejidad:

    • probá a fondo crons con carga

    • probá escenarios de locks / contención

    • medí si realmente necesitabas transaction o si con session + buen sizing ya estabas

Si querés, te lo aterrizo a tu caso concreto: decime (aunque sea aproximado) workers, db_maxconn, RAM/CPU del Postgres y si los crons son pesados (importaciones, conciliaciones, etc.). Con eso te digo qué modo de pooling te conviene y cómo dimensionar el pool para que no te agregue cola/latencia.

Siguiente capítulo ->

Pooling y ORM de Odoo: interacción real
Juan Manuel De Castro 7 de noviembre de 2025
Compartir
Etiquetas
Archivo
Iniciar sesión dejar un comentario