Guía técnica · Estados, mensajes de error y solución

Colas bloqueadas en Odoo (queue.job)

Trabajos atascados en failed o en enqueued para siempre, y sincronizaciones de marketplace que dejan de correr. Aquí tienes qué significa cada estado de queue_job (OCA), por qué se bloquea la cola y cómo diagnosticarla y reanudarla — con los mensajes de error reales.

Qué es queue_job y qué se rompe en la práctica

queue_job es el módulo de la OCA que ejecuta tareas en segundo plano en Odoo: en vez de bloquear al usuario mientras se sincroniza un pedido con un marketplace, el método se marca con @job (o se llama con .with_delay()) y se encola un registro queue.job que un proceso aparte — el jobrunner — recoge y ejecuta. Lo usan muchos conectores pesados (incluida la familia de connector de la OCA) para paralelizar sincronizaciones de stock, precios, pedidos y tracking.

El problema que llega a producción casi siempre es uno de estos dos: la cola no avanza (los jobs se quedan en enqueued o pending y nada se ejecuta) o los jobs fallan en bucle (estado failed, y los pedidos de Amazon, Shopify o Mirakl no entran). Los dos tienen causas distintas y se diagnostican en el mismo sitio.

Los estados de un job (y qué significa quedarse parado en cada uno)

Estado Qué significa Que se quede ahí es síntoma de…
pending En cola, esperando hueco en su canal. Canal saturado, runner parado o capacidad del canal a 0.
enqueued Reservado por el runner, a punto de ejecutarse. El runner murió justo después de reservarlo, o no hay worker que lo arranque.
started Ejecutándose ahora mismo. Worker reiniciado a mitad (deploy, OOM, timeout) y el job quedó huérfano.
done Terminó correctamente. Nada: estado sano.
failed Lanzó una excepción y agotó los reintentos. Error real de negocio o de la API: hay que leer exc_info.

Un job sano recorre pending → enqueued → started → done en segundos. Donde se quede parado te dice dónde mirar: si nunca pasa de pending o enqueued, el problema es el runner o los canales; si llega a failed, el problema está dentro del job (la traza lo cuenta).

1. La cola no avanza: jobs en enqueued o pending para siempre

El jobrunner no está corriendo

Es la causa número uno. El runner que vacía las colas solo arranca si Odoo está en modo multiproceso. Si lanzas el servidor con --workers=0 (típico en desarrollo, y a veces colado en producción), el runner no se inicia y los jobs se acumulan sin ejecutarse. Lo confirmas porque en el log de arranque no aparece la línea del runner:

INFO ... odoo.addons.queue_job.jobrunner.runner: starting

Solución: arranca con --workers=2 o más, y añade la sección en el odoo.conf para que el runner sepa qué base y canales servir:

[options]
workers = 4
server_wide_modules = web,queue_job
[queue_job]
channels = root:2

queue_job no está en server_wide_modules

El jobrunner es un hilo que se carga al arrancar el servidor, no por base de datos. Si queue_job está instalado en la base pero no figura en server_wide_modules (ni como --load=web,queue_job), el módulo funciona en la interfaz pero el runner nunca arranca. Resultado: los jobs entran a pending y se quedan ahí.

Solución: añade queue_job a server_wide_modules (o al flag --load) y reinicia. En Odoo.sh y otros PaaS gestionados esto suele requerir tocar la configuración del servidor, no solo instalar el módulo.

Canal saturado o con capacidad mal configurada

Cada job corre en un canal con una capacidad máxima de trabajos en paralelo (root:2 = dos a la vez). Si un job largo (una importación masiva de catálogo) ocupa el canal y no termina, todo lo demás se queda en pending detrás. Y si por error defines un canal con capacidad 0, ese canal nunca ejecuta nada.

Solución: separa el tráfico en canales (por ejemplo root:2,root.import:1) para que una importación pesada no bloquee las sincronizaciones rápidas de stock y pedidos. Revisa en Cola de tareas agrupando por canal qué está ocupando la capacidad.

Jobs huérfanos en enqueued / started tras un reinicio

Un deploy, un reinicio del worker, un out of memory o un timeout (limit_time_real) mata el proceso que estaba ejecutando un job. El registro se queda congelado en started o enqueued y, como ya estaba reservado, el runner no lo vuelve a coger por su cuenta.

Solución: en Ajustes → Técnico → Cola de tareas, filtra por estado started/enqueued con fecha antigua, selecciónalos y usa Requeue (volver a pending). El módulo trae también el cron Queue Job: Requeue Dead Jobs y un autovacuum que limpia/marca como fallidos los jobs que llevan demasiado tiempo congelados — comprueba que ambos crons estén activos.

2. Jobs en failed: el sync de marketplace no entra

Aquí la cola sí avanza, pero los trabajos revientan. El registro en failed guarda la traza completa en el campo exc_info: léela siempre antes de reintentar. Estos son los fallos típicos en sincronizaciones de marketplace.

Credenciales o token caducados

Lo más frecuente: el token de la API del marketplace expiró o se revocaron las credenciales. El job falla en la llamada y, tras agotar los reintentos, queda en failed. Suele verse algo como:

requests.exceptions.HTTPError: 401 Client Error: Unauthorized for url

Solución: renueva el token o las claves en la configuración del conector y luego haz Requeue. Reintentar antes de arreglar la credencial solo repite el 401.

Rate-limit de la API (429)

Amazon SP-API, Shopify y Mirakl limitan las peticiones por minuto. Si lanzas muchos jobs en paralelo (un canal con capacidad alta) los empiezas a recibir rechazados:

429 Too Many Requests / QuotaExceeded: You exceeded your quota for the requested resource

Solución: baja la capacidad del canal que toca esa API y deja que queue_job reintente con retry_pattern (backoff). Lo correcto es que el job traduzca el 429 en un reintento diferido, no en un fallo definitivo. Si tu conector no lo hace, es un fallo del conector.

Bloqueos de base de datos (SerializationFailure)

Si varios jobs tocan los mismos registros a la vez (por ejemplo el stock de un producto vendido en varios canales), PostgreSQL aborta una de las transacciones por conflicto de serialización:

psycopg2.errors.SerializationFailure: could not serialize access due to concurrent update

Solución: queue_job reintenta automáticamente este error (RetryableJobError), así que normalmente se resuelve solo en el siguiente intento. Si es persistente, reduce el paralelismo del canal o usa una identity_key para serializar los jobs que tocan el mismo recurso.

Dato del pedido que la API rechaza

Un país sin mapear, un método de envío inexistente, un SKU que no existe en Odoo o un importe que no cuadra hacen que la API devuelva un 400/422 con un mensaje de validación. Este fallo no se arregla reintentando: el dato sigue mal.

Solución: lee el mensaje exacto en exc_info, corrige el mapeo o el dato maestro (transportista, país, producto) y reintenta solo entonces. Marcar el job como done sin arreglar el dato hace que ese pedido quede sin sincronizar y desaparezca del radar.

Diagnóstico paso a paso (5 minutos)

¿queue_job de verdad o un cron sencillo? La parte honesta

queue_job es excelente, libre (OCA, LGPL) y la opción correcta cuando tienes volumen y necesitas paralelismo y reintentos por trabajo. Pero añade una pieza que mantener: el jobrunner, sus canales y un punto más donde algo se atasca. No todos los conectores lo necesitan.

Criterio queue_job (OCA) Cron normal (ir.cron)
Paralelismo Sí, por canales; ideal con picos de volumen. Secuencial; un lote tras otro.
Reintentos Por job, con backoff configurable. Manual o reescrito en el código del cron.
Trazabilidad Un registro por trabajo con su traza. Solo lo que tú dejes en el log/chatter.
Operación Necesita runner, canales y server_wide_modules. Cero infraestructura extra: ya está en Odoo.
Mejor para Catálogos grandes, muchos canales, picos. Catálogos pequeños/medios, operación simple.

Por eso varios conectores de FlexigoTech — Amazon, Shopify, Mirakl y ManoMano — sincronizan stock, pedidos y tracking con crons normales y no obligan a montar queue_job para empezar. Menos piezas, menos colas que se atascan. Si tu volumen lo pide, queue_job sigue ahí; pero no es un requisito para vender en marketplaces desde Odoo.

¿Cola atascada y pedidos sin entrar?

En FlexigoTech (Barcelona) diagnosticamos por qué tu queue_job no avanza, reactivamos los jobs huérfanos, arreglamos la causa de los failed y, si conviene, simplificamos tu sincronización a crons más robustos. Ingenieros, sin intermediarios.

Ver desarrollo Odoo a medida

FAQ

¿Por qué los jobs se quedan en enqueued para siempre?

enqueued significa que el job se reservó pero el proceso que debía ejecutarlo no lo hizo. Casi siempre es que el jobrunner no está corriendo: Odoo arrancado con --workers=0 no inicia el runner, falta queue_job en server_wide_modules, o falta la sección [queue_job] con sus canales en el odoo.conf. Un job en enqueued no tiene reintento automático: hay que hacer Requeue o relanzar el runner.

¿Qué diferencia hay entre pending, enqueued, started, done y failed?

pending: en cola esperando hueco en su canal. enqueued: reservado por el runner, a punto de ejecutarse. started: ejecutándose. done: terminó bien. failed: lanzó una excepción y agotó los reintentos. Un job sano pasa pending → enqueued → started → done en segundos. Si se queda en enqueued o started, el problema es el runner o un worker muerto a mitad.

¿Cómo reintento un job de marketplace que falló?

En Ajustes → Técnico → Cola de tareas abres el registro failed, lees el exc_info (la traza completa) y pulsas Requeue / Set to pending. Antes de reintentar corrige la causa: credenciales caducadas (401), rate-limit (429) o un dato del pedido que la API rechaza (400/422). Reintentar sin arreglar la causa solo repite el fallo.

¿Necesito queue_job para los conectores de marketplace?

No siempre. Muchos conectores, incluidos varios de FlexigoTech, sincronizan stock, pedidos y tracking con crons normales (ir.cron) sin depender de queue_job, lo que simplifica la operación. queue_job aporta paralelismo, reintentos y trazabilidad por trabajo, muy útil con volúmenes altos, pero añade una pieza más que mantener. Para catálogos pequeños un cron bien hecho suele ser más simple y menos frágil.

Siguiente paso

Desarrollo Odoo a medida Test gratis: ¿qué conector necesitas?