¿Qué ocurre después de que un productor de Kafka llama a send()?
Suena como una pregunta sencilla. El productor crea un registro, lo envía a Kafka y recibe un acknowledgement.
Sin embargo, entre esos pasos pueden ocurrir bastantes cosas.
El productor puede esperar más registros y crear un batch. Debe elegir una partición. Puede comprimir el batch. Si el broker es lento, el productor puede quedarse sin espacio en el búfer. Si una petición falla, puede reintentarla. Si el reintento tiene éxito después de que la primera petición ya se hubiera escrito, Kafka puede recibir el mismo batch dos veces.
Kafka Simulator v1.1 se centra en esta semántica del productor.
Esta versión añade 10 nuevos escenarios, con lo que el temario completo llega a 23 de 125 escenarios.
¿Qué incluye la v1.1?
Ocho de los diez escenarios se centran en el productor. Los dos restantes amplían el itinerario de anatomía del log: la lectura hasta el high watermark y el log start offset.
Los escenarios del productor cubren cinco áreas:
- el batching con
linger.msybatch.size, - los particionadores del productor,
- la compresión,
- el backpressure con
buffer.memoryymax.block.ms, - los reintentos y la idempotencia.
Veamos cada una de ellas.
Batching con linger.ms y batch.size
Un productor de Kafka no envía necesariamente cada registro en una petición separada.
Los registros asignados a la misma partición pueden agruparse en un batch. El productor envía el batch cuando se llena o cuando expira el tiempo de espera configurado.
Aquí es donde dos ajustes del productor se vuelven importantes:
batch.sizelimita el tamaño de un batch,linger.mspermite al productor esperar registros adicionales antes de enviarlo.
El compromiso es entre latencia y throughput.
Un batch más grande suele significar menos peticiones y mejor compresión. Sin embargo, esperar a que el batch se llene puede aumentar la latencia de un registro individual.
Los nuevos escenarios hacen visible este comportamiento. Puedes ver cuándo los registros entran en un batch, qué provoca el envío del batch y cómo el cambio de configuración afecta a las peticiones que produce el cliente.
Particionadores del productor
Antes de que el productor pueda crear un batch específico de una partición, debe seleccionar una partición.
Para los registros con clave, la clave se usa para elegir la partición. Por eso, los registros con la misma clave normalmente se asignan a la misma partición.
Para los registros sin clave, el comportamiento es distinto. El productor puede agrupar los registros usando su estrategia de particionado en lugar de calcular la partición a partir de una clave.
El simulador muestra ambos casos paso a paso:
- el productor recibe el registro,
- el particionador selecciona una partición,
- el registro se añade al batch correspondiente,
- el batch se envía al líder de esa partición.
Esto facilita ver por qué el particionado afecta tanto al orden como al batching.
La compresión ocurre por batch
La compresión del productor de Kafka se aplica a los batches de registros, no a cada registro de forma independiente.
Esta distinción importa.
La compresión suele ser más eficaz cuando un batch contiene varios registros similares. Un batch que solo contiene un registro pequeño puede beneficiarse muy poco de la compresión. Un batch más grande con datos repetitivos puede comprimirse de forma mucho más eficiente.
Como resultado, la compresión está conectada con la configuración del batching. Ajustes como batch.size y linger.ms pueden influir no solo en la frecuencia de las peticiones, sino también en la forma y la compresibilidad de los batches resultantes.
Los nuevos escenarios muestran los tamaños del batch sin comprimir y comprimido, haciendo explícita esta relación.
Backpressure con buffer.memory y max.block.ms
¿Qué ocurre cuando una aplicación produce registros más rápido de lo que Kafka puede aceptarlos?
El productor no puede mantener en memoria un número ilimitado de registros sin enviar. Usa un búfer acotado, configurado mediante buffer.memory.
Cuando el búfer no tiene espacio disponible, una llamada a send() puede bloquearse mientras espera a que se libere memoria.
No esperará para siempre. El tiempo máximo de bloqueo lo controla max.block.ms.
Esto nos da dos resultados posibles:
- se envían y confirman suficientes datos, liberando espacio del búfer,
- no se libera espacio antes del timeout y el envío falla.
El simulador rastrea el búfer del productor directamente. Puedes ver cómo se reserva memoria cuando se añaden registros y cómo se libera a medida que se completan las peticiones.
Esto es especialmente útil cuando se combina con un broker lento o una cuota de throughput. En lugar de tratar el backpressure del productor como un problema de configuración abstracto, puedes observar cómo se llena el búfer y ver el envío exacto que se bloquea.
Reintentos y el problema de los duplicados
Supongamos que el productor envía un batch a un broker.
El broker escribe el batch correctamente, pero el acknowledgement se pierde antes de llegar al productor.
Desde la perspectiva del productor, la petición falló. No puede saber si el broker escribió los datos. Por lo tanto, reintentar es razonable.
Sin embargo, si la primera petición se escribió correctamente, el reintento puede añadir el mismo batch de nuevo.
Este es el problema de los duplicados causado por fallos ambiguos.
El escenario de reintentos muestra ambas peticiones:
- la petición original llega al broker,
- el acknowledgement se pierde,
- el productor reintenta el batch,
- el broker recibe los mismos registros por segunda vez.
Sin idempotencia, ambas copias pueden añadirse.
Semántica del productor idempotente
Un productor idempotente permite a Kafka reconocer los batches reintentados.
Kafka asigna una identidad al productor y rastrea los números de secuencia de forma independiente para cada partición. Cuando un batch se reintenta con un número de secuencia que el broker ya ha procesado, el broker puede confirmarlo sin volver a añadir los registros.
La distinción importante es entre un reintento y un duplicado genuino.
Dos registros pueden contener exactamente la misma clave y el mismo valor y aun así ser dos registros distintos enviados intencionadamente por la aplicación. Kafka no debe eliminar tales registros.
La idempotencia deduplica los reintentos del protocolo del productor. No deduplica los datos de la aplicación en función de su contenido.
Los nuevos escenarios muestran:
- los IDs de productor,
- los números de secuencia de partición,
- los reintentos idempotentes exitosos,
- los registros duplicados genuinos,
- la detección de secuencias fuera de orden.
¿Por qué importa max.in.flight.requests.per.connection?
Un productor puede tener varias peticiones esperando respuesta al mismo tiempo.
Esto mejora el throughput, pero también crea un posible problema de orden.
Supongamos que el productor envía el batch A y luego el batch B. El batch A falla temporalmente, mientras que el batch B tiene éxito. Si A se reintenta después de que B ya se haya escrito, los registros pueden añadirse en un orden diferente.
La idempotencia usa los números de secuencia para detectar un orden inválido.
El simulador muestra esto explícitamente. Puedes ver el número de secuencia esperado por el broker, el número que lleva el batch entrante y el punto en el que el broker rechaza una petición fuera de orden.
Inspeccionar el estado interno del productor
Los nuevos escenarios se apoyan en gran medida en el inspector del simulador, que expone el estado interno de productores y particiones, incluyendo:
- los contadores de reintentos,
- los números de secuencia del productor,
- los números de secuencia del broker,
- la reserva del búfer.
Normalmente, estos valores están ocultos tras las métricas del productor, los logs y el comportamiento del protocolo. En el simulador, se pueden inspeccionar directamente mientras avanzas o retrocedes en un escenario.
Correcciones en el modelo del productor
Añadir los nuevos escenarios también requirió hacer más preciso el modelo del productor existente.
Los reintentos se cuentan por batch
Kafka reintenta peticiones de produce que contienen batches de registros.
Por lo tanto, una petición fallida debería consumir un reintento por el batch completo, no un reintento por cada registro que contiene.
El simulador ahora sigue este modelo. Cada registro de un batch fallido se envía de nuevo, mientras que el contador de reintentos avanza una vez.
La memoria del búfer se libera con precisión
La memoria del búfer del productor ahora se libera a medida que llegan los acknowledgements.
Esto corrige casos en los que el simulador podía reportar backpressure aunque los registros confirmados ya deberían haber liberado su memoria.
Lo mismo se aplica a los timeouts de max.block.ms. Cuando un envío bloqueado agota su tiempo, su reserva de búfer se limpia.
acks=all se rastrea por partición
La durabilidad de Kafka se define de forma independiente para cada partición.
Por lo tanto, un productor que escribe en varias particiones debe esperar a que la partición correcta satisfaga sus requisitos de acknowledgement. El progreso en otra partición no debe confirmar una petición no relacionada.
El simulador ahora rastrea este estado por separado para cada partición y topic.
El reinicio del productor limpia el estado de idempotencia
El broker almacena el estado de deduplicación asociado a un productor idempotente.
Cuando un productor simulado se elimina o se reinicia, este estado también debe eliminarse. De lo contrario, un productor recién creado podría tener sus registros clasificados incorrectamente como reintentos antiguos.
El comportamiento de reinicio ahora limpia el estado correspondiente del lado del broker.
Retroceder restaura el estado de reintentos
Los escenarios se pueden avanzar y retroceder paso a paso.
Retroceder ahora reconstruye los contadores de reintentos usados durante cada ronda de simulación. Por lo tanto, el estado visible del productor es coherente independientemente de si el escenario avanza o retrocede.
Mejoras en el terreno de juego
Esta versión también contiene varias correcciones fuera del modelo del productor.
Rebalanceo de grupos de consumidores
Los grupos de consumidores del terreno de juego recibieron varias correcciones de exactitud:
- el sticky assignor ahora minimiza el movimiento de particiones en lugar de reasignarlas desde cero,
- un miembro estático que permanece ausente más tiempo que
session.timeout.mses expulsado como cualquier otro miembro, - cambiar el número de particiones de un topic ahora desencadena un rebalanceo adecuado sin perturbar el estado de los miembros,
- las particiones nunca se asignan a un miembro que ya no está vivo.
Configuración de topics y compactación
Kafka nunca reduce el número de particiones de un topic. Reducirlo eliminaría datos y rompería el particionado basado en claves, por lo que kafka-topics --alter lo rechaza con InvalidPartitionsException.
El terreno de juego ahora sigue la misma regla. Una disminución del número de particiones se rechaza con un error didáctico en lugar de eliminar particiones silenciosamente.
Los topics compactados también se comportan con más precisión:
- un tombstone sobrevive hasta que expira su propia ventana de gracia de
delete.retention.ms, - no se puede producir un registro sin clave en un topic compactado,
- con
compact,delete, la retención por tiempo ahora recupera los registros envejecidos con independencia de la compactación.
Los enlaces para compartir conservan el escenario completo
El terreno de juego serializa sus acciones en la URL para que un escenario pueda compartirse con otra persona.
El codec ahora incluye todos los cambios de configuración, incluidas las actualizaciones del número de particiones. Un enlace que solicita un tipo de clúster de un paquete de versiones posterior ahora recurre a una topología compatible en lugar de renderizar un clúster inutilizable.
Por lo tanto, abrir un enlace compartido debería recrear el mismo escenario que se construyó originalmente.
Correcciones de interacción
También se corrigieron varios problemas menores del terreno de juego:
- los identificadores de entidad ya no colisionan después de eliminar y recrear una entidad,
- la colocación de productores funciona correctamente en cuadrículas de brokers más anchas.
Cabeceras de seguridad
El sitio ahora envía una Content Security Policy y una Permissions Policy.
¿Por qué empezar con el productor?
Las garantías de Kafka suelen discutirse desde la perspectiva del broker: replicación, pertenencia al ISR, high watermarks y failover de líder.
Sin embargo, la garantía que observa una aplicación también depende del productor.
¿Qué modo de acknowledgement usa? ¿Reintenta? ¿Está activada la idempotencia? ¿Cuántas peticiones pueden estar en vuelo? ¿Qué ocurre cuando su búfer local se llena?
Estos ajustes determinan si una escritura es rápida, duradera, ordenada, duplicada, bloqueada o rechazada.
Por eso el primer gran paquete de escenarios de Kafka Simulator se centra en el productor.
Una vez que el batching, los acknowledgements, los reintentos y la idempotencia son visibles, los escenarios posteriores sobre replicación, durabilidad y transacciones tienen una base más clara.
Abre el Kafka Simulator, selecciona el itinerario del productor y prueba los escenarios de fallo paso a paso.