¿Cómo funciona el orden de los mensajes en un sistema particionado como Kafka, y qué rompe esa garantía de orden?
Kafka solo promete orden dentro de una sola partición, así que la clave de particionamiento que elijas es el mecanismo real que decide si los eventos relacionados mantienen su secuencia.
APRENDE HACIENDOPon a prueba tu intuición3 pasos
¿Cómo funciona el orden de los mensajes en un sistema particionado como Kafka, y qué rompe esa garantía de orden?
Usa los ejemplos y la explicación de la lección para comprobar tu idea.
Reflexión personal. No se envía a ningún servicio de IA.#Kafka solo te hace una promesa sobre el orden — léela con cuidado
La garantía de orden de Kafka es precisa y fácil de enunciar mal: los mensajes dentro de una sola partición se entregan a los consumidores en el orden exacto en que se escribieron. No se promete nada más amplio que eso. Entre particiones —incluso dentro del mismo topic— no existe ninguna relación de orden; dos mensajes publicados con un milisegundo de diferencia pueden caer en particiones distintas y consumirse en cualquier orden, o incluso al mismo tiempo por dos instancias de consumidor distintas.
Eso significa que el orden es en realidad un problema de particionamiento, no un problema de Kafka: tu trabajo es enrutar cada mensaje que tenga una dependencia de orden respecto a otro mensaje hacia la misma partición, y dejar que la garantía por partición de Kafka haga el resto.
#Particionar por clave: el mecanismo estándar
Cuando produces un registro con una clave, el particionador por defecto de Kafka hashea la clave y la mapea de forma determinista a una de las particiones del topic (partition = hash(key) % numPartitions, a grandes rasgos — la implementación real usa un hash tipo sticky/murmur2, pero el efecto es el mismo: misma clave, misma partición, siempre, mientras el número de particiones no cambie).
Properties props = new Properties();props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true); // acks=all, reintentos en orden, sin escrituras duplicadasKafkaProducer<String, String> producer = new KafkaProducer<>(props);String orderId = "ORD-8891"; // la clave de particionamientoproducer.send(new ProducerRecord<>("order-events", orderId,"{\"type\":\"OrderCreated\",\"orderId\":\"ORD-8891\"}"));producer.send(new ProducerRecord<>("order-events", orderId,"{\"type\":\"OrderPaid\",\"orderId\":\"ORD-8891\"}"));producer.send(new ProducerRecord<>("order-events", orderId,"{\"type\":\"OrderShipped\",\"orderId\":\"ORD-8891\"}"));producer.flush();producer.close();
Los tres eventos usan orderId como clave, así que los tres se hashean a la misma partición. Un consumidor que lea esa partición los ve exactamente en el orden en que se enviaron —OrderCreated, luego OrderPaid, luego OrderShipped— nunca en otro orden, sin importar cuántos eventos de otros pedidos estén intercalados en otras particiones. Ese es todo el truco: elige una clave que coincida con tu frontera de orden (por pedido, por usuario, por cuenta — lo que sea que deba mantenerse en secuencia), y deja que todos los eventos de esa entidad compartan partición.
#Qué rompe realmente la garantía de orden
Cuatro cosas, cada una por una razón distinta:
-
Sin clave, o una clave que no coincide con tu unidad de orden. Con
key=null, el particionador sticky de Kafka reparte los registros entre particiones para balancear la carga — no hay ninguna relación de orden entre ellos. Usar como clave el campo equivocado (digamos,regioncuando necesitabas orden pororderId) igual dispersa los eventos de un mismo pedido entre las particiones a las que la región resulte hasheando. -
Cambiar el número de particiones de un topic existente.
hash(key) % numPartitionsdepende denumPartitions. Añade particiones a un topic en vivo y el mapeo para las claves existentes cambia de ahí en adelante — los mensajes ya escritos no se mueven, pero los mensajes nuevos de la misma clave pueden caer en una partición distinta a la de los antiguos, rompiendo la continuidad entre eventos viejos y nuevos de esa clave. -
Reintentos del productor sin idempotencia, cuando
max.in.flight.requests.per.connection > 1. Si un productor tiene varios lotes en vuelo y un lote anterior falla y se reintenta mientras un lote posterior tiene éxito, el lote reintentado puede quedar después del posterior — reordenando registros dentro de la misma partición. Configurarenable.idempotence=true(el valor por defecto en los clientes modernos de Kafka) cierra este hueco: el broker rastrea un número de secuencia por partición y por productor, y rechaza/reordena los reintentos correctamente, garantizando entrega en orden incluso bajo reintentos. -
Reordenamiento del lado del consumidor por procesamiento paralelo. Aunque Kafka le entregue a tu consumidor los registros en orden perfecto, repartirlos entre un pool de hilos para procesarlos en paralelo (para aumentar el throughput) puede permitir que un mensaje posterior termine y confirme antes que uno anterior. Si el orden importa aguas abajo, o procesas los registros de una partición en un solo hilo, o particionas tu cola interna de trabajo por la misma clave para que el orden por clave sobreviva al reparto.