⏱️ Lectura: 15 min
En 2010, LinkedIn tenía docenas de sistemas internos que necesitaban compartir datos entre sí en tiempo real, y ninguna cola de mensajes disponible aguantaba ese volumen sin caerse: así nació Apache Kafka. Hoy ese mismo log distribuido corre detrás de Netflix, Uber y Spotify, y se convirtió en el estándar de facto para mover eventos entre microservicios sin perderlos.
📑 En este artículo
- TL;DR
- Qué es Apache Kafka y por qué importa
- Cómo funciona por dentro
- Ejemplos prácticos: tu primer productor y consumidor
- Cómo empezar: levantar Kafka en minutos
- Casos de uso reales
- Errores comunes y buenas prácticas
- Comparativa con alternativas
- Profundizando: log compaction y exactly-once
- Preguntas frecuentes
- Referencias
TL;DR
- Vas a entender qué es un topic, una partición y un offset, y por qué reemplazan a una cola tradicional.
- Vas a poder escribir un productor y un consumidor de Kafka en Node.js en menos de 20 líneas cada uno.
- Vas a saber levantar un broker de Kafka local con Docker en modo KRaft, sin ZooKeeper.
- Vas a entender cómo un consumer group reparte particiones entre procesos sin duplicar trabajo.
- Vas a saber comprobar el lag de un consumidor con un solo comando de la CLI de Kafka.
- Vas a poder decidir cuándo conviene Kafka frente a RabbitMQ, Amazon SQS o Apache Pulsar.
- Vas a conocer los errores más comunes al diseñar particiones y cómo evitarlos desde el diseño.
Qué es Apache Kafka y por qué importa
Apache Kafka no es una cola de mensajes tradicional. Una cola clásica, como las que maneja RabbitMQ, borra un mensaje en cuanto un consumidor lo confirma. Kafka hace algo distinto: guarda cada mensaje en un log ordenado e inmutable, y lo conserva durante un período configurable aunque ya haya sido leído.
Esa diferencia parece pequeña, pero cambia todo lo que podés construir encima. Si diez sistemas distintos necesitan leer el mismo evento (un pedido nuevo, un clic, una métrica), cada uno puede leer el log a su propio ritmo, sin que el sistema productor sepa ni le importe quién está leyendo del otro lado.
Kafka nació en LinkedIn en 2010, de la mano de Jay Kreps, Neha Narkhede y Jun Rao, y se abrió como proyecto open source bajo la Apache Software Foundation en 2011. La motivación original era simple: LinkedIn tenía un feed de actividad y métricas operativas que ningún sistema de mensajería de la época podía mover con el volumen y la latencia que necesitaban.
Cómo funciona por dentro
Todo en Kafka gira alrededor de cuatro conceptos: topics, particiones, offsets y brokers. Un topic es una categoría de eventos, por ejemplo ordenes-nuevas o eventos-usuario. Cada topic se divide en una o más particiones, y cada partición es, literalmente, un archivo de log al que solo se le puede agregar contenido al final.
Cada mensaje dentro de una partición recibe un offset: un número entero secuencial que indica su posición exacta. Un consumidor no le pide a Kafka ‘el próximo mensaje sin leer’ como en una cola; le pide explícitamente ‘dame lo que hay desde el offset 4.812’, lo que le permite retroceder, repetir o adelantar la lectura a voluntad.
Los brokers son los servidores que almacenan las particiones. Un clúster de Kafka en producción tiene varios brokers, y cada partición tiene un broker líder que atiende las escrituras y N réplicas en otros brokers que copian ese log por si el líder se cae. Ese conjunto de réplicas al día se llama ISR, in-sync replicas.
Del lado del productor, cada mensaje se envía con una clave opcional. Kafka aplica un hash de esa clave para decidir a qué partición va, así que todos los eventos de una misma clave (por ejemplo, el mismo usuario_id) siempre terminan en la misma partición y se leen en el orden en que se escribieron.
Del lado del consumidor, los procesos se agrupan en consumer groups. Kafka reparte las particiones de un topic entre los miembros vivos de un grupo, de forma que cada partición la procesa un solo consumidor del grupo a la vez. Esto es lo que permite escalar el procesamiento agregando más instancias, hasta el límite del número de particiones.
flowchart TD
P1["Productor A"] --> B["Broker Kafka"]
P2["Productor B"] --> B
B --> Part0["Particion 0"]
B --> Part1["Particion 1"]
B --> Part2["Particion 2"]
Part0 --> C1["Consumidor 1"]
Part1 --> C2["Consumidor 2"]
Part2 --> C2
subgraph Grupo["Consumer group: procesador-ordenes"]
C1
C2
end
Hasta la versión 3.2, Kafka dependía de ZooKeeper para coordinar metadatos del clúster: qué broker es líder de qué partición, la lista de topics, etc. Desde que KRaft llegó a producción en Kafka 3.3, esa coordinación la maneja el propio Kafka con un protocolo de consenso tipo Raft, sin depender de un sistema externo.
Ejemplos prácticos: tu primer productor y consumidor
El ejemplo más simple posible es un productor que manda un solo mensaje. Usamos kafkajs, la librería de Node.js más usada para hablar con Kafka:
const { Kafka } = require('kafkajs')
const kafka = new Kafka({
clientId: 'app-eventos',
brokers: ['localhost:9092']
})
const productor = kafka.producer()
async function enviarEvento() {
await productor.connect()
await productor.send({
topic: 'eventos-usuario',
messages: [
{ key: 'usuario-42', value: JSON.stringify({ accion: 'login' }) }
]
})
await productor.disconnect()
}
enviarEvento()
Este script se conecta al broker local, envía un mensaje al topic eventos-usuario con la clave usuario-42 para que siempre caiga en la misma partición, y cierra la conexión. El resultado esperado: el mensaje queda persistido en el log y disponible para cualquier consumidor que se suscriba, incluso si todavía no existía cuando se envió.
Un caso más realista necesita control fino sobre cuándo se confirma un mensaje como procesado. Este consumidor forma parte de un consumer group y solo avanza su offset después de guardar la orden en base de datos:
const { Kafka } = require('kafkajs')
const kafka = new Kafka({
clientId: 'procesador-ordenes',
brokers: ['localhost:9092']
})
const consumidor = kafka.consumer({ groupId: 'procesador-ordenes' })
async function procesarOrdenes() {
await consumidor.connect()
await consumidor.subscribe({ topic: 'ordenes-nuevas', fromBeginning: false })
await consumidor.run({
autoCommit: false,
eachMessage: async ({ topic, partition, message }) => {
const orden = JSON.parse(message.value.toString())
await guardarEnBaseDeDatos(orden)
await consumidor.commitOffsets([
{ topic, partition, offset: (Number(message.offset) + 1).toString() }
])
}
})
}
procesarOrdenes()
Acá autoCommit está apagado a propósito. Si el commit del offset se hiciera automáticamente antes de guardar en base de datos, un crash a mitad de proceso perdería la orden para siempre: con este orden, guardar primero y confirmar offset después, si el proceso se cae, Kafka vuelve a entregar ese mensaje al reiniciar.
sequenceDiagram
participant P as Productor
participant L as Broker lider
participant R as Broker replica
participant C as Consumidor
P->>L: envia mensaje con acks=all
L->>R: replica el mensaje
R-->>L: confirma escritura
L-->>P: confirma commit
C->>L: poll pide siguiente offset
L-->>C: devuelve el mensaje
Cómo empezar: levantar Kafka en minutos
Para probar todo lo anterior no hace falta un clúster completo. La imagen oficial de Apache Kafka incluye KRaft, así que un solo contenedor alcanza para desarrollo local:
docker run -d --name kafka-local -p 9092:9092 \
-e KAFKA_NODE_ID=1 \
-e KAFKA_PROCESS_ROLES=broker,controller \
-e KAFKA_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093 \
-e KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092 \
-e KAFKA_CONTROLLER_QUORUM_VOTERS=1@localhost:9093 \
-e KAFKA_CONTROLLER_LISTENER_NAMES=CONTROLLER \
apache/kafka:3.7.0
Con el broker arriba, creá un topic con tres particiones para poder escalar hasta tres consumidores en paralelo:
docker exec kafka-local /opt/kafka/bin/kafka-topics.sh \
--create --topic eventos-usuario \
--partitions 3 --replication-factor 1 \
--bootstrap-server localhost:9092
Instalá la librería del cliente y corré el productor de ejemplo:
npm install kafkajs
node productor.js
Para confirmar que todo funciona sin escribir código, Kafka trae utilidades de consola que leen directamente del topic:
docker exec kafka-local /opt/kafka/bin/kafka-console-consumer.sh \
--bootstrap-server localhost:9092 \
--topic eventos-usuario --from-beginning
💡 Tip: para comprobar si un consumer group va al día o se está atrasando, corré kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group procesador-ordenes. La columna LAG muestra cuántos mensajes le faltan procesar a ese grupo en cada partición.
Casos de uso reales
El caso original de LinkedIn, mover eventos de actividad y métricas entre sistemas internos, sigue siendo el patrón más común: desacoplar quien genera un evento de quien lo consume, para que ambos lados escalen y fallen de forma independiente.
Otro uso extendido es el Change Data Capture (CDC): herramientas como Debezium leen el log de transacciones de una base de datos y publican cada cambio como un evento en un topic de Kafka, para que otros sistemas (cachés, motores de búsqueda, data warehouses) se mantengan sincronizados sin consultar la base original.
También es la columna vertebral de pipelines de analítica en tiempo real: en lugar de esperar un batch nocturno, cada clic o transacción se procesa apenas ocurre, con herramientas como Kafka Streams o ksqlDB corriendo agregaciones directamente sobre los topics.
Y en arquitecturas de microservicios, Kafka reemplaza las llamadas HTTP síncronas entre servicios por eventos asíncronos: un servicio de pedidos publica orden-creada y los servicios de facturación, inventario y notificaciones reaccionan cada uno a su ritmo, sin que el servicio de pedidos necesite saber que existen.
Errores comunes y buenas prácticas
El error más caro es subestimar cuántas particiones necesita un topic. El número de particiones fija el techo de paralelismo: si un topic tiene tres particiones, jamás vas a poder tener más de tres consumidores activos y útiles en un mismo consumer group, sin importar cuántos procesos levantes.
Aumentar particiones después no es gratis: Kafka puede agregar particiones nuevas a un topic existente, pero no reordena los mensajes ya escritos, así que eventos con la misma clave que antes caían siempre en la misma partición pueden empezar a repartirse entre varias, rompiendo el orden esperado.
Otro error frecuente es ignorar la clave de partición. Sin una clave explícita, kafkajs y la mayoría de los clientes reparten los mensajes de forma round robin, lo que está bien para eventos independientes, pero rompe cualquier lógica que dependa del orden entre eventos relacionados, como los cambios de estado de una misma orden.
También es común no monitorear el lag de los consumer groups hasta que ya es un problema en producción. Un lag que crece de forma sostenida significa que los consumidores procesan más lento de lo que los productores escriben, y la solución, agregar particiones y consumidores u optimizar el procesamiento, toma tiempo: hay que detectarlo temprano.
Por último, tratar Kafka como una base de datos para hacer consultas ad hoc es un error de diseño común. Kafka está pensado para leer secuencialmente desde un offset, no para buscar por campos arbitrarios: para eso siguen haciendo falta bases de datos o motores de búsqueda alimentados desde los mismos topics.
⚠️ Ojo: configuraracks=allen el productor ymin.insync.replicas=2en el topic es lo que evita perder mensajes cuando el broker líder de una partición se cae a mitad de una escritura. Sin esa combinación, un fallo del líder puede perder los últimos mensajes reconocidos.
Comparativa con alternativas
| Opción | Cuándo usarla | Ventaja | Limitación |
|---|---|---|---|
| Apache Kafka | Alto volumen de eventos, varios consumidores independientes, necesitás releer historial | Retiene el log y permite volver a leer desde cualquier offset | Operar particiones, brokers y replicación tiene una curva de aprendizaje alta |
| RabbitMQ | Colas de tareas clásicas con enrutamiento complejo | Exchanges flexibles (topic, fanout, direct) y latencia muy baja | El mensaje se borra al confirmarse: no hay replay nativo del historial |
| Amazon SQS | Equipos sin infraestructura propia que necesitan una cola simple | Cero mantenimiento, escala automática, totalmente administrado | Sin orden estricto en colas estándar ni replay de mensajes ya consumidos |
| Apache Pulsar | Multi-tenencia y separación entre cómputo y almacenamiento | Separa brokers de bookies de almacenamiento, cada uno escala aparte | Ecosistema y comunidad más chicos que los de Kafka |
Profundizando: log compaction y exactly-once
Un topic normal borra mensajes según una política de retención por tiempo o tamaño, por defecto siete días. Pero Kafka soporta un modo distinto, log compaction, donde en vez de borrar por antigüedad, conserva solo el último valor escrito para cada clave.
Esto convierte un topic en algo parecido a una tabla de estado: si publicás usuario-42 → {plan: free} y después usuario-42 → {plan: pro}, un topic compactado eventualmente se queda solo con el segundo valor. Es el mecanismo detrás de los changelog topics de Kafka Streams, que reconstruyen el estado de una aplicación leyendo el topic de punta a punta.
El otro tema avanzado ineludible es exactly-once semantics. Por defecto, si un productor reintenta un envío que en realidad sí llegó, por ejemplo por un timeout de red, el mensaje puede duplicarse. Activando enable.idempotence=true en el productor, Kafka asigna un número de secuencia a cada mensaje y descarta los duplicados del lado del broker.
Para operaciones que escriben en varias particiones o topics como una sola unidad (leer de un topic, transformar, escribir en otro), Kafka expone transacciones con un transactional.id, que garantizan que todas esas escrituras se confirman juntas o ninguna se confirma, incluso si el proceso productor se cae a mitad de camino.
💭 Clave: log compaction y retención por tiempo no son excluyentes: un mismo topic puede combinar ambas políticas, borrando por antigüedad mientras compacta por clave, según la configuración de cleanup.policy.
flowchart LR
Leader["Particion 0: lider"] --> Follower1["Replica en broker 2"]
Leader --> Follower2["Replica en broker 3"]
subgraph ISR["In-sync replicas"]
Leader
Follower1
Follower2
end
📖 Resumen en Telegram: Ver resumen
Tu próximo paso: levantá el contenedor de Kafka de este artículo, creá un topic con tres particiones y corré los dos scripts de Node.js en paralelo para ver cómo se reparte el consumo entre particiones.
Preguntas frecuentes
¿Kafka es lo mismo que una cola de mensajes?
No exactamente. Una cola borra el mensaje cuando se confirma su lectura; Kafka lo conserva en un log durante un período configurable, lo que permite releerlo, tener varios consumer groups independientes sobre el mismo topic y reprocesar desde cualquier offset.
¿Todavía necesito ZooKeeper para correr Kafka?
No. Desde que KRaft llegó a producción en la versión 3.3, Kafka puede manejar sus propios metadatos de clúster sin depender de ZooKeeper, lo que simplifica la instalación y la operación.
¿Cuántas particiones debería tener un topic?
Depende del paralelismo que necesites: el número de particiones es el techo de consumidores útiles simultáneos en un mismo consumer group. Conviene empezar con más particiones de las que creés necesitar hoy, porque reducirlas después no es posible sin recrear el topic.
¿Kafka garantiza el orden de todos los mensajes de un topic?
Solo dentro de una misma partición. Si necesitás orden entre un grupo de eventos, por ejemplo todos los cambios de una misma orden, tenés que usar la misma clave de partición para todos ellos.
¿Puedo usar Kafka como base de datos?
No para consultas arbitrarias. Kafka lee de forma secuencial desde un offset; para buscar por campos o hacer joins ad hoc seguís necesitando una base de datos o un motor de búsqueda alimentado desde los mismos topics.
¿Qué pasa si un broker líder se cae en medio de una escritura?
Si el productor configuró acks=all y el topic tiene min.insync.replicas mayor a uno, otra réplica sincronizada asume como líder sin perder el mensaje. Sin esa configuración, el mensaje reconocido antes de la caída puede perderse.
Referencias
- Documentación oficial de Apache Kafka: referencia completa de configuración, protocolo y APIs.
- Repositorio de Apache Kafka en GitHub: código fuente del proyecto y las imágenes oficiales de Docker.
- Apache Kafka en Wikipedia: historia del proyecto desde su origen en LinkedIn.
- Documentación de kafkajs: cliente de Node.js usado en los ejemplos de código de este artículo.
📱 ¿Te gusta este contenido? Únete a nuestro canal de Telegram @programacion donde publicamos a diario lo más relevante de tecnología, IA y desarrollo. Resúmenes rápidos, contenido fresco todos los días.
Imagen destacada: Foto de Emile Perron en Unsplash
0 Comentarios