Cosa succede dopo che un producer Kafka chiama send()?
Sembra una domanda semplice. Il producer crea un record, lo invia a Kafka e riceve un acknowledgement.
Tuttavia, tra questi passaggi può accadere parecchio.
Il producer può attendere altri record e creare un batch. Deve scegliere una partizione. Può comprimere il batch. Se il broker è lento, il producer può esaurire lo spazio del buffer. Se una richiesta fallisce, può riprovarla. Se il retry ha successo dopo che la prima richiesta era già stata scritta, Kafka può ricevere due volte lo stesso batch.
Kafka Simulator v1.1 si concentra su questa semantica del producer.
La release aggiunge 10 nuovi scenari, portando il curriculum completo a 23 scenari su 125.
Cosa include la v1.1?
Otto dei dieci scenari si concentrano sul producer. I due rimanenti estendono il percorso sull’anatomia del log: la lettura fino all’high watermark e il log start offset.
Gli scenari del producer coprono cinque aree:
- batching con
linger.msebatch.size, - i partitioner del producer,
- la compressione,
- il backpressure con
buffer.memoryemax.block.ms, - retry e idempotenza.
Vediamoli uno per uno.
Batching con linger.ms e batch.size
Un producer Kafka non invia necessariamente ogni record in una richiesta separata.
I record assegnati alla stessa partizione possono essere raccolti in un batch. Il producer invia il batch quando questo si riempie oppure quando scade il tempo di attesa configurato.
È qui che diventano importanti due impostazioni del producer:
batch.sizelimita la dimensione di un batch,linger.msconsente al producer di attendere altri record prima di inviarlo.
Il compromesso è tra latenza e throughput.
Un batch più grande di solito significa meno richieste e una compressione migliore. Tuttavia, attendere che il batch si riempia può aumentare la latenza di un singolo record.
I nuovi scenari rendono visibile questo comportamento. Puoi vedere quando i record entrano in un batch, cosa provoca l’invio del batch e come la modifica della configurazione influisce sulle richieste prodotte dal client.
I partitioner del producer
Prima che il producer possa creare un batch specifico per una partizione, deve selezionare una partizione.
Per i record con chiave, la chiave viene usata per scegliere la partizione. I record con la stessa chiave vengono quindi normalmente assegnati alla stessa partizione.
Per i record senza chiave, il comportamento è diverso. Il producer può raggruppare i record usando la propria strategia di partizionamento invece di calcolare la partizione da una chiave.
Il simulatore mostra entrambi i casi passo dopo passo:
- il producer riceve il record,
- il partitioner seleziona una partizione,
- il record viene aggiunto al batch corrispondente,
- il batch viene inviato al leader di quella partizione.
Questo rende più facile capire perché il partizionamento influisce sia sull’ordinamento sia sul batching.
La compressione avviene per batch
La compressione del producer Kafka viene applicata ai batch di record, non a ogni singolo record in modo indipendente.
Questa distinzione è importante.
La compressione è di solito più efficace quando un batch contiene più record simili. Un batch che contiene un solo record piccolo può trarre pochissimo vantaggio dalla compressione. Un batch più grande con dati ripetitivi può essere compresso in modo molto più efficiente.
Di conseguenza, la compressione è legata alla configurazione del batching. Impostazioni come batch.size e linger.ms possono influenzare non solo la frequenza delle richieste, ma anche la forma e la comprimibilità dei batch risultanti.
I nuovi scenari mostrano le dimensioni dei batch non compressi e compressi, rendendo esplicita questa relazione.
Backpressure con buffer.memory e max.block.ms
Cosa succede quando un’applicazione produce record più velocemente di quanto Kafka riesca ad accettarli?
Il producer non può mantenere in memoria un numero illimitato di record non inviati. Utilizza un buffer limitato, configurato tramite buffer.memory.
Quando il buffer non ha spazio disponibile, una chiamata a send() può bloccarsi in attesa che venga liberata della memoria.
Non attenderà per sempre. Il tempo massimo di blocco è controllato da max.block.ms.
Questo ci dà due possibili esiti:
- vengono inviati e confermati abbastanza dati, liberando spazio nel buffer,
- non si libera spazio prima del timeout e l’invio fallisce.
Il simulatore traccia direttamente il buffer del producer. Puoi vedere la memoria allocata quando i record vengono aggiunti e rilasciata quando le richieste si completano.
Questo è particolarmente utile in combinazione con un broker lento o una quota di throughput. Invece di trattare il backpressure del producer come un problema astratto di configurazione, puoi osservare il buffer riempirsi e vedere esattamente quale invio viene bloccato.
I retry e il problema dei duplicati
Supponiamo che il producer invii un batch a un broker.
Il broker scrive il batch con successo, ma l’acknowledgement viene perso prima di raggiungere il producer.
Dal punto di vista del producer, la richiesta è fallita. Non può sapere se il broker ha scritto i dati. Riprovare è quindi ragionevole.
Tuttavia, se la prima richiesta era stata scritta con successo, il retry può accodare di nuovo lo stesso batch.
Questo è il problema dei duplicati causato dai guasti ambigui.
Lo scenario di retry mostra entrambe le richieste:
- la richiesta originale raggiunge il broker,
- l’acknowledgement viene perso,
- il producer riprova il batch,
- il broker riceve gli stessi record per la seconda volta.
Senza idempotenza, entrambe le copie possono essere accodate.
La semantica del producer idempotente
Un producer idempotente consente a Kafka di riconoscere i batch ritentati.
Kafka assegna al producer un’identità e traccia i numeri di sequenza in modo indipendente per ogni partizione. Quando un batch viene ritentato con un numero di sequenza che il broker ha già elaborato, il broker può confermarlo senza accodare di nuovo i record.
La distinzione importante è tra un retry e un vero duplicato.
Due record possono contenere esattamente la stessa chiave e lo stesso valore ed essere comunque due record separati inviati intenzionalmente dall’applicazione. Kafka non deve rimuovere tali record.
L’idempotenza deduplica i retry del protocollo del producer. Non deduplica i dati dell’applicazione in base al loro contenuto.
I nuovi scenari mostrano:
- i producer ID,
- i numeri di sequenza per partizione,
- i retry idempotenti riusciti,
- i veri record duplicati,
- il rilevamento delle sequenze fuori ordine.
Perché è importante max.in.flight.requests.per.connection?
Un producer può avere più richieste in attesa di risposta contemporaneamente.
Questo migliora il throughput, ma crea anche un potenziale problema di ordinamento.
Supponiamo che il producer invii il batch A e poi il batch B. Il batch A fallisce temporaneamente, mentre il batch B ha successo. Se A viene ritentato dopo che B è già stato scritto, i record potrebbero essere accodati in un ordine diverso.
L’idempotenza usa i numeri di sequenza per rilevare gli ordinamenti non validi.
Il simulatore lo mostra esplicitamente. Puoi vedere il numero di sequenza atteso dal broker, il numero trasportato dal batch in arrivo e il punto in cui il broker rifiuta una richiesta fuori ordine.
Ispezionare lo stato interno del producer
I nuovi scenari si appoggiano molto all’inspector del simulatore, che espone lo stato interno di producer e partizioni, tra cui:
- i contatori dei retry,
- i numeri di sequenza del producer,
- i numeri di sequenza del broker,
- l’allocazione del buffer.
Normalmente, questi valori sono nascosti dietro le metriche del producer, i log e il comportamento del protocollo. Nel simulatore possono essere ispezionati direttamente mentre si avanza o si torna indietro in uno scenario.
Correzioni al modello del producer
L’aggiunta dei nuovi scenari ha richiesto anche di rendere più preciso il modello del producer esistente.
I retry vengono conteggiati per batch
I retry di Kafka producono richieste che contengono batch di record.
Una richiesta fallita dovrebbe quindi consumare un retry per l’intero batch, non un retry per ogni record al suo interno.
Il simulatore ora segue questo modello. Ogni record di un batch fallito viene inviato di nuovo, mentre il contatore dei retry avanza una sola volta.
La memoria del buffer viene rilasciata con precisione
La memoria del buffer del producer viene ora rilasciata man mano che arrivano gli acknowledgement.
Questo corregge i casi in cui il simulatore poteva segnalare backpressure anche se i record confermati avrebbero già dovuto rilasciare la propria memoria.
Lo stesso vale per i timeout di max.block.ms. Quando un invio bloccato va in timeout, la sua allocazione di buffer viene ripulita.
acks=all viene tracciato per partizione
La durabilità di Kafka è definita in modo indipendente per ogni partizione.
Un producer che scrive su più partizioni deve quindi attendere che la partizione corretta soddisfi i propri requisiti di acknowledgement. L’avanzamento in un’altra partizione non deve confermare una richiesta non correlata.
Il simulatore ora traccia questo stato separatamente per ogni partizione e topic.
Il reset del producer azzera lo stato di idempotenza
Il broker memorizza lo stato di deduplicazione associato a un producer idempotente.
Quando un producer simulato viene rimosso o resettato, anche questo stato deve essere rimosso. Altrimenti, un producer appena creato potrebbe vedere i propri record classificati erroneamente come vecchi retry.
Il comportamento di reset ora azzera il corrispondente stato lato broker.
Il rewind ripristina lo stato dei retry
Gli scenari possono essere avanzati e riavvolti un passo alla volta.
Il rewind ora ricostruisce i contatori dei retry usati durante ogni round di simulazione. Lo stato del producer visibile è quindi coerente indipendentemente dal fatto che lo scenario si muova in avanti o all’indietro.
Miglioramenti al playground
La release contiene anche diverse correzioni al di fuori del modello del producer.
Il rebalancing dei consumer group
I consumer group nel playground hanno ricevuto diverse correzioni di correttezza:
- lo sticky assignor ora minimizza lo spostamento delle partizioni invece di riassegnarle da zero,
- un membro statico che rimane assente più a lungo di
session.timeout.msviene espulso come qualsiasi altro membro, - la modifica del numero di partizioni di un topic ora attiva un rebalance corretto senza disturbare lo stato dei membri,
- le partizioni non vengono mai assegnate a un membro che non è più attivo.
Configurazione dei topic e compattazione
Kafka non riduce mai il numero di partizioni di un topic. Ridurlo eliminerebbe dati e romperebbe il partizionamento basato sulle chiavi, quindi kafka-topics --alter lo rifiuta con InvalidPartitionsException.
Il playground ora segue la stessa regola. Una riduzione del numero di partizioni viene rifiutata con un errore didattico invece di eliminare silenziosamente le partizioni.
Anche i topic compattati si comportano in modo più preciso:
- un tombstone sopravvive finché non scade la propria finestra di grazia
delete.retention.ms, - un record senza chiave non può essere prodotto su un topic compattato,
- con
compact,delete, la retention temporale ora recupera i record invecchiati indipendentemente dalla compattazione.
I link di condivisione preservano lo scenario completo
Il playground serializza le proprie azioni nell’URL in modo che uno scenario possa essere condiviso con un’altra persona.
Il codec ora include tutte le modifiche di configurazione, comprese le variazioni del numero di partizioni. Un link che richiede un tipo di cluster di un pacchetto di release successivo ora ripiega su una topologia supportata invece di visualizzare un cluster inutilizzabile.
Aprire un link condiviso dovrebbe quindi ricreare lo stesso scenario che era stato costruito in origine.
Correzioni all’interazione
Sono stati corretti anche diversi problemi minori del playground:
- gli identificatori delle entità non collidono più dopo che un’entità viene rimossa e ricreata,
- il piazzamento dei producer funziona correttamente su griglie di broker più ampie.
Header di sicurezza
Il sito ora invia una Content Security Policy e una Permissions Policy.
Perché iniziare dal producer?
Le garanzie di Kafka vengono spesso discusse dal punto di vista del broker: replicazione, appartenenza all’ISR, high watermark e failover del leader.
Tuttavia, la garanzia osservata da un’applicazione dipende anche dal producer.
Quale modalità di acknowledgement usa? Fa retry? L’idempotenza è abilitata? Quante richieste possono essere in flight? Cosa succede quando il suo buffer locale si riempie?
Queste impostazioni determinano se una scrittura è veloce, durevole, ordinata, duplicata, bloccata o rifiutata.
È per questo che il primo pacchetto di scenari più esteso di Kafka Simulator si concentra sul producer.
Una volta che batching, acknowledgement, retry e idempotenza sono visibili, gli scenari successivi che coprono replicazione, durabilità e transazioni hanno una base più chiara.
Apri il Kafka Simulator, seleziona il percorso del producer e prova gli scenari di guasto passo dopo passo.