Ir al contenido

Cron jobs y colas en Odoo: tareas programadas fiables

ir.cron, idempotencia y colas de trabajo sin duplicados
4 de julio de 2026 por
Cron jobs y colas en Odoo: tareas programadas fiables
Atemi, Aitor Atencia

Odoo · DevOps · Automatización

Un ir.cron mal diseñado no falla de forma ruidosa: se ejecuta dos veces, se solapa consigo mismo o deja registros a medio procesar sin que nadie se entere hasta semanas después. La regla que evita el 90% de estos incidentes es simple: toda tarea programada debe poder ejecutarse dos veces sin romper nada. Veamos cómo diseñar crons fiables e idempotentes.

Cron Logo Odoo
Un cron periódico y un job asíncrono resuelven problemas distintos: no son intercambiables.

Diseño

ir.cron vs cola de trabajos

ir.cron está pensado para tareas periódicas de mantenimiento: limpiar registros caducados, sincronizar un catálogo cada hora, recalcular un indicador cada noche. Cuando la tarea nace de una acción de usuario (confirmar un pedido, subir un fichero) y necesita reintentos con backoff, lo correcto es delegarla a una cola como queue_job en lugar de forzarla dentro de un cron.

<record id="ir_cron_sync_catalog" model="ir.cron">
    <field name="name">Sincronizar catálogo con ERP</field>
    <field name="model_id" ref="model_product_template"/>
    <field name="state">code</field>
    <field name="code">model._cron_sync_catalog()</field>
    <field name="interval_number">1</field>
    <field name="interval_type">hours</field>
    <field name="numbercall">-1</field>
    <field name="doall" eval="False"/>
</record>

La lógica nunca vive en el XML: el code del cron llama a un único método Python testeable, y ese método es el que contiene las precondiciones y el manejo de errores.

Idempotencia

Que la segunda ejecución no duplique nada

class ProductTemplate(models.Model):
    _inherit = 'product.template'

    def _cron_sync_catalog(self):
        pending = self.search([
            ('sync_state', '=', 'pending'),
        ], limit=200)
        for product in pending:
            try:
                product._push_to_erp()
                product.sync_state = 'done'
            except ExternalApiError:
                product.sync_state = 'error'
                _logger.warning(
                    "Fallo sincronizando %s", product.default_code)

El truco no es evitar que el cron se ejecute dos veces —eso pasa igualmente por reinicios, workers duplicados o un numbercall mal calculado—, sino que cada ejecución consulte el estado real en base de datos antes de actuar. Si un registro ya está en done, el filtro del search lo excluye solo; no hace falta lógica extra de deduplicación.

Concurrencia

Evitar que dos workers procesen el mismo lote

    def _cron_sync_catalog(self):
        pending = self.search([
            ('sync_state', '=', 'pending'),
        ], limit=200)
        pending._for_update_skip_locked()
        for product in pending:
            ...

Con varios workers cron activos, dos procesos pueden leer el mismo lote de registros pendientes antes de que ninguno los marque como procesados. Un SELECT ... FOR UPDATE SKIP LOCKED a nivel de SQL (o un candado explícito por lote) es la única forma de garantizar que cada registro lo procesa un único worker.

Checklist

Antes de activar un cron en producción

✅ ComprobaciónPor qué
Lógica en un método Python, no en el XMLTesteable y auditable
Filtra por estado antes de actuarIdempotencia ante reintentos
doall=False salvo necesidad realEvita picos de carga por backlog
Maneja excepciones por registro, no por loteUn fallo no bloquea el resto
numbercall y interval revisadosEvita solapes con la duración real de la tarea

Resumen

Usa ir.cron para tareas periódicas de mantenimiento y una cola para trabajo asíncrono derivado de acciones de usuario. Diseña cada ejecución para que sea idempotente comprobando el estado real antes de actuar, evita doall=True salvo que necesites recuperar histórico, y asume que habrá ejecuciones concurrentes: bloquea a nivel de fila si varios workers pueden procesar el mismo lote.

en Odoo
Portal del cliente en Odoo: listas, formularios y descargas
Controladores, templates QWeb y permisos por partner