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
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
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.
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.
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.
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.
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.
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.
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)
- Activa el modo desarrollador y abre Ajustes → Técnico → Cola de tareas (Queue Jobs). Agrupa por estado y por canal.
- Si dominan
pending/enqueuedy casi no haydonerecientes → problema de runner o canales. Ve al log de arranque y busca la líneajobrunner.runner: starting. Si no está, revisa--workersyserver_wide_modules. - Si dominan los
failed→ abre uno, leeexc_infoentero y clasifícalo: credencial (401), rate-limit (429), bloqueo de BD (SerializationFailure) o dato del pedido (400/422). Arregla la causa, no el síntoma. - Para los huérfanos antiguos en
started/enqueued: selecciónalos y pulsa Requeue. Verifica que los crons Requeue Dead Jobs y el autovacuum de queue_job estén activos para que no se repita. - Confirma que avanza: lanza un job de prueba (un sync manual) y mira que pase a
doneen segundos.
¿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.
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.