17 min di lettura·Aggiornato il 16 ottobre 2026

Questo è un post che non dovrei scrivere. La convenzione nel settore è celebrare i successi, mostrare case study patinati, tacere sui fallimenti. Io faccio il contrario, e la ragione è semplice: quasi tutto quello che ho imparato di utile sulle automazioni AI in produzione l’ho imparato dai fallimenti, miei o di altri. Nascondere i fallimenti ai clienti futuri significa farli ripetere a loro spese.

Il 12 e il 13 aprile 2026 abbiamo fatto i primi test end-to-end completi della pipeline AI iaFlux per un cliente moda italiana. L’architettura era già progettata, testata in unità, smoke-tested. Dieci foto in coda, aspettativa: dieci foto processate con successo. Risultato dei primi giri, fra il 12 e il 13 aprile: 60 record di errore nel database (secondo le nostre note di lavoro del 12 aprile: quel registro è stato poi azzerato) e job bloccati in stati intermedi. In quei due giorni sono emersi 3 bug critici distinti. Recovery totale richiesto. Pipeline resettata e ripartita.

Dopo le prime correzioni e alcuni reset manuali: 10 foto su 10 processate correttamente, costo totale $0,19, classificazione 100% OK. Ma i primi giri sono stati un disastro istruttivo. Ecco cosa è andato storto, come l’abbiamo diagnosticato, cosa abbiamo fixato, e le sei lezioni che applico oggi a ogni automazione in produzione. Se vuoi il contesto positivo completo, il case study con i numeri finali del batch è pubblicato come post separato.

Il contesto — pipeline AI per un catalogo da 126 foto e-commerce

Per chi non conosce il caso: il cliente è un’azienda moda italiana con 126 foto prodotto da allineare (rimozione sfondo, normalizzazione colore, ritaglio coerente). La pipeline iaFlux usa tre workflow n8n che parlano con Supabase Postgres come job queue, Flux Kontext Pro su Replicate come modello AI, Google Drive come storage input/output. Lo stack tecnico completo, con i costi di ciascun componente, è descritto nel post dedicato.

La scelta dell’orchestratore non è neutra rispetto a questi errori: per capire perché abbiamo costruito su n8n e quando conviene un’alternativa, abbiamo scritto un confronto tra n8n, Make e Zapier.

L’architettura era disegnata bene sulla carta:

  • Workflow 1 (Ingestion) scansiona INBOX ogni 60 secondi, accoda i nuovi file in Supabase
  • Workflow 2 (Worker) gira ogni 10 secondi, processa il prossimo job in coda con lock esplicito
  • Workflow 3 (StuckRecovery) gira ogni 60 secondi, riaccoda job bloccati da lock scaduto
  • Budget check, retry con classificazione errori, dead letter queue dopo 3 tentativi

Ogni componente testato in unità. Smoke test passato con 2 foto. Si parte con il primo test su 10 foto reali e iniziano i problemi.

Primo sintomo — errori generici di Replicate

Dopo l’avvio del test, il log errori di Supabase inizia a riempirsi. Errori generici tipo “bad request”, “invalid image format”, “prediction failed”. I job venivano ritentati e fallivano, venivano ritentati e fallivano di nuovo. Il contatore retry_count saliva a 3 e i job finivano in dead_letter.

Prima diagnosi sbagliata: “Replicate ha problemi momentanei, aspettiamo”. Seconda diagnosi sbagliata: “forse il modello Flux Kontext Pro ha rate limit non documentati”. Queste diagnosi sbagliate ci hanno fatto perdere tempo di debug. Vero errore: non guardare il payload effettivo che stavamo mandando all’API.

Quando siamo finalmente andati a vedere cosa stavamo inviando a Replicate, la sorpresa: l’URL dell’immagine non era un file binario, era una pagina HTML di Google Drive. Questo ci porta al primo bug critico.

Bug P0 — URL Drive non compatibile con Replicate

Severità: Critica (blocca completamente la pipeline).
Scoperta: Dopo diverse diagnosi sbagliate.
Sintomo: Replicate risponde con errori generici invece di processare l’immagine.

Il workflow di ingestion generava per ogni file un link di download diretto di Google Drive, del tipo https://drive.google.com/uc?export=download&id=FILE_ID. Questo URL restituisce il binario dell’immagine solo in alcuni casi. Per le nostre foto da 8192×5464 pixel, che nel registro pesano circa 10 MB in media, Google Drive non restituiva il file direttamente. Restituiva una pagina HTML invece del file (dalle nostre note di lavoro del 12 aprile 2026).

Replicate vedeva l’URL, faceva una GET, riceveva HTML invece di bytes di immagine, provava a interpretare l’HTML come immagine, e falliva con un errore generico “invalid image format”. Nessun indizio dal messaggio che il problema era HTML-vs-binary.

Fix applicato: invece di passare URL Drive, scarichiamo il file all’interno del container n8n via OAuth2 (autenticazione Google), poi facciamo upload multipart a Replicate Files API (POST https://api.replicate.com/v1/files). Replicate restituisce un proprio URL temporaneo, che garantiamo contenga il binario corretto, con una scadenza indicata nella risposta (campo expires_at). Zero ambiguità, zero pagine HTML di mezzo.

Costo del fix: tempo di sviluppo.

Lezione: ogni interfaccia tra due sistemi è un potenziale punto di rottura. Non dare mai per scontato che un URL “funzioni” senza verificare cosa restituisce veramente quell’URL al fornitore di servizio. Il problema non erano né Google Drive né Replicate: erano le assunzioni implicite tra i due.

Bug P1 — Retry su errore 402 crediti esauriti

Severità: Alta (può bruciare budget).
Scoperta: Accidentale, mentre sistemavamo P0.
Sintomo: Un job che ha fallito una volta per crediti esauriti viene ritentato più volte.

Il nostro error classifier guardava il codice HTTP di risposta e decideva se l’errore era temporary (retry automatico dopo N secondi) o permanent (job marcato come fallito, no retry). Codici 5xx erano temporary, codici 4xx erano permanent. Logica standard.

Il problema: HTTP 402 (Payment Required) — usato da Replicate quando il credito è esaurito — era classificato come temporary per default. Risultato: un job che aveva fallito una volta per crediti zero veniva rimesso in coda e ritentato, finendo sempre nello stesso errore 402. Il contatore retry_count saliva a 3 e poi si fermava, ma solo dopo aver tentato 3 volte ogni job della coda. Con 10 job in coda e 3 retry ciascuno, sono 30 tentativi inutili su un account a credito zero.

Il danno in questo caso è stato minimo perché eravamo a credito zero, quindi ogni retry falliva senza consumare nulla. Ma con un errore che consuma credito a ogni tentativo, lo stesso schema di retry brucerebbe dollari in errori inutili. In una pipeline senza un limite ai tentativi e senza un tetto di spesa, un bug che fa consumare più del previsto a ogni chiamata può far crescere la fattura molto in fretta. Il tema del costo AI subdolo e variabile che sfugge di mano è trattato come limite numero 4 nel post dedicato.

Fix applicato: check esplicito if (response.status === 402) errClass = 'permanent'. Il job finisce immediatamente in failed senza retry. Nel registro, però, gli unici errori 402 (pomeriggio del 13 aprile 2026) risultano ancora classificati come temporanei e rimessi in coda.

Fix architetturale aggiunto: budget_check come primo nodo del worker. Prima di ogni esecuzione, il worker legge dal nostro registro dei costi la spesa già registrata nella giornata (una stima calcolata dal tempo di elaborazione) e la confronta con un tetto giornaliero configurabile (nel nostro caso 50 dollari di spesa stimata). Se il tetto è raggiunto, il worker esce senza tentare il job.

Lezione: ogni pipeline AI in produzione deve avere di default due meccanismi anti-runaway: un controllo della spesa prima di ogni esecuzione, con un tetto giornaliero, e la classificazione come permanenti degli errori che non si risolvono riprovando, come il credito esaurito. Se il tuo fornitore non te li ha inclusi, te li stai prendendo addosso al primo incidente serio.

Bug P2 — Trigger 10s causava concorrenza distruttiva

Severità: Media (degrada le performance ma non blocca).
Scoperta: Correlando timing esecuzioni con picchi di errori.
Sintomo: Errori intermittenti “Try spacing your requests out”.

Il trigger del workflow worker era impostato ogni 10 secondi. L’idea era processare la coda velocemente. Dettaglio ignorato: ogni singolo job richiede circa 20 secondi end-to-end (download, upload Replicate, prediction, polling, QC, upload output, finalize), 21 in media nel registro del 13 aprile 2026.

Cosa succedeva con trigger a 10 secondi e job da circa 20 secondi: la seconda esecuzione del worker partiva prima che la prima fosse finita, la terza prima che la seconda, la quarta prima che la terza. Tre esecuzioni in parallelo che facevano lock sul database, HTTP request concorrenti su Replicate, scritture simultanee su Drive.

n8n ha un meccanismo interno di throttling per prevenire abuso sulle API esterne. Quando vede troppe richieste HTTP simultanee partire dallo stesso nodo, inizia a buffer-arle e poi a rifiutarle con l’errore “Try spacing your requests out”. Non è un limite di Replicate o Google, è n8n che si auto-protegge (e protegge le API esterne dal tuo workflow rumoroso).

L’errore di throttling si propagava come fallimento del job, che tornava in coda, che veniva ritentato, aumentando ulteriormente la pressione. Circolo vizioso di auto-rinforzo.

Fix applicato: trigger del worker portato a 30 secondi (nel registro dal pomeriggio del 13 aprile, dopo il rilancio raccontato più sotto). Adesso ogni esecuzione ha il tempo di finire prima che parta la successiva. Pipeline più lenta in termini di throughput teorico (120 job/ora invece di 360).

Per aumentare il throughput senza reintrodurre concorrenza distruttiva, abbiamo aggiunto la possibilità di eseguire più worker in parallelo (ciascuno con il proprio workflow trigger 30s) usando il pattern SKIP LOCKED di Postgres per evitare race condition sui job.

Lezione: il trigger interval deve essere sempre più lungo del tempo di esecuzione medio di un singolo job, con un margine del 20-30%. Se vuoi throughput maggiore, la soluzione è parallelizzare con più worker, non triggerare più spesso lo stesso worker. Trigger frequenti su esecuzioni lente = concorrenza distruttiva garantita.

Il recovery — come resettare una pipeline in stato caotico

Dopo i primi fix, la pipeline era ancora in uno stato sporco. Secondo le nostre note di lavoro del 12 aprile, nel log c’erano 60 record di errore e 10 job da rimettere in coda.

Applicare i fix senza resettare lo stato avrebbe significato continuare a processare su un backlog di errori già esistenti. Servivano tre operazioni chirurgiche sul database Supabase (secondo le stesse note, le abbiamo fatte il 12 aprile):

  1. Clean del log errori: TRUNCATE iaflux.error_log per partire pulito. Gli errori storici erano tutti riferiti ai bug fixati, tenerli avrebbe inquinato le statistiche future.
  2. Reset dei job bloccati: UPDATE iaflux.jobs SET status='queued', retry_count=0, prediction_id=NULL, worker_id=NULL, classification=NULL, lock_acquired_at=NULL, lock_expires_at=NULL, error_type=NULL, error_message=NULL WHERE status NOT IN ('success'). Tutti i job che non erano stati completati con successo vengono resettati come nuovi.
  3. Azzeramento cost log: TRUNCATE iaflux.cost_log perché i costi registrati erano tutti riferiti a tentativi falliti, non a reali elaborazioni.

Dopo queste tre query, abbiamo rilanciato la pipeline. Stavolta con le correzioni fatte fino a quel momento, ma con il trigger del worker ancora a 10 secondi. Risultato, il 13 aprile 2026: 4 job sono andati a buon fine e 6 sono finiti in dead letter; dopo quattro reset manuali dei job non ancora completati, fra le 09:57 e le 10:18, alle 10:19 i job completati erano 10 su 10, costo totale $0,1884, classificazione 100% OK (registro della pipeline: completamenti dalle 09:41 alle 10:19). Il passaggio del trigger a 30 secondi risulta nel registro dal pomeriggio dello stesso giorno.

Il recovery è stato rapido; la parte lunga è stata il debug che ci ha portato a capire i tre bug. Lezione collaterale: saper recuperare una pipeline bloccata in stati intermedi è un’abilità separata da saperla progettare. Va esercitata prima di metterla in produzione, non durante il primo incidente.

Le 6 lezioni distillate

Dopo questa esperienza, ho consolidato sei lezioni che applico a ogni pipeline AI in produzione. Non sono consigli generici di manualistica. Sono cicatrici operative.

1. Ogni integrazione tra due sistemi va verificata con il payload reale

“L’ho configurato, dovrebbe funzionare” è la frase che precede ogni disastro. Prima di mettere una pipeline in produzione, intercetta il payload esatto che invii al sistema esterno e verifica che sia quello che il sistema si aspetta. Per Replicate: intercetta l’URL che gli passi e fai una curl -I da locale per vedere cosa restituisce. Per API di gestionali: genera un payload finto e mandalo a mano con Postman prima di automatizzare.

2. Classificare gli errori è più importante che loggare gli errori

Loggare ogni errore è facile e non serve a niente. Classificare gli errori in categorie azionabili (temporary, permanent, budget_exhausted, rate_limited, auth_failed) è difficile ed è il 70% del valore di un sistema robusto. Ogni categoria ha una strategia di reazione diversa: retry, dead letter, alert critico, disattivazione del worker. Senza classificazione, un sistema di retry è un moltiplicatore di problemi, non una soluzione.

3. Ogni pipeline AI deve avere budget check preventivo

Non negoziabile. Prima di ogni esecuzione: confronta la spesa registrata nella giornata con un tetto e non partire se il tetto è raggiunto; è quello che fa la nostra pipeline. Un controllo del credito residuo sull’API AI è un secondo livello utile. Insieme prevengono il caso di bug di consumo anomalo che brucia centinaia di dollari in poche ore. Costo implementativo: basso. Costo del non implementarlo: potenzialmente illimitato.

4. Il trigger interval deve essere sempre più lungo del tempo di esecuzione

Regola empirica: trigger interval = tempo medio di esecuzione + 30% di margine. Se un singolo job impiega 20 secondi, trigger minimo 26 secondi: noi lo arrotondiamo a 30. Per throughput maggiore, parallelizzare con più worker con pattern SKIP LOCKED. Mai aumentare la frequenza di trigger sullo stesso worker sperando in più throughput: ottieni solo concorrenza distruttiva.

5. I lock distribuiti devono avere sempre un timeout e un recovery

Un lock senza timeout è un deadlock in attesa di accadere. Ogni lock in una pipeline (sia esso in Postgres, Redis, o un semaforo applicativo) deve avere un timeout esplicito (tipicamente 2-3x il tempo di esecuzione massimo atteso) e un recovery automatico per i lock scaduti. Senza recovery, il primo crash di un worker lascia un lock orfano che blocca tutto finché qualcuno non lo pulisce a mano.

6. Il recovery da stato caotico va esercitato prima, non durante l’incidente

Quando la pipeline è bloccata e ci sono 60 record di errore nel log, non è il momento di imparare quali query SQL di reset sono sicure e quali no. Scrivi le query di recovery in anticipo, testale su un backup del database di produzione, documentale come procedura operativa. Un bug critico trovato in produzione si recupera in fretta se sai cosa fare, in ore se lo stai scoprendo sotto pressione.

Cosa porterai via da questo post

Il messaggio più importante non è tecnico: è che ogni pipeline AI in produzione può avere incidenti nelle prime settimane, e conviene metterlo in conto fin dall’inizio. L’AI introduce componenti distribuiti nuovi (API esterne con latenza variabile, modelli che consumano risorse in modo non lineare, rate limit nascosti) che creano classi di errori mai viste prima.

La differenza tra una pipeline che sopravvive e una che fallisce non è il codice iniziale. È la capacità di osservare, diagnosticare e recuperare quando le cose vanno storte. Pipeline senza logging strutturato, senza classificazione errori, senza budget check, senza recovery automatico non sono pipeline: sono esperimenti che qualcuno ha messo in produzione troppo presto.

Se stai pianificando un progetto AI per la tua PMI, chiedi al fornitore quali meccanismi di recovery e monitoraggio ha previsto prima ancora di chiedere quale modello AI userà. Se non ha una risposta pronta, cerca un altro fornitore.

Sei domande sull’affidabilità da fare al fornitore

Le sei lezioni, trasformate in domande da fare a chi costruirà la tua automazione. Non servono competenze tecniche per farle: basta ascoltare se la risposta è concreta. Per scegliere il fornitore in generale (proprietà del codice, preventivo, cosa succede dopo la consegna) ci sono le cinque domande della guida sul fornitore; queste sei riguardano solo l’affidabilità del sistema.

  1. Avete provato il collegamento con i dati veri? Non con un esempio di prova, ma con un file reale, nel formato che manderà il tuo sistema (lezione 1).
  2. Come distinguete un errore temporaneo da uno definitivo? Un errore di rete si riprova; un credito esaurito no: riprovare non serve (lezione 2).
  3. Cosa succede se il credito del servizio AI finisce a metà lavoro? La risposta giusta è un controllo della spesa prima di ogni esecuzione, non un avviso a cose fatte (lezione 3).
  4. Ogni quanto parte l’automazione, e quanto dura un’esecuzione? Se parte più spesso di quanto dura, le esecuzioni si accavallano (lezione 4).
  5. Se un’esecuzione si blocca, chi o che cosa la sblocca, e dopo quanto tempo? Ogni blocco deve avere un tempo massimo e un meccanismo che lo rilasci (lezione 5).
  6. Avete una procedura scritta per rimettere in piedi il sistema dopo un blocco? Provata prima, non scritta durante l’incidente (lezione 6).

Domande frequenti

Cosa succede se un’automazione si blocca in produzione?

Una pipeline ben progettata ha un meccanismo di recovery automatico (come WF3 StuckRecovery in iaFlux): i job bloccati vengono rilevati dopo un timeout (nel nostro sistema 10 minuti), il lock viene rilasciato e il job viene rimesso in coda automaticamente. Senza recovery automatico, i job rimangono in stati intermedi (claimed, processing, polling) finché qualcuno non li resetta manualmente con query SQL. Questo può trasformare un blip di 2 minuti in un blocco di ore se nessuno è di guardia al sistema.

Come si gestisce il budget AI in un’automazione in produzione?

Conviene combinare due meccanismi. Primo: un controllo come primo step di ogni esecuzione del worker, che prima di processare qualsiasi job confronta la spesa già registrata nella giornata con un tetto configurabile e, se il tetto è raggiunto, ferma il worker (nel nostro caso il tetto è di 50 dollari di spesa stimata al giorno). Secondo: trattare come permanente l’errore di credito esaurito (HTTP 402), così il job non viene ritentato su un account a credito zero. Questi due meccanismi insieme prevengono sia i retry su un account esaurito sia il caso bug-di-consumo-anomalo.

Quante esecuzioni concorrenti può gestire n8n su un VPS standard?

Il numero di esecuzioni concorrenti che n8n su Docker gestisce su un VPS standard dipende dai workflow e dalle risorse del server: va misurato sul caso reale. Il limite non è tipicamente la CPU ma la contesa sui lock del database (SQLite o Postgres) e il throttling HTTP interno di n8n che protegge le API esterne. Per volumi PMI contenuti un singolo VPS può bastare; per volumi superiori si parallelizzano più worker o si scala verticalmente. Il trigger interval minimo consigliato è 30 secondi per workflow con esecuzioni sopra i 20 secondi.

Quanto tempo prima che un’automazione AI funzioni davvero senza problemi?

Le prime settimane di produzione sono di solito le più problematiche: si scoprono bug di integrazione, edge case non previsti, limiti API non documentati. Con monitoring attivo e iterazioni il tasso di errore di solito scende, ma restano sempre errori transitori: è per questi che serve un recovery automatico.

Le lezioni di questo incidente sono le stesse che oggi tengono in piedi i controlli che possono bocciare il lavoro prima che esca: il caso studio La macchina che boccia il proprio lavoro racconta come funzionano su un cantiere vero, e il portfolio dice quali sistemi abbiamo costruito, con lo stato di ciascuno.

Se stai già correndo in produzione con problemi simili, scrivici a studio@iaflux.it con i sintomi e i log che hai: rispondiamo per iscritto entro un giorno lavorativo con una mappa dei rischi e i meccanismi di protezione che servono dal primo giorno.

Antonio Santoro

Aggiornato il 17 settembre 2026: correzioni di lingua, chiusura scritta e link al caso studio. Dati riferiti a progetti pilota 2026. Metriche verificabili su richiesta.

Aggiornato il 3 ottobre 2026: abbiamo verificato le affermazioni segnalate dai nostri controlli del 14 giugno 2026. Alcune cifre di costo sono ancora in pagina com’erano: le stiamo verificando. Abbiamo corretto il titolo, il riassunto e la descrizione: parlavano di un’automazione fallita «in produzione» e di «60 job persi», ma il 12 e il 13 aprile si trattava dei primi test, non della produzione, e i job del test erano 10. Le cifre e le condizioni del test (esito, tempi, frequenza del worker) vengono ora dal registro della pipeline. Abbiamo corretto la data dei primi test, la durata dei job e la descrizione del controllo del budget; fra le affermazioni segnalate, abbiamo tolto le durate di lavoro, le percentuali di errore e le frasi sulla stabilità, che non avevano una fonte. Abbiamo corretto anche cinque accenti.

Aggiornato il 16 ottobre 2026: aggiunte le sei domande sull’affidabilità da fare al fornitore.

Lo stesso metodo, sui tuoi numeri?

Scrivici cosa deve fare e con quali dati lavora: rispondiamo per iscritto entro un giorno lavorativo, con una proposta e un preventivo fisso.

Scrivici →

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *