Volver a la wiki

Patrón de trabajos asíncronos: job + polling

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:

  1. El cliente encola una operación sin esperar a su resultado → el servidor responde 202 Accepted + job_id
  2. El cliente hace polling periódico a GET /api/jobs/{job_id} para consultar el estado
  3. Cuando el estado es done o error, 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:

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)

Restricciones y consideraciones

Véase también

Subir