El log como abstracción central
Antes de hablar de Kafka hay que entender su idea central: el log (registro append-only). Un log es una secuencia de registros ordenada en el tiempo, donde los nuevos registros solo se añaden al final. Nada se modifica ni se borra en su lugar.
Esta abstracción, aparentemente simple, es muy poderosa:
- El orden está garantizado: cada registro tiene una posición (offset).
- Varios lectores pueden leer el mismo log a ritmos distintos, cada uno recordando su propia posición.
- Al ser inmutable, el log funciona como fuente de verdad de lo que ocurrió y en qué orden.
Kafka toma esta idea y la convierte en un log distribuido, replicado y tolerante a fallos, capaz de mover millones de eventos por segundo entre sistemas.
Conceptos fundamentales
| Concepto | ¿Qué es? | Analogía |
|---|---|---|
| Topic | Categoría o canal al que se publican los eventos | Una sección del periódico |
| Partición | Subdivisión ordenada de un topic; unidad de paralelismo | Las columnas de esa sección |
| Offset | Posición única y creciente de un registro dentro de una partición | Número de página |
| Producer | Aplicación que escribe (publica) eventos en un topic | El redactor que publica |
| Consumer | Aplicación que lee eventos desde una partición | El lector suscrito |
| Consumer group | Conjunto de consumers que se reparten las particiones de un topic | Varios lectores que se reparten las secciones |
| Broker | Servidor de Kafka que almacena particiones y atiende peticiones | La imprenta/distribuidora |
Un topic se divide en una o más particiones. Cada partición es un log ordenado e inmutable. La clave: el orden solo está garantizado dentro de una partición, no entre particiones del mismo topic.
graph LR P1["Producer A"] --> TP P2["Producer B"] --> TP subgraph TP["Topic: pedidos"] direction TB PA["Partición 0<br/>[0] [1] [2] [3] ..."] PB["Partición 1<br/>[0] [1] [2] ..."] PC["Partición 2<br/>[0] [1] [2] [3] [4] ..."] end subgraph CG1["Consumer Group 1"] C1["Consumer 1"] C2["Consumer 2"] end subgraph CG2["Consumer Group 2"] C3["Consumer 3"] end PA --> C1 PB --> C1 PC --> C2 PA --> C3 PB --> C3 PC --> C3
Producers y consumers
El producer decide a qué partición va cada registro:
- Si el registro tiene clave (key), se aplica un hash de la clave para elegir la partición: la misma clave siempre cae en la misma partición, preservando el orden por entidad (por ejemplo, todos los eventos de un mismo usuario).
- Si no hay clave, se distribuye en round-robin para balancear la carga.
El consumer lee registros en orden y guarda su offset: la posición del último registro procesado. Si el consumer se cae y se reinicia, retoma la lectura desde el último offset confirmado (committed).
El offset es responsabilidad del consumer, no del broker. Esto permite que cada consumer avance a su propio ritmo y relea datos si lo necesita, algo imposible en una cola de mensajería clásica donde el mensaje desaparece al consumirse.
Consumer groups y paralelismo
Un consumer group es un conjunto de consumers que cooperan para consumir un topic:
- Cada partición es asignada a exactamente un consumer del grupo.
- Por tanto, el paralelismo máximo de un grupo es el número de particiones: si un topic tiene 3 particiones y el grupo tiene 5 consumers, 2 quedarán ociosos.
- Grupos distintos son independientes: cada uno recibe su propia copia lógica del stream completo (patrón pub/sub). Dentro de un grupo, las particiones se reparten (patrón cola de trabajo).
Si un consumer del grupo falla, Kafka rebalancea: reasigna sus particiones a los consumers restantes.
Brokers, replicación y retención
Un clúster de Kafka está formado por varios brokers. Cada partición vive en un broker que actúa como líder, y puede tener réplicas en otros brokers como seguidoras:
- Los producers escriben y los consumers leen del líder de la partición.
- Las réplicas copian el log del líder. Si el líder cae, una réplica sincronizada (in-sync replica) asume el liderazgo.
- El factor de replicación típico en producción es 3: se tolera la caída de 2 brokers sin perder datos.
A diferencia de una cola tradicional, Kafka no borra los mensajes al consumirlos. Los registros se conservan según la política de retención configurada:
- Por tiempo: por ejemplo, 7 días (
retention.ms). - Por tamaño: por ejemplo, hasta 1 GB por partición (
retention.bytes).
Esto permite releer el histórico: un nuevo consumer puede arrancar desde el offset 0 y reconstruir estado a partir de todo el log.
Ejercicio: producer y consumer sobre un log en memoria
🧪 Ejercicio
Simulando un topic de Kafka
Implementa un mini-Kafka en memoria: una clase `Topic` que mantenga un log (lista) con offsets, un método `producir(mensaje)` que añada al final devolviendo el offset, y una clase `Consumer` con su propio offset que lea los mensajes pendientes con `poll()`. Simula dos consumers leyendo el mismo topic a ritmos distintos y muestra que cada uno conserva su posición.
🔍 El offset del consumer es simplemente el índice del siguiente elemento de la lista que aún no ha leído.
class Topic:
def __init__(self, nombre):
self.nombre = nombre
self.log = [] # cada elemento es (offset, mensaje)
def producir(self, mensaje):
offset = len(self.log)
self.log.append((offset, mensaje))
return offset
class Consumer:
def __init__(self, topic, nombre):
self.topic = topic
self.nombre = nombre
self.offset = 0 # posición del siguiente registro a leer
def poll(self, max_registros=10):
pendientes = self.topic.log[self.offset:self.offset + max_registros]
self.offset += len(pendientes)
return pendientes
# Simulación
topic = Topic("pedidos")
for i in range(5):
topic.producir(f"pedido-{i}")
c1 = Consumer(topic, "facturacion")
c2 = Consumer(topic, "envios")
print(c1.nombre, "lee:", c1.poll(3)) # lee 3
print(c1.nombre, "lee:", c1.poll(3)) # lee los 2 restantes
print(c2.nombre, "lee:", c2.poll(10)) # lee los 5 desde el inicio
# Un nuevo consumer puede releer todo el historico
c3 = Consumer(topic, "auditoria")
print(c3.nombre, "lee:", c3.poll())Comprueba lo aprendido
Comprueba que lo pillaste
Un topic tiene 4 particiones y un consumer group con 6 consumers. ¿Cuántos consumers reciben datos activamente?
Cada partición se asigna a un único consumer del grupo, así que el paralelismo máximo es el número de particiones (4). Los otros 2 consumers quedan ociosos hasta un rebalanceo.
Comprueba que lo pillaste
¿Qué garantiza que todos los eventos de un mismo usuario lleguen en orden al mismo consumer?
El producer hace hash de la clave para elegir partición, así que todos los registros con la misma clave caen en la misma partición, donde el orden está garantizado y son leídos por un solo consumer.
Comprueba que lo pillaste
¿Cuándo se borran los registros de una partición de Kafka?
Kafka no borra al consumir: los registros permanecen hasta que la retención configurada (por tiempo o tamaño) los expire, lo que permite releer el histórico.
Resumen
- Kafka es un log distribuido: los eventos se añaden al final y no se modifican.
- Un topic se divide en particiones; el orden solo se garantiza dentro de cada una y el offset marca la posición.
- Los consumer groups reparten las particiones: el paralelismo máximo es el número de particiones y grupos distintos leen copias independientes.
- Los brokers almacenan las particiones con replicación líder/seguidora para tolerancia a fallos.
- La retención (tiempo o tamaño), no el consumo, determina cuándo se borran los datos, permitiendo releer el histórico.
📚 Lecturas y fuentes
| Recurso | Tipo | Por qué leerlo |
|---|---|---|
| Apache Kafka (Wikipedia en español) | Artículo | Repaso en español de arquitectura y APIs: lee la introducción y “Arquitectura”; la lista de empresas usuarias es anécdota. |
| Introduction (docs oficiales de Kafka) | Docs | La explicación canónica de topics, particiones, producers y consumers en una sola página corta. Léela entera. (en inglés) |
| Design (docs oficiales de Kafka) | Docs | El “por qué” del rendimiento: lee “Persistence”, “Efficiency” y “Replication”; el resto de la sección es afinado avanzado. (en inglés) |
| Books and Papers de Apache Kafka | Paper | Índice oficial que enlaza “The Log” de Jay Kreps (empieza por ahí: la primera mitad explica el log como abstracción) y el paper original de 2011. (en inglés) |