Introducción
El patrón async job + polling es el modelo recomendado en CreaRack Pro para operaciones que tardan más de ~1 segundo (ADR T2, Regla de Arquitectura R5). La idea es sencilla:
- El cliente encola una operación sin esperar a su resultado → el servidor responde
202 Accepted + job_id - El cliente hace polling periódico a
GET /api/jobs/{job_id}para consultar el estado - Cuando el estado es
doneoerror, se redirige al recurso creado o se muestra el error
Ventaja principal: el worker ASGI no se bloquea durante la operación larga (ej. análisis IA de ~4 min); otros usuarios siguen siendo atendidos.
Componentes
1. Modelo AsyncJob (core/models_async_job.py)
class AsyncJob(models.Model):
job_id = UUIDField(unique=True) # Identificador opaco del cliente
organization = ForeignKey(Organization) # Aislamiento tenant
kind = CharField(choices=['autoplan', 'ai_analysis', 'backup_full', 'restore_full'])
status = CharField(choices=['pending', 'processing', 'done', 'error'])
progress = PositiveSmallIntegerField() # 0-100 (opcional)
result = JSONField() # Resultado en éxito (sin secretos)
error = TextField() # Mensaje apto para usuario
created_by = ForeignKey(User) # Quién encoló
created_at, updated_at = DateTimeField()
Propiedades:
- Tabla ÚNICA reusable por todos los flujos async (no una tabla por tipo de job)
- Tiene RLS (aislamiento por
organizationen BD), como todas las tablas tenant - Los índices (
organization, -created_atystatus, -created_at) permiten listar jobs rápidamente - Los métodos
mark_processing(),mark_done(),mark_error()encapsulan las transiciones de estado
2. Endpoint de polling (core/api/jobs.py)
GET /api/jobs/{job_id}
Respuesta:
{
"job_id": "abc-123",
"kind": "autoplan",
"status": "processing",
"progress": 45,
"result": null,
"error": ""
}
Aislamiento: doble anclaje (filtro ORM + RLS en BD). Un job_id de otra org devuelve 404 (no confirma existencia).
3. Tareas Huey
Cada flujo que se quiere async encola una tarea en blueprints/tasks.py, racks/tasks.py, etc. con firma:
@db_task()
def run_<operation>(job_id, ...args, org_id):
job = AsyncJob.objects.get(job_id=job_id)
try:
job.mark_processing(progress=0)
# ... lógica larga ...
job.mark_done(result={...})
except Exception as e:
job.mark_error(str(e))
Seguridad: credenciales (ej. API key) se resuelven dentro de la task desde settings, NO se serializan en el payload de Redis.
Ciclo de vida
1. Cliente POST /api/<operation>
↓
2. Servidor crea AsyncJob (status=pending) + encola task → responde 202 + job_id
↓
3. Cliente hace polling GET /api/jobs/{job_id} cada N ms
↓
4. Task Huey corre en background:
- mark_processing() → status=processing
- ... trabajo ...
- mark_done(result) → status=done, progress=100
↓
5. Cliente detecta status=done, redirige o muestra resultado
Flujos migrados (ADR T2)
- ✅ Auto-Plan: análisis IA de planos (~4 min) →
run_autoplan_import - 🔄 Análisis IA de config: perf/finops (pendiente)
- 🔄 Backup/Restore full: (pendiente)
Restricciones y consideraciones
- Timeout de cliente: sugerencia 6 min (tope de seguridad en Auto-Plan)
- Poll interval: 2.5 seg (equilibrio entre feedback y carga)
- Progreso opcional: si la task no reporta
progress, el cliente solo ve 0 → done - Result sin secretos:
AsyncJob.resultNUNCA lleva credenciales, tokens o datos sensibles - Estado READ-ONLY para el cliente: no puede cambiar
statusvía API, solo leer
Véase también
- [[entity—core—model—asyncjob]]
- [[entity—core—endpoint—jobs-polling]]
- [[entity—blueprints—service—run-autoplan-import]]
- [[feature—blueprints—autoplan-async]]
Referenciado desde
- Auto-Plan async — análisis IA de planos en segundo plano (v1.55.0)
- Endpoint GET /api/jobs/{job_id} — consulta estado de operaciones async
- Modelo AsyncJob — estado de operaciones largas en background
- Subida de MIBs personalizados se encola en Huey — 202 + job_id (v1.89.0)
- Task Huey run_autoplan_import — análisis IA asíncrono de planos