Odoo · Performance · ORM
Un módulo que va bien con 200 registros de demo y se arrastra con 200.000 en producción casi siempre tiene el mismo origen: un bucle Python que dispara una query por iteración. La solución no es "optimizar después", es no escribir el N+1 la primera vez.
--log-sql) es la herramienta más honesta para ver si tu código escala.El problema clásico
N+1: una query por registro en vez de una para todos
Acceder a un campo relacional dentro de un bucle for record in records: parece inofensivo, pero cada acceso a record.partner_id.name en una recordset sin prefetch puede disparar una query SQL individual. Con 10 registros no se nota; con 10.000, el request tarda minutos.
# Mal: N queries, una por factura
for invoice in invoices:
total += invoice.partner_id.credit_limit
# Bien: el ORM precarga partner_id de todo el recordset de una vez
partners = invoices.mapped("partner_id")
credit_by_partner = {p.id: p.credit_limit for p in partners}
total = sum(credit_by_partner[inv.partner_id.id] for inv in invoices)
Agregaciones
read_group / _read_group: sumar sin traer registros a Python
Cuando necesitas totales, medias o conteos agrupados, traer todos los registros a Python con browse() y sumarlos en un bucle es tirar el trabajo que PostgreSQL hace bien de forma nativa. _read_group genera un GROUP BY SQL y devuelve solo los agregados, sin materializar cada registro.
# Mal: trae todas las líneas de venta a Python para sumar
lines = self.env["sale.order.line"].search([("order_id.state", "=", "sale")])
total_by_product = {}
for line in lines:
total_by_product.setdefault(line.product_id, 0)
total_by_product[line.product_id] += line.price_subtotal
# Bien: agregación en SQL, un solo GROUP BY
result = self.env["sale.order.line"]._read_group(
domain=[("order_id.state", "=", "sale")],
groupby=["product_id"],
aggregates=["price_subtotal:sum"],
)
Escritura masiva
Batch processing: agrupa antes de escribir
Igual que leer registro a registro es lento, escribir registro a registro también lo es: cada record.write(vals) dentro de un bucle dispara sus propios triggers de @api.depends, @api.constrains y reglas de seguridad. Agrupar los IDs y llamar a write() una sola vez sobre el recordset completo reduce drásticamente el número de recomputaciones.
# Mal: un write() por registro, N recomputaciones de campos calculados
for order in orders:
order.write({"state": "done"})
# Bien: un único write() sobre todo el recordset
orders.write({"state": "done"})
| Situación | Qué hacer |
|---|---|
| Sumar/contar/agrupar registros | _read_group, nunca bucle Python |
| Actualizar el mismo campo en varios registros | write() sobre el recordset, no uno a uno |
| Sospechas de N+1 | Activar --log-sql y contar queries por request |
| Necesitas solo IDs o un campo | search_read con fields concreto, no browse completo |
Resumen
El rendimiento en Odoo se decide en el patrón de acceso a datos, no en microoptimizaciones. Evita bucles que consultan o escriben registro a registro, delega las agregaciones a _read_group y mide con --log-sql antes de asumir dónde está el cuello de botella.