Vai al contenuto principale
Producer e Consumer Kafka - immagine ufficiale della lezione su GinnyTech

Producer, Consumer e Serializzazione

Implementare producer e consumer Kafka robusti con pattern di serializzazione ottimale per analytics.

AD
Creato daAndrii Dyshkantiuk
Lezione 113 / 236Livello: AvanzatoDurata: 22 minPrerequisiti: 1

Cosa imparerai

  • Comprendere il problema analitico e il contesto decisionale
  • Applicare esempi, metriche e controlli a casi reali

import json

import time

Producer, Consumer e Serializzazione

Un producer sembra semplice finché un retry duplica eventi, un consumer resta indietro o una serializzazione rompe un servizio a valle. Il lavoro professionale, qui, è progettare chiavi, ack, batch, commit degli offset e formato del messaggio come parti dello stesso contratto. Producer e consumer robusti non sono client library configurate bene: sono contratti eseguibili tra sistemi. La domanda che guida ogni scelta è quale garanzia stai promettendo, in termini di perdita accettabile, duplicati tollerati, ordine necessario, compatibilità del formato e recupero dopo un errore.

Cosa promette davvero un producer

Inviare un messaggio a Kafka sembra banale: istanzi un client e invochi un metodo produce(). La realtà è più complessa, e la differenza tra un’implementazione ingenua e una professionale si misura in durabilità dei dati, throughput e latenza. Un producer Kafka non spedisce immediatamente ogni messaggio sulla rete. Al suo interno opera un’architettura basata su un record accumulator, un buffer di memoria dove i messaggi vengono raggruppati in batch per partizione, e su un sender thread in background che spedisce questi batch ai broker. La nostra abilità sta nel configurare questo meccanismo per bilanciare le esigenze del caso d’uso.

La configurazione che decide la durabilità

La configurazione più critica è acks (acknowledgements), che definisce il livello di garanzia sulla scrittura del messaggio. È qui che dichiari, in modo esplicito, quanto sei disposto a rischiare in cambio di latenza più bassa.

Valore di acksGaranziaQuando usarlo
0Nessuna conferma, throughput massimoTelemetria sacrificabile, log non critici
1Conferma dal solo leaderAnalytics dove un evento perso ogni tanto non sposta le metriche
allConferma da tutte le ISR, nessuna perditaTransazioni e dati che non possono andare persi

La scelta non è tecnica in senso stretto. È una decisione di prodotto travestita da parametro: stai fissando una soglia di rischio accettabile.

Chiavi, ordine e partizionamento

La chiave del messaggio decide la partizione, e la partizione decide l’ordine. Kafka garantisce l’ordine solo all’interno di una partizione, quindi se ti serve che tutti gli eventi di un ordine arrivino in sequenza devi usare order_id come chiave. Il rovescio è che una chiave a bassa cardinalità, o una chiave calda, concentra il traffico su poche partizioni e crea consumer sbilanciati. La scelta della chiave è quindi un compromesso tra ordine garantito e parallelismo, e va fatta guardando ai pattern di accesso reali, non a un’ipotesi astratta.

Consumer e gestione degli offset

Dal lato consumer, il punto delicato è il commit degli offset, cioè il modo in cui Kafka tiene traccia di cosa hai già letto. Se committi prima di aver processato il messaggio rischi di perderlo in caso di crash (delivery at-most-once). Se committi dopo, un crash a metà lavoro ti farà rileggere lo stesso messaggio (delivery at-least-once), e l’applicazione deve essere idempotente per non duplicare gli effetti. La scelta tra commit automatico e manuale è ancora una volta una dichiarazione di garanzia, non una preferenza stilistica.

Esempio: progettare il contratto degli eventi

Immagina un team che deve definire producer e consumer per un flusso di transazioni. La domanda non è qual è la configurazione corretta in assoluto, ma quale scelta diventa meno rischiosa se progettata bene. La tabella mostra come leggere alcune situazioni tipiche.

SituazioneLettura prudenteDecisione
Un retry sembra duplicare eventiIl producer non è idempotenteAttivare l’idempotenza e rendere il consumer at-least-once safe
Una partizione riceve molto più trafficoLa chiave scelta crea hotspotRivedere la chiave o aumentare la cardinalità
Un servizio a valle si rompe dopo un deployIl formato del messaggio è cambiato in modo incompatibileVersionare lo schema e garantire la compatibilità
Il consumer accumula lagCapacità di consumo insufficienteAggiungere istanze fino al numero di partizioni

Serializzazione e compatibilità del formato

Il formato del messaggio è parte del contratto quanto la chiave o gli ack. JSON è leggibile e flessibile ma verboso e senza garanzie di schema, mentre formati come Avro o Protobuf, abbinati a uno schema registry, rendono esplicita la struttura e permettono di evolvere il formato senza rompere i consumer esistenti. La regola pratica è semplice: appena più di un team legge un topic, il formato smette di essere un dettaglio implementativo e diventa un’interfaccia pubblica, con tutte le responsabilità di compatibilità che ne derivano.

Errori tipici da evitare

L’errore più frequente è trattare producer e consumer come configurazioni da copiare invece che come contratti da progettare. Succede quando si sceglie un valore di acks senza chiedersi quanta perdita di dati è accettabile, o una chiave di partizionamento senza guardare la distribuzione reale del traffico. Il sintomo è sempre lo stesso: tutto funziona in sviluppo e si rompe sotto carico.

Tre controlli minimi riducono il rischio. Primo, dichiarare esplicitamente la garanzia di delivery che stai promettendo. Secondo, verificare la distribuzione delle chiavi su un campione di traffico reale prima di andare in produzione. Terzo, versionare lo schema dei messaggi e testare la compatibilità prima di ogni cambio di formato.

Lab ed esercizi

Per consolidare, lavora su tre livelli.

  1. Scrivi in cinque righe quale garanzia vuoi promettere su un topic reale: perdita accettabile, ordine necessario, duplicati tollerati, e quale valore di acks ne consegue.
  2. Costruisci una tabella con quattro colonne (segnale osservato, interpretazione prudente, controllo necessario, azione consigliata) e includi almeno un caso in cui il segnale non basta per decidere.
  3. Trasforma l’esercizio in un decision memo che includa assunzioni, criteri di esclusione, soglia di intervento, sensitivity check e un piano di monitoraggio dopo la decisione.

Per il dato puoi usare un export reale, una dashboard interna o un dataset sintetico, purché contenga almeno una dimensione di segmento, una metrica osservabile e un periodo di confronto.

Checkpoint

Prima di chiudere, collega ogni concetto a una scelta concreta. Quale decisione deve cambiare dopo l’analisi? Quale evento, tabella o metrica rende osservabile il problema? Quale errore di raccolta, modellazione o interpretazione può alterare il risultato? Quale confronto impedisce una lettura isolata? Quale raccomandazione diventa più difendibile? Se un frammento tecnico non chiarisce almeno una di queste aree, trattalo come dettaglio secondario.

Riepilogo operativo

Producer, consumer e serializzazione diventano utili quando riducono l’incertezza su una scelta reale. La forma corretta del lavoro è sempre la stessa: dichiari la garanzia (acks, idempotenza, commit degli offset), scegli la chiave guardando la distribuzione del traffico, fissi il formato come interfaccia pubblica. Ogni configurazione è un compromesso esplicito tra durabilità, ordine, throughput e latenza, e una buona decisione si riconosce perché un altro ingegnere può ricostruirne il ragionamento e arrivare alla stessa conclusione.