L’11 gennaio 2011 LinkedIn ha rilasciato Kafka sotto licenza Apache 2.0 e lo ha descritto come «una coda di messaggi distribuita, persistente ed efficiente». Quello che lo separa dai broker tradizionali è una scelta strutturale: il sistema non tiene uno stato di consegna per ogni messaggio, ma espone un log append-only persistente da cui ogni consumatore legge alla propria posizione.
Contesto e problema
Kafka nasce per tracciare gli eventi di attività di un sito ad alto traffico: pagine viste, query di ricerca, annunci mostrati. LinkedIn parla di un volume giornaliero enorme di eventi, da raccogliere con bassa latenza e da mettere a disposizione di più sistemi a valle — indicizzazione, analisi, monitoraggio.
I broker a code, come quelli che parlano JMS (Java Message Service) o AMQP (Advanced Message Queuing Protocol), hanno una semantica diversa. Un messaggio arriva a un consumatore e poi sparisce dalla coda; il broker tiene traccia di ogni consegna e di ogni acknowledgement. Per il lavoro transazionale funziona bene: ogni messaggio è un’azione da eseguire una volta sola. Funziona molto peggio quando lo stesso flusso di eventi deve passare per più sistemi indipendenti, ciascuno al proprio ritmo, e quando il volume rende caro tenere, messaggio per messaggio, lo stato di ogni destinatario.
Architettura
Kafka raccoglie i messaggi in topic. Ogni topic si divide in partizioni, e ogni partizione è una sequenza ordinata e immutabile a cui si scrive solo in coda. Ogni messaggio scritto in una partizione riceve un numero progressivo, l’offset, che ne segna la posizione. Le partizioni stanno sparse sui nodi del cluster, i broker; è il partizionamento a far crescere il throughput in orizzontale, perché produttori e consumatori lavorano in parallelo su partizioni diverse.
Il produttore pubblica scrivendo in coda alla partizione. Il consumatore legge a partire da un offset e va avanti chiedendo i messaggi successivi: il modello è a pull, cioè è il consumatore a chiedere i dati al broker, non il broker a spingerglieli. Così il broker non deve gestire né conoscere il ritmo di chi legge. Lo stato della lettura — fin dove un consumatore è arrivato — è solo un offset, che tiene il consumatore stesso. Riavvolgere un consumatore o rilavorare un intervallo di messaggi vuol dire rileggere da un offset precedente, senza che il broker ritrasmetta o duplichi niente.
I messaggi non si cancellano dopo la lettura. Restano su disco per un periodo di retention configurato; a cancellarli è l’età, non il consumo. Sul broker il log di una partizione è una directory di file: una sequenza di segmenti scritti nello stesso formato in cui il produttore li ha mandati e in cui il consumatore li legge. Con un solo formato lungo tutta la catena, blocchi del log passano dalla page cache del sistema operativo direttamente alla socket di rete con la chiamata sendfile, senza copie intermedie nello spazio utente. Così Kafka scarica sulla cache del kernel e sull’I/O sequenziale del filesystem gran parte del lavoro che un broker tradizionale farebbe in memoria applicativa.
Il punto critico
Quello che cambia davvero è dove sta lo stato di consumo. In un broker a code lo stato sta nel broker: per ogni messaggio e ogni destinatario deve sapere se la consegna è avvenuta. Questo stato cresce con il numero di consumatori e con i messaggi in volo, e finisce per concentrare lì complessità e costo. In Kafka lo stato di consumo si riduce a un offset per consumatore per partizione, e sta fuori dal percorso di scrittura. Il broker fa una cosa sola: appende al log e serve letture sequenziali a partire da una posizione.
Da qui viene la proprietà più utile per le pipeline di dati. Lo stesso topic lo possono leggere insieme un processo di indicizzazione, uno di analisi e un sistema di monitoraggio, ognuno con il proprio offset, senza che la lettura di uno tocchi quella degli altri e senza che il broker tenga una copia per destinatario. Produttore e consumatore si disaccoppiano del tutto nel tempo: non devono essere accesi nello stesso momento, perché a legarli è il log persistente, non una sessione di consegna.
Implicazioni
Trattare il log come primitiva, e non come dettaglio interno di una coda, cambia il modo di guardare i dati di evento. Il flusso di attività di un sistema diventa un oggetto durevole e rileggibile, non una serie di messaggi da consegnare e dimenticare. Un consumatore aggiunto dopo può partire dall’inizio della retention disponibile e ricostruirsi lo stato dal log, senza che chi produce gli eventi ne sappia niente. Per chi costruisce pipeline che raccolgono e distribuiscono log e metriche, è una base che regge nuovi sistemi a valle senza moltiplicare il costo sul broker.
Il log come fondamento non è un’idea nuova: è la stessa primitiva sotto la replica dei database e i write-ahead log. Kafka la espone come servizio di messaggistica di prima classe, con partizionamento e retention configurabile, invece di tenerla chiusa dentro un singolo sistema di storage.
Limiti
Kafka è un progetto giovane: il codice uscito a gennaio 2011, scritto in Scala, è alla versione 0.x e va preso come tale. La semantica di consegna che il modello a pull con offset rende naturale è at-least-once: se un consumatore va in crash, riprendere da un offset committato può rilavorare messaggi già processati, e tocca all’applicazione gestire l’idempotenza. L’ordine è garantito dentro una partizione, non tra le partizioni di uno stesso topic: chi vuole un ordine totale deve far convergere i messaggi correlati su un’unica partizione, e rinunciare a un po’ di parallelismo. Infine la persistenza su filesystem con retention a tempo va bene per flussi di eventi ad alto volume e basso valore unitario; non rimpiazza un broker transazionale, dove ogni singolo messaggio chiede garanzie di consegna individuali e conferme puntuali.
https://blog.linkedin.com/2011/01/11/open-source-linkedin-kafka http://sna-projects.com/kafka/ https://www.apache.org/licenses/LICENSE-2.0 https://www.noze.it/insights/apache-kafka-open-source/
Immagine di copertina: Diagramma dell’architettura di Apache Kafka: in alto tre produttori, al centro un cluster Kafka con tre topic suddivisi in… — diagramma di Ch.ko123, CC BY 4.0 — https://commons.wikimedia.org/wiki/File:Overview_of_Apache_Kafka.svg