Vai al contenuto principale
Kafka architettura - immagine ufficiale della lezione su GinnyTech

Kafka: fondamenti e architettura

Architettura interna di Kafka: broker, replicazione, leader election e garanzie di delivery.

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

Cosa imparerai

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

import pandas as pd

Kafka: fondamenti e architettura

Un broker Kafka non è una coda generica. Topic, partizioni, consumer group e retention definiscono come l’organizzazione pubblica gli eventi, li conserva e li rilegge. La lezione è tecnica, ma il punto non è accumulare definizioni: è capire quale decisione di disegno cambia quando scegli una configurazione invece di un’altra. Se sbagli la chiave di partizionamento, il problema non resta tecnico. Diventa ordine degli eventi rotto, consumer sbilanciati e analisi a valle che non tornano.

Perché l’architettura conta

Il problema vero non è conoscere Kafka in astratto, ma decidere come disegnare i topic quando hai vincoli reali: volumi che crescono, consumer che restano indietro, formati che devono restare compatibili nel tempo. Conviene leggere l’architettura come una mappa delle responsabilità. Chi produce, chi consuma, chi possiede il topic, quanto dura la retention e cosa succede quando un consumer accumula ritardo. Le basi servono nel momento in cui ti permettono di prevedere il comportamento del sistema sotto carico, prima che sia in produzione.

Da qui in avanti ogni concetto va collegato a una scelta concreta: come partizionare, quale livello di garanzia chiedere alla scrittura, quanta retention tenere. Se una nozione non cambia una di queste decisioni, è solo terminologia.

Replicazione e fault tolerance

Ogni partizione ha UN leader e N follower, configurati con replication.factor. Il leader gestisce tutte le letture e scritture, mentre i follower replicano passivamente. Se un broker muore, uno dei follower viene eletto leader.

Le ISR (In-Sync Replicas) sono l’insieme di repliche allineate al leader, cioè con un lag inferiore a replica.lag.time.max.ms (default 30s). Solo una ISR può essere eletta leader. Se imposti min.insync.replicas=2, almeno due broker devono confermare una scrittura prima che sia considerata committed. Questo protegge dalla perdita di dati nel caso in cui un broker muoia subito dopo aver ricevuto una scrittura.

Garanzie di delivery: acks

Il producer sceglie il livello di garanzia con il parametro acks:

  • acks=0: fuoco e dimentica, nessuna garanzia, throughput massimo.
  • acks=1: il leader conferma. Se il leader muore prima che i follower replichino, i dati vanno persi.
  • acks=all (o -1): tutte le ISR confermano, nessuna perdita di dati, latenza più alta.

Per l’analytics, acks=1 è spesso sufficiente, perché un evento perso ogni tanto non sposta le metriche. Per le transazioni finanziarie serve invece acks=all. La scelta è esattamente il tipo di decisione che l’architettura deve rendere esplicita: stai dichiarando quanta perdita di dati sei disposto a tollerare in cambio di latenza.

Idempotenza e transazioni

L’idempotent producer (enable.idempotence=true) garantisce che un messaggio inviato più volte per via dei retry venga scritto una volta sola. Kafka assegna un Producer ID e un sequence number a ogni messaggio, e il broker deduplica.

Le transazioni (transactional.id) permettono di scrivere atomicamente su più topic o partizioni: o tutti i messaggi sono committed, o nessuno lo è. Sono il meccanismo che abilita l’exactly-once processing in Kafka Streams.

Dimensionamento del cluster

Tre numeri guidano il dimensionamento. Le partizioni non devono mai scendere sotto il throughput target diviso 10 MB/s, perché una singola partizione regge circa 10-20 MB/s in scrittura: per 1 GB/s servono circa 100 partizioni. La retention bilancia costo di storage e valore analitico, e 7 giorni è lo standard, mentre per audit e compliance si sale a 30-90 giorni. Sul fronte broker, il minimo per la fault tolerance è 3, e ogni broker regge circa 1-2 Gbps di throughput con hardware standard.

Esempio: la decisione di partizionamento

Immagina un team che deve progettare i topic per ordini, pagamenti e spedizioni. La scelta critica è se partizionare per order_id, customer_id o area geografica, perché da quella decisione dipendono l’ordine degli eventi, il parallelismo dei consumer e la facilità di ricostruzione storica. La tabella seguente mostra come leggere alcuni segnali tipici prima di decidere.

SituazioneLettura prudenteDecisione
Il throughput migliora ma su una baseline deboleIl guadagno potrebbe dipendere dal campione di caricoRafforzare il test prima di scalare le partizioni
Una sola partizione riceve la maggior parte del trafficoLa chiave scelta crea hotspotRivedere la chiave o aumentare la cardinalità
Il costo operativo cresce con le partizioniPiù partizioni significano più overhead di coordinamentoFissare una soglia economica esplicita
I consumer producono conteggi incoerentiL’ordine per chiave non è garantito come previstoCorreggere ownership del topic e contratto degli eventi

Caso reale: Netflix e la disciplina delle metriche

Netflix è un esempio utile perché ha costruito molte decisioni di prodotto intorno a segnali comportamentali osservabili: completamento degli episodi, tempo di ricerca prima della riproduzione, abbandono dopo pochi minuti, ritorno nella settimana successiva, efficacia delle raccomandazioni. Il punto non è che ogni azienda debba copiare Netflix, ma il metodo: il dato non viene trattato come ornamento, viene trattato come infrastruttura decisionale.

Quando Netflix valuta una modifica all’esperienza, per esempio una nuova riga di raccomandazioni, una diversa immagine di copertina o un algoritmo di ranking, non misura solo il click immediato. Misura anche segnali di qualità: l’utente guarda davvero il contenuto, torna nei giorni successivi, riduce il tempo speso a cercare. Questa disciplina impedisce di ottimizzare vanity metric che sembrano positive nel breve periodo ma erodono valore nel lungo. Lo stesso vale per Kafka: la scelta architetturale deve essere collegata a un outcome misurabile, altrimenti resta un esercizio di stile.

Esempio SQL: costruire una vista di controllo

Il pattern seguente è volutamente generico ma eseguibile nella maggior parte dei warehouse moderni. Serve a creare una base analitica con metrica, segmento e finestra temporale, così da confrontare periodi e gruppi senza riscrivere la logica ogni volta.

WITH base_events AS (
  SELECT
    user_id,
    account_id,
    event_type,
    event_time,
    DATE_TRUNC('week', event_time) AS week,
    source,
    device_type
  FROM events
  WHERE event_time >= CURRENT_DATE - INTERVAL '180 days'
    AND user_id IS NOT NULL
),
weekly_user_metrics AS (
  SELECT
    week,
    user_id,
    COALESCE(source, 'unknown') AS source,
    COALESCE(device_type, 'unknown') AS device_type,
    COUNT(*) AS total_events,
    COUNT(DISTINCT DATE(event_time)) AS active_days,
    COUNT(DISTINCT event_type) AS event_diversity,
    MAX(CASE WHEN event_type IN ('purchase', 'subscribe', 'activation') THEN 1 ELSE 0 END) AS reached_key_outcome
  FROM base_events
  GROUP BY week, user_id, source, device_type
)
SELECT
  week,
  source,
  device_type,
  COUNT(DISTINCT user_id) AS users,
  ROUND(AVG(active_days), 2) AS avg_active_days,
  ROUND(AVG(event_diversity), 2) AS avg_event_diversity,
  ROUND(AVG(reached_key_outcome) * 100, 2) AS key_outcome_rate
FROM weekly_user_metrics
GROUP BY week, source, device_type
ORDER BY week, source, device_type;

La query non è la risposta finale. Crea una superficie di osservazione (trend, segmenti, differenze tra canali, variazioni nel tempo) da cui formulare ipotesi più precise.

Esempio Python: controllare stabilità e anomalie

Una metrica utile deve essere abbastanza stabile da orientare decisioni e abbastanza sensibile da segnalare cambiamenti reali. In Python puoi controllare le variazioni anomale settimana su settimana con un semplice z-score mobile.


# df contiene: week, segment, users, key_outcome_rate
# key_outcome_rate espresso in percentuale, es. 12.4

df = df.sort_values(['segment', 'week']).copy()
df['previous_rate'] = df.groupby('segment')['key_outcome_rate'].shift(1)
df['wow_change_pp'] = df['key_outcome_rate'] - df['previous_rate']
df['rolling_mean'] = df.groupby('segment')['key_outcome_rate'].transform(
    lambda s: s.rolling(4, min_periods=2).mean()
)
df['rolling_std'] = df.groupby('segment')['key_outcome_rate'].transform(
    lambda s: s.rolling(4, min_periods=2).std()
)
df['z_score'] = (df['key_outcome_rate'] - df['rolling_mean']) / df['rolling_std']

anomalies = df[df['z_score'].abs() >= 2].sort_values('z_score')
print(anomalies[['week', 'segment', 'key_outcome_rate', 'wow_change_pp', 'z_score']])

Il valore di questo controllo è pratico. Evita di reagire a ogni oscillazione casuale e segnala solo quando una variazione merita un’indagine. In un contesto aziendale può alimentare alert, review settimanali e retrospettive di prodotto.

Errori tipici da evitare

L’errore più frequente è trattare l’architettura come un’etichetta invece che come un criterio di scelta. Succede quando si presenta un numero senza dire quale decisione cambia, quale baseline lo rende interpretabile e quale rischio resta aperto. In quel caso il dato sembra preciso, ma non guida l’azione.

Ce ne sono altri tre da tenere d’occhio. Il primo è lavorare su dati aggregati troppo presto: una media globale può nascondere due segmenti che si muovono in direzioni opposte. Il secondo è non controllare la qualità del dato, perché eventi duplicati, tracking incompleto, timezone incoerenti e cambi di definizione producono conclusioni false. Il terzo è confondere correlazione e causalità: se gli utenti che usano una feature convertono di più, non significa che la feature causi conversione, potrebbero usarla perché erano già più motivati. Per ridurre questi rischi conviene tenere sempre una definizione esplicita della metrica, un confronto per segmento e una verifica contro un periodo precedente.

Lab ed esercizi

Per fissare i concetti, lavora su tre livelli di difficoltà crescente.

  1. Scrivi in cinque righe quale decisione architetturale concreta vuoi prendere su un topic reale: indica chiave di partizionamento, livello di acks, retention e rischio principale.
  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.

Come materiale 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 considerare chiusa la lezione, rispondi a queste domande. Quale decisione di disegno concreta vuoi migliorare? Quale unità di analisi rende il problema misurabile? Quale baseline useresti per evitare una lettura isolata? Quale assunzione, se falsa, cambierebbe la conclusione? Quale controllo presenteresti prima di raccomandare un’azione a uno stakeholder?


Riferimenti:

  • Confluent. (2024). “Kafka Configuration.” docs.confluent.io.
  • Jun Rao. (2015). “Exactly-once Semantics in Kafka.” Confluent Blog.

Riepilogo operativo

L’architettura di Kafka diventa utile quando riduce l’incertezza su una scelta reale, non quando aggiunge terminologia. Le scelte che contano sono poche e concrete: come partizionare, quale livello di acks chiedere, quanta retention tenere, quante repliche garantire. Ognuna è un compromesso esplicito tra durabilità, latenza, costo e ordine degli eventi. Una buona decisione si riconosce perché un altro ingegnere può riprodurre il ragionamento, criticare le assunzioni e arrivare alla stessa conclusione partendo dagli stessi dati.