Vai al contenuto
English

Blog · Transazioni

I messaggi fantasma di Apache Kafka: transazioni annullate e read_uncommitted

· 3 min di lettura · Claudio Dall'Ara

Un produttore apre una transazione, scrive 20 messaggi, qualcosa va storto e la transazione viene annullata. Per l'applicazione quei 20 messaggi non sono mai esistiti. Eppure apri il topic con il tuo client preferito e li vedi lì, belli in fila, come se niente fosse.

Non è un bug del broker. È una questione di isolation level, e vale la pena capirla prima di passare un'ora a cercare un ordine che «c'è nel topic ma non nel database».

Cosa resta nel log

In Apache Kafka i messaggi di una transazione finiscono nel log subito, mentre vengono scritti. Il broker non aspetta il commit per salvarli. Alla fine della transazione aggiunge un marcatore (control record): COMMIT oppure ABORT.

Quindi una transazione annullata lascia nel log:

  • i suoi messaggi, che restano lì fino alla retention o alla compattazione;
  • un marcatore ABORT, che non si vede come messaggio ma occupa un offset.

Chi legge decide cosa fare di quei messaggi.

I due modi di leggere

L'impostazione è isolation.level del consumer:

ValoreCosa vede
read_uncommittedtutto quello che è nel log, comprese le transazioni annullate e quelle ancora aperte
read_committedsolo i messaggi non transazionali e quelli di transazioni confermate; si ferma al last stable offset (LSO)

Il dettaglio che sorprende: i default non sono uguali fra le librerie. Il consumer Java di Apache Kafka parte da read_uncommitted. librdkafka, la libreria in C usata da molti client in altri linguaggi, parte da read_committed.

Quindi due programmi che leggono lo stesso topic, entrambi «con le impostazioni di base», possono mostrare numeri diversi.

Una prova concreta

Nel nostro banco di correttezza abbiamo scritto un topic con 40 messaggi confermati e 20 di una transazione annullata, poi abbiamo chiesto a due client di contarli:

DomandaAttesoClient con read_committedClient con read_uncommitted
Messaggi della transazione annullata0020
Tutto il topic404060

Il secondo client non sbaglia a leggere: legge tutto il log. Ma se stai cercando di capire cosa è successo davvero nella tua applicazione, quei 20 messaggi sono rumore, e a volte rumore pericoloso.

Il last stable offset

Con read_committed c'è un effetto collaterale da conoscere: il consumer non va oltre il last stable offset, cioè il primo offset che appartiene a una transazione ancora aperta. Se un produttore apre una transazione e resta appeso, chi legge in read_committed si ferma lì, anche se dopo ci sono messaggi già confermati di altri produttori.

Se una partizione «non avanza» per un consumer transazionale ma il suo high watermark cresce, la prima cosa da guardare è proprio una transazione rimasta aperta. Si chiude da sola allo scadere di transaction.timeout.ms del produttore.

Come accorgersene

  • Controlla l'isolation level del client con cui stai guardando i dati, non solo quello dell'applicazione.
  • Gli offset hanno dei buchi: ogni marcatore di transazione occupa un offset. Se fra un messaggio e il successivo manca un numero, lì c'è un marcatore.
  • Confronta i conteggi: se «fine meno inizio» non torna con il numero di messaggi letti, in mezzo ci sono marcatori, transazioni annullate o messaggi compattati.

In Topicup

Topicup legge in read_committed: quello che vedi è quello che le applicazioni consumer con le impostazioni di default di librdkafka vedono. Un messaggio di una transazione annullata non compare né nella lettura né nella ricerca.