Vai al contenuto
DedicatedPHP Contatto

I retry non bastano: progettare la riconciliazione dei job asincroni in PHP

Scopra come verificare i risultati di business, rilevare incoerenze e riparare i job asincroni in PHP senza affidarsi solo ai retry.

Diagramma di riconciliazione dei job asincroni in un'applicazione PHP

I job asincroni consentono di disaccoppiare importazioni, sincronizzazioni, notifiche, generazione di documenti e integrazioni. Tuttavia, il fatto che un consumer abbia elaborato un messaggio non dimostra necessariamente che il risultato di business sia corretto. Potrebbe essere stato registrato un successo prima di confermare un effetto esterno, potrebbe essersi verificato un arresto tra due passaggi oppure la stessa operazione potrebbe essere stata eseguita più di una volta.

La riconciliazione dei job asincroni in PHP copre questa differenza: confronta ciò che il sistema si aspettava di ottenere con l'evidenza di ciò che è accaduto, rileva assenze o discrepanze e attiva una correzione controllata. Non sostituisce la coda, i retry né l'idempotenza; li completa con una verifica indipendente.

Un'esecuzione tecnica non equivale a un risultato di business

Un'esecuzione tecnica non equivale a un risultato di business — guía visual de DedicatedPHP

Un'attività può terminare senza un'eccezione e, ciononostante, lasciare un processo incompleto. Per esempio, un'applicazione crea una richiesta di sincronizzazione, il consumer chiama un'API esterna e riceve una risposta non conclusiva a causa di un'interruzione di rete. Se ritenta senza una chiave idempotente, può creare un duplicato. Se presume il successo, può lasciare il record non sincronizzato.

Non si deve nemmeno dare per scontata una semantica di consegna specifica dell'infrastruttura di messaggistica. La possibilità di redelivery ed esecuzioni duplicate dipende dal broker, dalla sua configurazione di persistenza, dai riconoscimenti, dal comportamento del consumer e dai guasti che si verificano. Il design deve verificare queste proprietà nella tecnologia scelta e, quando possono verificarsi duplicati o riordinamenti, tollerarli esplicitamente.

La domanda operativa non è solo «il messaggio è stato consumato?», ma «posso dimostrare che l'effetto atteso esiste, una sola volta quando appropriato, e con i dati corretti?». Questa dimostrazione richiede una fonte di evidenza: una risposta consultabile del sistema esterno, un identificatore remoto persistito, un documento archiviato o una modifica di stato confermata.

Retry, idempotenza e riconciliazione: responsabilità diverse

I retry gestiscono errori transitori: indisponibilità temporanea, limiti di utilizzo, brevi blocchi o problemi di rete. È opportuno definire limite dei tentativi, ritardo progressivo, classificazione degli errori e destinazione per i messaggi che richiedono attenzione. Ritentare indefinitamente può nascondere un errore nei dati o aggravare un incidente esterno.

L'idempotenza rende sicuro ripetere un'operazione. Può essere ottenuta con un identificatore stabile dell'operazione inviato a un provider esterno, un vincolo univoco nel database o un controllo transazionale prima dell'effetto. Non significa che l'effetto si sia verificato: significa che una ripetizione non dovrebbe moltiplicarlo.

La riconciliazione cerca operazioni in sospeso, incomplete o contraddittorie e decide cosa fare con ciascuna. È particolarmente necessaria quando sono presenti effetti esterni, processi batch, aggiornamenti di più sistemi o comunicazioni la cui ricezione non può essere dimostrata solo dall'applicazione mittente.

  • Usi i retry per riprovare errori classificati come transitori.
  • Usi l'idempotenza per impedire che retry o redelivery duplicano gli effetti.
  • Usi la riconciliazione per verificare lo stato finale e riparare le differenze rilevate.

Modellare l'operazione e conservare evidenza verificabile

Un design manutenibile separa tre concetti. Il job richiesto rappresenta l'intenzione, per esempio, «sincronizzare l'ordine 452». L'effetto atteso definisce il risultato osservabile: «il sistema esterno contiene l'ordine con la versione 7». La conferma memorizza l'evidenza che tale risultato esiste: identificatore remoto, versione, timestamp, risposta validata o risultato di una query successiva.

Prima di pubblicare un messaggio, crei un record di esecuzione in un database durevole. Se l'applicazione modifica dati propri e pubblica un messaggio, consideri il pattern outbox: salvi la modifica di business e l'evento in attesa nella stessa transazione, e deleghi la pubblicazione a un processo successivo. In questo modo si riduce il rischio di confermare la modifica locale e perdere il messaggio, oppure di pubblicare un messaggio per una modifica che è stata annullata.

Il record deve includere, come minimo:

  • operation_id immutabile e univoco, usato per correlare messaggi, log e chiamate esterne.
  • Tipo di operazione, entità interessata e versione o impronta del contenuto atteso.
  • Stato attuale, numero di tentativi, tentativo successivo consentito e timestamp.
  • Chiave idempotente e, se esiste, identificatore della risorsa remota.
  • Evidenza sintetica e riferimenti sicuri a risposte o errori, senza registrare segreti né dati personali non necessari.
  • Motivo di chiusura, compensazione, scarto o escalation alla revisione umana.

Definisca transizioni esplicite, per esempio: pending, processing, awaiting_confirmation, confirmed, retry_scheduled, manual_review, compensated e not_applicable. Ogni transizione deve avere un responsabile e una condizione verificabile. Un aggiornamento condizionale, come passare a processing solo se lo stato precedente è pending, riduce le race condition tra consumer.

Costruire il processo di riconciliazione

La riconciliazione può essere eseguita tramite un comando PHP pianificato, un worker dedicato o un flusso operativo. Deve lavorare con finestre temporali: non esamini operazioni create pochi secondi fa se l'integrazione esterna richiede normalmente diversi minuti. Definisca la finestra con dati reali di latenza e la riesamini quando cambiano limiti o provider.

Per ogni operazione idonea, confronti fonti di verità definite in precedenza. Il database locale può essere autorevole riguardo all'intenzione e alla versione del dato; il sistema esterno, riguardo al fatto che abbia ricevuto o creato la risorsa. Quando non esiste una query affidabile sulla destinazione, l'evidenza può essere una ricevuta firmata, un identificatore del provider o un controllo differito tramite file di risultato.

  1. Selezioni operazioni non confermate che superano il termine previsto.
  2. Verifichi se l'effetto esiste usando operation_id, la chiave idempotente o una chiave di business inequivocabile.
  3. Confronti i campi rilevanti e le versioni, non solo l'esistenza della risorsa.
  4. Classifichi il caso come assente, corretto, divergente, ambiguo o non applicabile.
  5. Esegua l'azione autorizzata e salvi la decisione con la relativa evidenza.

Un risultato ambiguo non deve trasformarsi automaticamente in un reinserimento in coda. Se una chiamata potrebbe aver creato una risorsa ma non c'è modo di consultarla in modo affidabile, ritentare potrebbe duplicare un addebito, una notifica o un documento. In questi casi, blocchi l'azione automatica e invii il caso a un pannello delle eccezioni con contesto sufficiente per decidere.

Correggere senza introdurre nuovi danni

L'azione dipende dalla discrepanza e dal costo di sbagliare. Reinserire in coda è appropriato quando l'effetto manca e l'operazione è idempotente. Compensare può annullare un effetto errato mediante un'operazione di business esplicita, non tramite una cancellazione tecnica indiscriminata. Contrassegnare per revisione è preferibile in presenza di ambiguità, conflitto di versioni o conseguenze finanziarie. Chiudere come non applicabile serve quando l'entità è stata annullata o sostituita secondo regole documentate.

Anche le riparazioni manuali devono lasciare traccia: chi ha preso la decisione, quale evidenza ha consultato, quale azione ha applicato e quale è stato il risultato. Limiti i permessi ed eviti pulsanti che eseguono un'operazione senza mostrare l'entità, la versione, la destinazione e il rischio di duplicazione.

Osservabilità e test che validano il design

I log correlati tramite operation_id facilitano il tracciamento di un'operazione tra web, worker e servizi esterni. Le metriche utili non si limitano alle eccezioni: misuri l'età delle operazioni in sospeso, la quantità in revisione manuale, il tasso di divergenze, i retry per causa e il tempo fino alla conferma. Gli avvisi devono attivarsi per accumulo, anzianità o mancato rispetto del termine, non per ogni errore isolato.

Provi guasti rappresentativi: arresto dopo l'effetto esterno e prima di persistere la conferma; esecuzione duplicata; messaggio fuori ordine; riavvio del worker; timeout con risultato remoto incerto; indisponibilità prolungata; e modifiche di versione mentre un'operazione resta in sospeso. Il test deve verificare sia lo stato finale sia l'assenza di duplicati e la qualità dell'evidenza memorizzata.

Esempio: sincronizzare un record con un sistema esterno

Si supponga che un'applicazione PHP sincronizzi un record cliente. Al momento della modifica, crea l'operazione sync_customer con un identificatore stabile e la versione locale attesa. Il worker invia questi valori alla destinazione come chiave idempotente. Se riceve una conferma valida, persiste l'identificatore remoto e modifica lo stato in confirmed.

Se il timeout si verifica dopo l'invio della richiesta, il worker lascia l'operazione in awaiting_confirmation. Il riconciliatore interroga la destinazione tramite la chiave idempotente. Se trova la stessa versione, conferma. Se non la trova, pianifica un nuovo invio. Se trova una versione diversa, contrassegna il caso per revisione invece di sovrascrivere dati che potrebbero essere stati modificati legittimamente nell'altro sistema.

Checklist per introdurre la riconciliazione senza riscrivere il processo

Checklist per introdurre la riconciliazione senza riscrivere il processo — guía visual de DedicatedPHP
  • Faccia l'inventario dei job esistenti e dia priorità a quelli che producono effetti esterni, riguardano denaro, dati regolamentari o processi difficili da ripetere.
  • Per ogni tipo, documenti l'effetto atteso, la fonte di verità e l'evidenza che consentirà di confermarlo.
  • Verifichi nel broker e nei consumer specifici cosa accade in caso di arresti, riconoscimenti tardivi, persistenza, redelivery e ordine dei messaggi.
  • Aggiunga un operation_id stabile e lo propaghi nel messaggio, nei log, nelle chiamate esterne e nei record di stato.
  • Introduca una tabella delle operazioni con stati, tentativi, termini, chiave idempotente ed evidenza; inizi in modalità osservazione se necessario.
  • Definisca transizioni condizionali e una policy scritta per ritentare, confermare, compensare, fare escalation o chiudere come non applicabile.
  • Implementi un riconciliatore limitato a una finestra temporale e a un tipo di operazione pilota.
  • Validi con casi di duplicato, timeout ambiguo, arresto tra passaggi, riordinamento e riavvio prima di automatizzare le correzioni.
  • Crei un pannello o una query delle eccezioni con anzianità, entità, evidenza e azione consigliata.
  • Riveda periodicamente metriche, operazioni bloccate e decisioni manuali per adeguare termini, regole e controlli.

L'adozione graduale consente di migliorare l'affidabilità senza sostituire l'intera architettura: prima renda visibili le operazioni incerte, poi confermi i risultati e, infine, automatizzi solo le correzioni di cui può dimostrare la sicurezza.

Vuoi applicare queste idee al tuo progetto?Parliamo della tua piattaforma PHP.
Visualizza il servizio correlato