1. Resumen del problema
En una estructura donde se utiliza transaction.on_commit()
para llamar tareas de Celery en un entorno Django,
se presenta el fenómeno de que los datos del campo ManyToMany quedan vacíos dentro de la tarea de Celery.
2. Fenómeno
- Después de completar
post.categories.add(...)
,post.tags.add(...)
- Se llama a
translate_post.delay()
mediante un callback detransaction.on_commit()
- Al consultar
post.categories.all()
,post.tags.all()
dentro de la tarea de Celery, se devuelve una lista vacía ([]
)
3. Configuración del entorno
- WRITE: PostgreSQL maestro en una VM de GCP
- READ: PostgreSQL réplica (replicación en streaming) en Raspberry Pi
- Configuración para que las solicitudes de lectura se envíen a la réplica a través de un router de DB de Django
- Modelo Post con un campo de relación ManyToMany
4. Análisis de logs
✅ Resumen de orden (según el tiempo de ejemplo)
13:13:56.728
— Creación de Post13:14:00.922
— Última consulta deTaggedItem
(ManyToMany)13:14:01.688
— Ejecución deon_commit()
→ Llamada a Celery13:14:01.772
— Resultados de la consulta dentro detranslate_post()
:- categorías:
[]
- etiquetas:
[]
- categorías:
Es decir, el orden es correcto pero el contenido no se refleja
5. Análisis de causas
✅ Retraso en la réplica de PostgreSQL
- La replicación básica de PostgreSQL funciona de manera asíncrona
- Los cambios en la base de datos maestra se reflejan en la réplica con un retraso de ms a cientos de ms
- El registro de la tabla intermedia que se conecta mediante
add()
aún no se ha reflejado en la réplica
✅ Funcionamiento de on_commit() en Django
on_commit()
se ejecuta inmediatamente después de que la transacción de Django se comitea- Sin embargo, Celery es un proceso separado y la lectura utiliza la base de datos réplica
- Como resultado, en el momento de on_commit, la réplica aún está en un estado no actualizado
6. Puntos de verificación
- Existen registros de creación de Post
- Los registros de consulta de add() se ejecutan correctamente
- El orden de las consultas reales y el orden de la llamada a la tarea son correctos
- El problema es que la base de datos en el momento de la consulta era la réplica
7. Soluciones
1. Forzar lectura desde la base de datos maestra en la tarea de Celery
# Enfoque ORM
post = Post.objects.using('default').get(id=post_id)
tags = post.tags.using('default').all()
categories = post.categories.using('default').all()
2. Configuración de un router de DB específico para Celery
Se puede configurar en settings.py
para que siempre utilice el maestro en caso de que sea una tarea de Celery
3. En caso de que la réplica deba estar siempre actualizada:
- Es necesario establecer
synchronous_commit = on
en PostgreSQL (posible disminución de rendimiento)
8. Conclusión
Este problema no es un error de Django, sino que es el resultado de latencias en un entorno de replicación asíncrona y la política de prioridad de lectura del ORM.
La clave de la solución es asegurar el uso forzado de la base de datos maestra en la tarea de Celery.
9. Comentarios de Jesse
"on_commit() se ejecutó claramente en el momento adecuado, pero el hecho de que la base de datos leída era la réplica fue el núcleo del problema.
No hay culpa en Django ni en Celery. Al final, comprender y coordinar la arquitectura del sistema es responsabilidad del desarrollador."
Add a New Comment