Che cosa sono i job in background e gli scheduler di attività?

Aggiornato: ottobre 2026

Un job in background è un compito che gira fuori dal ciclo della richiesta: accodato dall’app, eseguito dai worker, ritentato in caso di errore. La linea di demarcazione è il tempo: una risposta deve sembrare istantanea (qualche centinaio di millisecondi) e deve battere il timeout del gateway (tipicamente circa 30 secondi), mentre il lavoro reale – e-mail via SMTP, elaborazione di immagini, generazione di report, API di terze parti – richiede da secondi a minuti e merita dei ritentativi. Tutto ciò che sta dalla parte sbagliata di quella linea viene accodato, confermato e svolto altrove; su larga scala è infrastruttura centrale, non idraulica – una grande azienda di messaggistica elabora oltre un miliardo di job al giorno esattamente con questo macchinario.

Punti chiave

DomandaRisposta
L’architetturaIl produttore accoda → la coda persiste → il worker esegue e conferma
La linea di demarcazioneBudget di risposta circa 300 ms; tutto ciò che è lento o ritentabile diventa un job
Le tre pianificazioniImmediata · differita (“tra 24 h”) · ricorrente (cron)
Il contratto di affidabilitàAt-least-once + job idempotenti + backoff con jitter + dead letter
L’errore silenziosoNiente dà errore quando un job pianificato non gira – va sorvegliata l’assenza

L’anti-pattern che insegna tutto

// ✗ La registrazione che si fa ostaggio di un server di posta
app.post('/signup', async (req, res) => {
  const user = await createUser(req.body);
  await sendWelcomeEmail(user);        // SMTP lento? Chi si registra fissa uno spinner.
  res.json(user);                      // SMTP giù? La registrazione dà 500 – ma
});                                    // l'account ESISTE. Il peggio di tutti i mondi.

// ✔ Accodare e rispondere
app.post('/signup', async (req, res) => {
  const user = await createUser(req.body);
  await jobs.enqueue('welcomeEmail', { userId: user.id }); // millisecondi
  res.json(user);                      // l'e-mail parte, ritenta e riesce
});                                    // secondo i propri tempi – in modo invisibile

Lo stesso principio visto dai due lati di un backend reale – un job che lavora sui dati, e un client che accoda invece di aspettare:

// JavaScript — Cloud Code (cloud/main.js)
// A background job: heavy work outside the request cycle
Parse.Cloud.job('sendWeeklyDigest', async (request) => {
  const query = new Parse.Query(Parse.User).equalTo('digestOptIn', true);
  await query.eachBatch(async (users) => {
    for (const user of users) await sendDigest(user); // per-record, resumable
  }, { useMasterKey: true });
  request.message('Digest run complete'); // status visible in the dashboard
});
// Run on demand or on a schedule from the dashboard — no queue to operate

Produttore, coda, worker

Architettura produttore, coda e worker con ritentativi e dead letterL'applicazione accoda i job in una coda durevole e risponde subito agli utenti. I worker prelevano i job, li eseguono e li confermano in caso di successo. I job falliti vengono riaccodati con un backoff esponenziale, e quelli che esauriscono i ritentativi passano in una dead letter queue per l'ispezione e il rigioco.

successo

errore

tentativi esauriti

App (produttore)
accoda + risponde subito

Coda
durevole, più o meno ordinata

Scheduler
cron: riaccoda al momento giusto

Worker
prelevano · eseguono · ack

Fatto

Backoff + jitter
attesa, poi riaccodamento

Dead letter queue
ispezionare · avvisare · rigiocare

L'applicazione accoda i job in una coda durevole e risponde subito agli utenti. I worker prelevano i job, li eseguono e li confermano in caso di successo. I job falliti vengono riaccodati con un backoff esponenziale, e quelli che esauriscono i ritentativi passano in una dead letter queue per l'ispezione e il rigioco.

Tre ruoli, un contratto. Il produttore – di solito un handler di richiesta – crea un job: un nome di tipo e un piccolo payload. La coda lo persiste; la durabilità è tutto il punto, perché un lavoro che esiste solo nella memoria di un processo in agonia muore con lui. I worker prelevano, eseguono e confermano (ack); un job sparisce solo dopo la conferma, ed è così che il job di un worker andato in crash sopravvive per girare di nuovo. Il vocabolario che viaggia insieme: enqueue/dequeue, ack, visibility timeout, e la distinzione che vale la pena difendere – una coda di messaggi trasporta dati tra servizi (e può diffonderli verso molti sottoscrittori); una coda di job esegue lavoro, esattamente una volta per job e per worker, con ritentativi e stato integrati.

I tre modi di pianificazione – e un’introduzione a cron

Ogni sistema di job implementa gli stessi tre modi temporali: immediato (accodato ora, eseguito appena un worker si libera), differito (accodato ora, eleggibile in un momento futuro – promemoria, scadenze dei periodi di prova e il meccanismo stesso dei ritentativi) e ricorrente (uno scheduler riaccoda secondo un calendario). Le pianificazioni ricorrenti si scrivono quasi sempre con i cinque campi di cron:

┌ minuto (0-59)  ┌ ora (0-23)  ┌ giorno del mese  ┌ mese  ┌ giorno della settimana
0 2 * * *      → ogni giorno alle 02:00
*/15 * * * *   → ogni 15 minuti
0 9 * * 1      → ogni lunedì alle 09:00

Due mine dello scheduler: i passaggi all'ora legale (le 02:30 svaniscono o
avvengono due volte – conviene pianificare in UTC) e la sovrapposizione (un
job che supera il proprio intervallo richiede un lock, altrimenti due copie
elaborano gli stessi dati).

Code di job vs. code di messaggi vs. scheduler

CriterionCoda di jobCoda di messaggiScheduler
UnitàUn job da eseguireUn messaggio da consegnareUn orario di scatto
ConsumatoriEsattamente un workerUno o molti sottoscrittoriI job che accoda
IntegratiRitentativi, stato, priorità, ritardoDurabilità, routing, fan-outCalendari, ricorrenza
Nomi open sourceSidekiq, Celery, BullMQRabbitMQ, Kafka, Rediscron, Quartz
Si combina comeEsegue ciò che gli eventi richiedonoTrasporta tra sistemiAlimenta la coda al momento giusto

La combinazione è la risposta a quasi tutti i dibattiti su “quale dei tre?”: gli scheduler decidono il quando, le code tengono il che cosa, i worker fanno il lavoro – e un batch notturno è cron che accoda job che i worker eseguono con tutta la semantica dei ritentativi.

Il contratto di affidabilità

I pezzi si insegnano di solito separatamente; sono un unico contratto. La coda promette at-least-once – un worker che va in crash dopo il lavoro ma prima della conferma significa che il job gira di nuovo; è inevitabile, non sciatto. In cambio si promette idempotenza: deduplicazione su un ID di job, protezione con vincoli di unicità, scritture in upsert, così la seconda esecuzione è un no-op invece di una seconda fattura. I ritentativi con backoff esponenziale e jitter colmano i guasti transitori – 1 s, 2 s, 4 s, con una componente casuale perché mille job che falliscono insieme non ritentino insieme, una cortesia che i sistemi di terze parti con rate limiting impongono comunque se non viene concessa. La dead letter queue raccoglie il resto: dopo il tetto dei tentativi, i job si parcheggiano dove le persone possono ispezionarli, correggerli e rigiocarli – e i job malformati in modo permanente dovrebbero saltare del tutto il teatro dei ritentativi e andare direttamente lì.

Progettare bene i job

Le regole su cui i framework di coda concordano, messe insieme: passare ID, non oggetti – un payload di job che contiene userId: "u-8fk2" ricarica lo stato aggiornato al momento dell’esecuzione, mentre un oggetto utente serializzato è già vecchio nell’istante in cui viene accodato; tenere i payload piccoli e semplici come JSON, mai segreti; un job per record – un job per utente che fallisce ritenta un solo utente, un mega-job ritenta tutti; rendere riprendibile il lavoro lungo – spezzarlo in blocchi, salvare il progresso a checkpoint e uscire con grazia sui segnali di arresto a metà blocco; e dare per scontata la concorrenza – un giorno due worker elaboreranno job adiacenti che toccano la stessa riga, e quella è una questione di lock a cui si risponde in fase di progetto oppure durante un incidente.

Sorvegliare la coda

L’errore in background è silenzioso: nessuno vede una pagina di errore quando il job del digest muore. I quattro indicatori che sostituiscono la pagina di errore: la profondità della coda (un arretrato che si accumula significa che i worker stanno perdendo), l’età del job misurata dall’accodamento al completamento – un job elaborato in 200 ms dopo 40 minuti di attesa è un job da 40 minuti per chi lo aspetta; i tassi di errore e di dead letter con avvisi sulla dead letter queue, perché i job parcheggiati e dimenticati sono dati che in silenzio non vengono elaborati; e le esecuzioni mancate per il lavoro pianificato – il caso più insidioso, perché una pianificazione che non è mai scattata non ha prodotto nessun errore, nessuna riga di log e nessun job. L’allarme va messo sull’assenza.

Casi d’uso tipici

  • E-mail e notifiche – il rinvio canonico: accodare alla registrazione, consegnare con i ritentativi.
  • Elaborazione multimediale – ridimensionamenti, transcodifiche, anteprime: minuti di CPU che nessuna richiesta dovrebbe attendere.
  • Report ed esportazioni – generare in background, notificare quando il file è pronto.
  • Sincronizzazione con terze parti – sincronizzazioni CRM e consegne di webhook, con backoff contro remoti instabili.
  • Pulizia e manutenzione – passate di TTL, potatura degli orfani, costruzione dei digest sul cron notturno.

Quale pattern serve? Matrice decisionale

Il lavoro assomiglia aScegliere
Attivato da azioni delle persone, volume variabileCoda di job + worker
Calendario fisso, limitato, prevedibileJob pianificato (cron)
Batch notturno su molti recordCron accoda; i worker eseguono record per record
Servizi che si dicono delle coseCoda di messaggi / pub-sub
Lavoro lento che oggi sta dentro un handler di richiestaIl refactoring qui sopra – accodare e rispondere
Deve sopravvivere ai crash e ritentare in sicurezzaQualsiasi di questi – più il contratto di affidabilità

Limiti e trade-off

  • Alla fine, non all’istante. Il lavoro accodato avviene dopo – le interfacce hanno bisogno di stati di attesa, e quel “dopo” ha bisogno di un limite scelto con intenzione.
  • Lo stato esce dal canale principale. I risultati arrivano via campi di stato, callback o notifiche – la semplicità di richiesta-risposta è spesa, e l’idraulica va messa in bilancio.
  • L’infrastruttura è reale. Broker, worker e scheduler sono servizi con le proprie modalità di guasto – oppure un problema della piattaforma, che è l’argomento a favore dei job gestiti.
  • I duplicati sono garantiti, prima o poi. At-least-once è il contratto; ogni job non idempotente è un incidente a orologeria.
  • Le code assorbono il sovraccarico con grazia – con troppa grazia. Un arretrato che cresce sembra tranquillo fino al momento in cui l’età dei job esplode; gli allarmi su profondità ed età sono il meccanismo di onestà.

I job in background su Back4app

Back4app è una piattaforma open-source di Backend as a Service (BaaS) che combina database gestito, API REST e GraphQL generate automaticamente, autenticazione, archiviazione di file e funzioni serverless con Cloud Code. I job qui sono Cloud Code con una pianificazione: si definisce Parse.Cloud.job – le schede di codice mostrano un job di digest che procede a lotti sugli utenti con eachBatch, record per record e riprendibile – e si esegue su richiesta oppure con una ricorrenza in stile cron dalla dashboard, con stato e log nel pannello Jobs; nessun broker da provisionare, nessuna flotta di worker da scalare, nessun servizio di coda da tenere in vita. La metà guidata dagli eventi si compone dalle stesse primitive: un handler di richiesta scrive una riga con status: "queued", un trigger afterSave avvia il lavoro, e i client seguono il progresso con le Live Queries invece di fare polling – il pattern produttore/worker espresso come dati, con il contratto di affidabilità (ID nei payload, handler idempotenti, uno stato su cui mettere un allarme) come disciplina di progetto invece che come infrastruttura.

Domande frequenti

Che cos'è un job in background?

Un compito che l'applicazione esegue fuori dal ciclo richiesta-risposta: il server accoda il lavoro, risponde subito a chi ha fatto la richiesta, e un worker separato lo esegue in modo asincrono – con ritentativi in caso di errore. Tutto ciò che è lento, ritentabile o inutile alla risposta appartiene a quel posto.

Perché non fare semplicemente il lavoro nell'handler della richiesta?

Perché il lavoro lento blocca la risposta, immobilizza la capacità del server e va a sbattere contro i timeout del gateway – e se il lavoro va in errore a metà richiesta, l'utente riceve un fallimento anche se una parte è già avvenuta. L'esempio canonico: inviare l'e-mail di benvenuto durante la registrazione, dove un server di posta lento trasforma la creazione dell'account in una rotella che gira.

Come funziona una coda di job?

È una lista durevole tra produttori e worker: l'applicazione inserisce un job – un tipo più un piccolo payload – la coda lo persiste, e i worker lo prelevano, lo eseguono e lo confermano. I job non confermati tornano nella coda, ed è da lì che viene l'affidabilità.

Qual è la differenza tra una coda di job e una coda di messaggi?

La finalità. Una coda di messaggi sposta dati tra servizi: la consegna è l'obiettivo, e un messaggio può diffondersi verso molti consumatori. Una coda di job esegue lavoro: aggiunge sopra ritentativi, pianificazione, priorità e stato, e ogni job va a esattamente un worker.

Qual è la differenza tra cron job e job in background?

Il trigger. Cron è guidato dal tempo – "ogni notte alle 2"; i job in background sono guidati dagli eventi – "quando qualcuno carica un file". I due pattern si combinano: un progetto diffuso affida a cron l'accodamento del batch notturno, mentre i worker lo eseguono con tutta la semantica dei ritentativi.

Come funzionano i ritentativi dei job?

I job falliti girano di nuovo automaticamente con un backoff esponenziale – un secondo, poi due, quattro, otto – più un jitter casuale perché mille errori non ritentino tutti nello stesso istante, entro il limite di un numero massimo di tentativi. Il backoff colma i guasti transitori; il tetto impedisce a quelli permanenti di ritentare all'infinito.

Perché i job in background devono essere idempotenti?

Perché le code promettono un'esecuzione at-least-once: un worker può andare in crash dopo aver fatto il lavoro ma prima di confermarlo, e il job gira di nuovo. Girare due volte non deve addebitare due volte – chiavi di idempotenza, vincoli di unicità e upsert trasformano i duplicati in no-op.

Che cos'è una dead letter queue?

Il posto dove finiscono i job che hanno esaurito i ritentativi: conservati per ispezione, avvisi e rigioco manuale, invece di ritentare all'infinito o sparire in silenzio. I job malformati che non potranno mai riuscire dovrebbero saltare i tentativi e andare direttamente lì.

Termini correlati

Confronta con

Fonti di approfondimento

Pronto a costruire il tuo backend?

Avvia il tuo progetto su Back4app in pochi minuti: database, autenticazione, API e Cloud Code inclusi. Nessuna carta di credito richiesta.

Scritto e revisionato da Back4app Engineering, Back4app Engineering · Pubblicato il 2026-10-13