Case study 001
Running · Appendix 001 · Pipeline

01 / Deep-dive — Pipeline

Distributed
auto-publish
pipeline.

Un único job central que recorre todos los tenants, despacha trabajo a sus colas aisladas, y se reprograma a sí mismo cada minuto sin depender de cron externo. Fan-out masivo con invariantes estrictas.

Angle
Async · Scheduling · Fan-out
Runtime
Laravel Horizon · Redis · Supervisor
Scope
All tenants · 1 job · N integrations
Cadence
Self-rescheduling · 1 min tick

01

Flow.

Self-rescheduling fan-out

// architecture · fan-out tick

   ┌─────────────────────────┐
   │  AutoPublishScheduler   │ ◀─── self-dispatch delay(60s)
   └──────────┬──────────────┘
              │ dispatches
              ▼
   ┌─────────────────────────┐
   │ ProcessAutoPublishPosts │   ← central-auto-publish queue
   │    timeout 600s · 3x    │   ← Horizon supervisor
   └──────────┬──────────────┘
              │ iterates
              ▼
   ┌─────────────────────────┐
   │  for each tenant t:     │
   │    tenancy::init(t)     │
   │    select eligible()    │
   │    for each post p:     │
   │      provider.publish() │   ── external API ──▶ ●
   │      post.markSent()    │
   └─────────────────────────┘
              │
              ▼
   ┌─────────────────────────┐
   │  Horizon dashboard      │ ◀── metrics, failed jobs
   │  storage/logs/*.log     │ ◀── structured trace
   └─────────────────────────┘

02

Beats.

Six design decisions that matter

  1. 01

    One job, every tenant

    Un único job central — ProcessAutoPublishPosts — itera sobre todos los tenants del sistema en cada ejecución. Para cada uno inicializa el contexto de tenancy (DB, cache, filesystem), consulta publicaciones con auto_publish = true y estado = aprobado, y delega a la misma lógica de publicación que usa el flujo manual. Cero duplicación.

  2. 02

    Self-rescheduling — no external cron

    El AutoPublishScheduler se auto-despacha cada 60 s usando delay() de Laravel Queue. Al terminar su tick, encola su propia siguiente ejecución. Resultado: un sistema que se mantiene vivo solo — si Horizon corre, el pipeline corre. Sin cron externo, sin supervisión adicional.

  3. 03

    Queue isolation per tenant

    QueueTenancyBootstrapper prefija las claves de Redis por tenant, así los workers procesan únicamente los jobs de su contexto. La cola central-auto-publish vive en la DB central pero los jobs hijos ejecutan contra DBs de tenant — el bootstrapper intercala contextos sin colisión.

  4. 04

    Retry, timeout, memory — deterministic

    Cada job declara explícitamente timeout = 600s, tries = 3 con backoff exponencial, y memory = 512MB. El supervisor central-auto-publish-supervisor escala a 2 procesos en producción. Los fallos caen a failed_jobs con stack trace completo y dejan trazabilidad en storage/logs/auto-publish.log.

  5. 05

    Strict selection invariants

    La query de selección exige siete invariantes sobre cada publicación antes de tocar la API del proveedor: auto_publish, estado, cuenta_id, fecha_publicacion_real null, external_id null, tipo de cuenta válido, y token del tenant verificado. Imposible re-publicar por accidente.

  6. 06

    Graceful provider failures

    El provider externo falla — tokens expiran, rate-limits aparecen, APIs cambian payloads sin aviso. Cada publicación se encapsula en try/catch con clasificación del error: reintentable (red, 5xx) vs. terminal (4xx de negocio). Los terminales marcan el post como error con mensaje humano-legible; los reintentables devuelven al job a la cola.

03

Signal.

Self-rescheduling in code

app/Jobs/AutoPublishScheduler.php

class AutoPublishScheduler implements ShouldQueue
{
    public $timeout = 60;
    public $tries = 3;

    public function handle(): void
    {
        // 1. dispatch the work for this tick
        ProcessAutoPublishPosts::dispatch()
            ->onQueue('central-auto-publish');

        // 2. schedule our own next tick (60s later)
        self::dispatch()->delay(now()->addMinute());
    }
}

El patrón auto-dispatch elimina la dependencia de cron. Tras php artisan autopublish:start el sistema late solo mientras Horizon esté vivo — y Horizon tiene su propio supervisor, así que el loop se reinicia solo tras fallo.

04

Runtime.

Determinism at the edges

Queue
central-auto-publish · Redis
Supervisor
2 procesos en prod · 1 en local
Timeout
600 s por job
Retries
3 con backoff exponencial
Memory cap
512 MB por worker
Tick cadence
every minute · withoutOverlapping

05

Watch.

Observability

Horizon dashboard
Métricas en vivo: jobs por cola, throughput, failed jobs, supervisor health
Structured logs
Tenants procesados · publicaciones encontradas · errores por post · estadísticas finales
Failed jobs table
Stack traces completos con payload del job original — reintento con un comando
Pail
Tail en vivo de logs estructurados durante debugging

End of appendix

Need this
at your scale?