oct_12_2003_delivery

Abbiamo già spiegato perché esiste solo one-way messaging, oggi cerchiamo di sfatare un secondo mito: exactly-once delivery. Che altro non è che l’aspettativa che un messaggio sia sempre consegnato una ed una sola volta.

È possibile?

Si, in un solo scenario:

un trasporto transazionale con il supporto per il DTC in combinazione con un processo di gestione del messaggio che è in grado di fare enlistment nella transazione distribuita.

Quindi se avete MSMQ con le code transazionali, tutto quello che fate quando processate il messaggio è lavorare ad esempio con un database su SQL Server, e infine decidete di accettare di pagare l’obolo del DTC, ecco, siete in grado di gestire il seguente scenario:

  • Il messaggio è in coda
  • Il processo client lo scoda iniziando una transazione con la coda
  • La fase di processamento scrive dei dati su un database transazionale
  • Il processo client committa la transazione
    • Il messaggio viene cancellato dalla coda come processato
    • I dati sono nel DB
  • Qualsiasi cosa vada storta il processo client fa rollback della transazione ed è come se nulla fosse successo

Ovvio che se invece di parlare con un database transazionale stessimo parlando con un endpoint HTTP c’è poco da fare rollback, il messaggio verrebbe riprovato e la chiamata HTTP rifatta. Prima di affrontare questo secondo problema vediamo cosa succede con un trasporto che non va a braccetto con il DTC.

Amazon SQS: at-least-once delivery semantics

Dal punto di vista infrastrutturale Amazon SQS è un broker, internamente per garantire affidabilità e scalabilità l’infrastruttura è basata su repliche master-master, questo significa che una coda X è replicata su più nodi.

Vediamo cosa succederebbe con MSMQ in uno scenario simile: cluster.

  • 4 macchine con MSMQ
  • Windows Network Load Balancing tools per definire il cluster
  • I mittenti vedono una sola coda, quella del cluster
  • Quando un messaggio arriva in realtà arrivano 4 messaggi identici nelle code locali di ognuna delle 4 macchine
  • Inizia una transazione distribuita che porta al consenso su chi processerà il messaggio effettivamente
  • Come potete immaginare, o sapere se avete nozione degli internals del DTC, all’aumentare delle risorse coinvolte nella 2 phase commit diminuiscono le prestazioni. Cioè il tempo impegnato per garantire exactly-once delivery cresce al crescere della richiesta di affidabilità e anche di scalabilità, che è un controsenso: voglio più potenza di fuoco ma la più potenza inficia la potenza di fuoco stessa.

I vendor quindi scappano a gambe levate dal DTC verso il concetto di visibility timeout (ce ne sono svariate implementazioni), il che vuol dire che nell’esempio di cui sopra il dialogo è più o meno il seguente:

  • 4 macchine con i nodi della coda di nostra scelta, ad esempio SQS
  • tools per definire il cluster
  • I mittenti vedono una sola coda, quella del cluster
  • Quando un messaggio arriva la struttura master-master fa si che arrivino 4 messaggi identici nelle code locali di ognuna delle 4 macchine
  • A questo punto una delle 4 dice: preso, è mio per i prossimi x secondi
  • L’infrastruttura master-master propaga questa informazione al cluster e gli altri 3 stanno li buoni buoni nascondendo il messaggio agli eventuali client

Adesso fate scale-out anche dei client, introducendo il così detto modello competing consumers. Quindi 2 client identici, su due nodi diversi, che guardano la stessa coda di input.

Va tutto bene fintantoché…Il carico è moderato, ooops

Al crescere del carico, numero di messaggi ricevuti per secondo, quello che può facilmente succedere è che questo:

  • A questo punto una delle 4 dice: preso, è mio per i prossimi x secondi
  • L’infrastruttura master-master propaga questa informazione al cluster e gli altri 3 stanno li buoni buoni nascondendo il messaggio agli eventuali client

Non si propaghi abbastanza velocemente e se due dei client di cui sopra per sbaglio stanno guardando due nodi diversi (shard) del cluster ecco che entrambi cominciano tranquillamente a processare lo stesso identico messaggio. At-least-once delivery servita su un piatto d’argento.

Nel frattempo dietro le quinte il visibility timeout si è propagato, quindi il nodo che per ultimo darà l’ACK al cluster confermando di aver processato il messaggio riceverà un errore e avrà la possibilità di fare rollback, sempre che possa ovviamente.

Ho usato SQS come esempio perché è quello dove è più facile che il problema si presenti, Azure ServiceBus è anch’esso at-least-once ma i namespace riducono lo scope, facendovi vedere solo un sottoinsieme del cluster riducendo l’impatto del problema, ma introducendo altri problemi che non sono oggetto di questo post.

Dobbiamo preoccuparci?

Neanche per sogno, dobbiamo però sapere bene che come un messaggio potrebbe non essere mai processato allo stesso modo potrebbe essere processato più volte.

Il nostro sistema deve essere ben conscio di tutto ciò e deve accettare che exactly-once delivery non scala o addirittura è impossibile. Ha quindi molto più senso fregarsene e spostare la nostra attenzione su exactly-once processing.

Alla prossima.