Apache Kafka para Ingenieria de Datos: Particiones, Streaming y Pipelines en Tiempo Real
Guia completa de Apache Kafka para ingenieria de datos. Arquitectura KRaft, estrategias de particionamiento, consumer groups, CDC con Debezium, exactly-once semantics y Share Groups con ejemplos en Python.

Apache Kafka se posiciona como la tecnologia de referencia para construir pipelines de datos en tiempo real. En el ecosistema actual de ingenieria de datos, donde la latencia de horas ya no resulta aceptable para la mayoria de los casos de uso, Kafka ofrece la capacidad de procesar millones de eventos por segundo con garantias de durabilidad, orden y tolerancia a fallos. Desde la ingesta de clickstream hasta la sincronizacion de bases de datos mediante Change Data Capture, los equipos de data engineering dependen de Kafka como la columna vertebral de sus arquitecturas de streaming. Esta guia recorre los fundamentos operativos de Kafka 4.x, los patrones de implementacion mas relevantes y las preguntas que aparecen con mayor frecuencia en entrevistas tecnicas.
Arquitectura KRaft de Kafka 4.x, estrategias de particionamiento, consumer groups con manejo manual de offsets, Change Data Capture con Debezium, semanticas exactly-once, Share Groups de Kafka 4.2 y preguntas de entrevista para data engineers. Todos los ejemplos de codigo utilizan Python con la libreria confluent-kafka.
Kafka 4.x y la arquitectura KRaft: el fin de ZooKeeper
Kafka 4.0, lanzado en marzo de 2025, marco un punto de inflexion al eliminar por completo la dependencia de ZooKeeper. Durante mas de una decada, ZooKeeper fue el componente encargado de gestionar los metadatos del cluster: eleccion de lideres, configuracion de topics y registro de brokers. Sin embargo, esta pieza adicional traia consigo complejidad operativa significativa, un sistema mas que monitorear y una fuente recurrente de incidentes en ambientes productivos.
El modo KRaft (Kafka Raft) integra la gestion de metadatos directamente en los brokers de Kafka, empleando un protocolo de consenso basado en Raft. El resultado practico para los equipos de ingenieria de datos es contundente: despliegues mas sencillos, recuperacion ante fallos mas rapida y la capacidad de escalar a millones de particiones dentro de un solo cluster.
Levantar un cluster de desarrollo local con Kafka 4.x requiere unicamente un archivo Docker Compose. No hace falta configurar ni mantener un ensemble de ZooKeeper aparte.
# docker-compose.yml — Kafka 4.x KRaft cluster (no ZooKeeper)
services:
kafka-1:
image: apache/kafka:4.2.0
environment:
KAFKA_NODE_ID: 1
KAFKA_PROCESS_ROLES: broker,controller
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka-1:9093,2@kafka-2:9093,3@kafka-3:9093
KAFKA_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
KAFKA_LOG_DIRS: /var/lib/kafka/data
KAFKA_NUM_PARTITIONS: 6
KAFKA_DEFAULT_REPLICATION_FACTOR: 3
KAFKA_MIN_INSYNC_REPLICAS: 2
ports:
- "9092:9092"La variable KAFKA_PROCESS_ROLES: broker,controller configura cada nodo con ambos roles. En un ambiente productivo, lo habitual es separar los nodos controller de los nodos broker para aislar la carga de gestion de metadatos del procesamiento de mensajes. La configuracion KAFKA_CONTROLLER_QUORUM_VOTERS define los participantes del quorum Raft, mientras que un factor de replicacion de 3 con MIN_INSYNC_REPLICAS de 2 asegura que cualquier mensaje confirmado con acks=all sobrevive la caida de un broker sin perdida de datos.
La combinacion de replication_factor: 3 con min.insync.replicas: 2 representa el estandar para pipelines de datos en produccion. Esta configuracion tolera la perdida de un broker sin interrumpir el servicio ni comprometer los datos ya confirmados. Ajustar estos valores por debajo de lo recomendado pone en riesgo la durabilidad de los mensajes ante fallos de hardware.
Estrategias de particionamiento para pipelines de datos
Las particiones son el mecanismo central de paralelismo en Kafka. Cada topic se divide en una o mas particiones, y cada particion funciona como un log ordenado e inmutable. El orden de los mensajes se garantiza exclusivamente dentro de una misma particion, pero no existe ninguna garantia de orden entre particiones distintas.
La seleccion de la clave de particion impacta directamente tres aspectos criticos del pipeline: el orden de procesamiento, la distribucion de carga entre consumidores y la capacidad de escalar horizontalmente. Una clave mal elegida puede generar hot partitions, donde una sola particion acumula la mayor parte del trafico mientras las demas permanecen subutilizadas.
Las tres estrategias mas utilizadas en ingenieria de datos son:
Particionamiento por entidad de negocio. Se utiliza el identificador de la entidad principal (customer_id, account_id, order_id) como clave. Todos los eventos de un mismo cliente terminan en la misma particion, lo que garantiza su procesamiento en orden cronologico. Es la estrategia predeterminada para pipelines de transacciones, pedidos y actividad de usuarios.
Particionamiento por sesion. Para datos de clickstream o telemetria, la clave session_id agrupa los eventos de una sesion en la misma particion. Usar user_id seria un error si un usuario puede tener multiples sesiones abiertas al mismo tiempo, ya que se mezclarian los eventos de sesiones distintas.
Particionamiento round-robin. Cuando el orden no importa (logs de aplicacion, metricas de infraestructura), se omite la clave de particion. Kafka distribuye los mensajes de forma uniforme entre todas las particiones, maximizando el paralelismo a costa de perder cualquier garantia de ordenamiento.
# partition_strategy.py — Choosing the right partition key
from confluent_kafka import Producer
import json
def create_producer():
return Producer({
'bootstrap.servers': 'kafka-1:9092',
'acks': 'all',
'enable.idempotence': True,
'max.in.flight.requests.per.connection': 5
})
def publish_order_event(producer, event):
# Key by customer_id: all events for one customer stay ordered
key = str(event['customer_id']).encode('utf-8')
value = json.dumps(event).encode('utf-8')
producer.produce(
topic='order-events',
key=key,
value=value,
headers=[('source', b'order-service'), ('version', b'2')]
)
producer.flush()
def publish_clickstream(producer, event):
# Key by session_id: ordering within a session matters
# NOT by user_id — one user may have multiple sessions
key = str(event['session_id']).encode('utf-8')
value = json.dumps(event).encode('utf-8')
producer.produce(
topic='clickstream',
key=key,
value=value
)La configuracion enable.idempotence: True combinada con acks: all habilita la produccion idempotente, lo que previene la duplicacion de mensajes cuando el productor reintenta un envio por timeout de red. El parametro max.in.flight.requests.per.connection: 5 preserva el orden aun con reintentos activos, gracias a una mejora introducida en Kafka 2.0 que elimino la restriccion previa de limitar las solicitudes en vuelo a 1.
Si el 80% de los eventos corresponde a un solo customer_id, esa particion concentrara una carga desproporcionada mientras las demas permanecen ociosas. Es fundamental monitorear el lag por particion y considerar claves compuestas (customer_id + fecha) o reparticionamiento cuando se detecta este desbalanceo. Agregar un sufijo aleatorio a la clave resuelve la distribucion pero sacrifica el orden.
Consumer Groups y manejo manual de offsets
Los consumer groups constituyen el mecanismo principal de paralelismo en el lado del consumo. Kafka asigna cada particion a un unico consumidor dentro del grupo, de manera que ningun mensaje se procesa en duplicado dentro del mismo grupo. Cuando un consumidor falla o se incorpora uno nuevo, Kafka ejecuta un rebalanceo que redistribuye las particiones entre los miembros activos.
Para pipelines de ingenieria de datos, la practica estandar es desactivar el auto-commit de offsets y confirmar manualmente despues de que cada lote se procesa de forma exitosa. Este patron proporciona semanticas at-least-once: si el consumidor falla despues de procesar pero antes de confirmar el offset, los mensajes se reprocesan tras el reinicio. La contrapartida es que la logica downstream debe ser idempotente para manejar duplicados sin generar inconsistencias.
El procesamiento en micro-batches optimiza el throughput al agrupar multiples registros antes de ejecutar una sola operacion de escritura al warehouse, en lugar de escribir registro por registro.
# consumer_pipeline.py — Consumer group with manual offset management
from confluent_kafka import Consumer, KafkaError
import json
def create_consumer(group_id: str):
return Consumer({
'bootstrap.servers': 'kafka-1:9092',
'group.id': group_id,
'auto.offset.reset': 'earliest',
'enable.auto.commit': False, # Manual commit for at-least-once
'max.poll.interval.ms': 300000,
'session.timeout.ms': 45000,
'isolation.level': 'read_committed' # Only read committed transactions
})
def process_batch(consumer: Consumer, batch_size: int = 500):
"""Process records in micro-batches for throughput."""
consumer.subscribe(['order-events'])
buffer = []
while True:
msg = consumer.poll(timeout=1.0)
if msg is None:
continue
if msg.error():
if msg.error().code() == KafkaError._PARTITION_EOF:
continue
raise Exception(msg.error())
record = json.loads(msg.value().decode('utf-8'))
buffer.append(record)
if len(buffer) >= batch_size:
# Process the full batch (write to warehouse, etc.)
write_to_warehouse(buffer)
# Commit AFTER successful processing
consumer.commit(asynchronous=False)
buffer.clear()
def write_to_warehouse(records: list):
# Insert batch into data warehouse (BigQuery, Snowflake, etc.)
passEl parametro isolation.level: read_committed resulta esencial cuando los productores utilizan transacciones. Con esta configuracion, el consumidor solo lee mensajes que fueron confirmados por una transaccion exitosa, descartando automaticamente los datos de transacciones abortadas.
La configuracion max.poll.interval.ms: 300000 (5 minutos) define el tiempo maximo permitido entre llamadas consecutivas a poll(). Si el procesamiento de un lote excede este intervalo, Kafka interpreta que el consumidor dejo de responder y dispara un rebalanceo. Para escrituras a warehouses lentos o lotes de gran volumen, resulta necesario incrementar este valor o reducir el tamano del batch.
¿Listo para aprobar tus entrevistas de Data Engineering?
Practica con nuestros simuladores interactivos, flashcards y tests técnicos.
Change Data Capture con Debezium y Kafka Connect
Change Data Capture (CDC) es uno de los patrones mas poderosos de Kafka en el contexto de ingenieria de datos. En lugar de ejecutar consultas periodicas (polling) contra la base de datos de origen, Debezium lee el log de replicacion (WAL en PostgreSQL, binlog en MySQL, oplog en MongoDB) y convierte cada INSERT, UPDATE y DELETE en un evento estructurado que se publica en un topic de Kafka.
Las ventajas de este enfoque son significativas: latencia minima entre el cambio en la base y su propagacion al pipeline, cero impacto en la base de datos operacional (la lectura del WAL es pasiva) y captura exhaustiva de todas las operaciones, incluyendo eliminaciones que el polling por timestamp no puede detectar sin mecanismos adicionales como soft delete.
La configuracion de un conector CDC para PostgreSQL muestra los componentes fundamentales:
{
"name": "postgres-cdc-source",
"config": {
"connector.class": "io.debezium.connector.postgresql.PostgresConnector",
"database.hostname": "postgres-primary",
"database.port": "5432",
"database.user": "replication_user",
"database.dbname": "production",
"topic.prefix": "cdc",
"table.include.list": "public.orders,public.customers,public.products",
"slot.name": "debezium_slot",
"plugin.name": "pgoutput",
"publication.name": "dbz_publication",
"snapshot.mode": "initial",
"transforms": "route",
"transforms.route.type": "org.apache.kafka.connect.transforms.RegexRouter",
"transforms.route.regex": "cdc\\.public\\.(.*)",
"transforms.route.replacement": "warehouse.$1"
}
}El parametro snapshot.mode: initial indica que Debezium captura primero una instantanea completa de las tablas configuradas y despues cambia a streaming de cambios incrementales. La transformacion RegexRouter renombra los topics de salida: cdc.public.orders se convierte en warehouse.orders, lo que facilita la organizacion logica por destino en el data warehouse.
El replication slot (debezium_slot) asegura que PostgreSQL no elimine segmentos WAL que todavia no fueron consumidos por el conector. Sin embargo, si Debezium se detiene durante un periodo prolongado, el slot puede acumular gigabytes de WAL, afectando el espacio en disco del servidor de base de datos.
Cuando Debezium se desconecta o se detiene por un periodo prolongado, el replication slot retiene segmentos WAL sin procesar. Es recomendable configurar alertas sobre el tamano del slot y establecer un limite maximo de retencion mediante el parametro max_slot_wal_keep_size en PostgreSQL para evitar que el disco del servidor se sature.
Exactly-once semantics: transacciones atomicas en Kafka
Las semanticas exactly-once resuelven el problema de procesamiento duplicado o perdida de mensajes en pipelines de streaming. Kafka implementa exactly-once a traves de transacciones que envuelven el ciclo completo consume-transform-produce en una sola operacion atomica.
El flujo transaccional opera de la siguiente forma: el consumidor lee un mensaje de un topic de entrada, aplica una transformacion y publica el resultado en un topic de salida. El offset del consumidor y el mensaje producido se confirman dentro de la misma transaccion. Si cualquier paso del proceso falla, la transaccion completa se aborta y el consumidor reprocesa el mensaje en el siguiente ciclo.
# exactly_once_pipeline.py — Transactional consume-transform-produce
from confluent_kafka import Consumer, Producer
import json
def transactional_pipeline():
consumer = Consumer({
'bootstrap.servers': 'kafka-1:9092',
'group.id': 'etl-transformer',
'enable.auto.commit': False,
'isolation.level': 'read_committed'
})
producer = Producer({
'bootstrap.servers': 'kafka-1:9092',
'transactional.id': 'etl-transformer-001',
'acks': 'all',
'enable.idempotence': True
})
producer.init_transactions()
consumer.subscribe(['raw-events'])
while True:
msg = consumer.poll(1.0)
if msg is None or msg.error():
continue
# Begin atomic transaction
producer.begin_transaction()
try:
raw = json.loads(msg.value())
enriched = transform(raw)
# Write transformed record to output topic
producer.produce(
'enriched-events',
key=msg.key(),
value=json.dumps(enriched).encode('utf-8')
)
# Commit consumer offset within the same transaction
producer.send_offsets_to_transaction(
consumer.position(consumer.assignment()),
consumer.consumer_group_metadata()
)
producer.commit_transaction()
except Exception:
producer.abort_transaction()
def transform(record: dict) -> dict:
# Apply business logic transformations
record['processed_at'] = '2026-04-20T00:00:00Z'
record['pipeline_version'] = '2.1'
return recordEl transactional.id identifica de forma unica al productor transaccional. Si el productor se reinicia con el mismo ID, Kafka aborta automaticamente cualquier transaccion pendiente de la instancia anterior, evitando mensajes duplicados. Cada instancia del pipeline debe operar con un ID transaccional distinto.
Es importante considerar que exactly-once introduce una penalizacion de latencia medible, tipicamente entre 50 y 200 milisegundos adicionales por transaccion. Para pipelines donde la latencia resulta critica y la logica de procesamiento ya es idempotente por naturaleza, las semanticas at-least-once con commit manual pueden resultar mas apropiadas.
Las transacciones de Kafka garantizan exactly-once exclusivamente dentro del ecosistema Kafka (consume-transform-produce). Para lograr exactly-once de extremo a extremo hacia un sistema externo como un data warehouse o una base de datos, es necesario complementar las transacciones de Kafka con escrituras idempotentes en el destino final, por ejemplo mediante upserts basados en una clave primaria natural.
Share Groups: el nuevo modelo de consumo en Kafka 4.2
Kafka 4.0 introdujo los Share Groups (KIP-932), un modelo de consumo que permite que multiples consumidores procesen mensajes de la misma particion de forma concurrente. A diferencia de los consumer groups clasicos, donde cada particion se asigna de manera exclusiva a un solo consumidor, los Share Groups distribuyen mensajes individuales entre los miembros del grupo con un mecanismo de acknowledgment por mensaje.
Este modelo se inspira en las colas de mensajes tradicionales (RabbitMQ, Amazon SQS) pero conserva las ventajas de Kafka como plataforma de streaming. Resulta particularmente valioso para cargas de trabajo donde el orden no es un requisito pero el paralelismo masivo si lo es: procesamiento de notificaciones, validaciones independientes, generacion de reportes y tareas de enriquecimiento sin estado.
| Caracteristica | Consumer Groups (clasico) | Share Groups (Kafka 4.x) | |---|---|---| | Asignacion | Una particion por consumidor | Mensajes individuales a cualquier consumidor | | Orden | Garantizado dentro de la particion | No garantizado | | Escalabilidad | Limitada por numero de particiones | Sin restriccion (mas consumidores que particiones) | | Caso de uso | Pipelines ordenados, CDC, event sourcing | Colas de trabajo, procesamiento sin estado | | Rebalanceo | Reasigna particiones completas | No requiere rebalanceo | | Semantica de entrega | At-least-once / exactly-once | At-least-once con acknowledgment por mensaje | | Reintento | Replay desde un offset | Redelivery por mensaje individual (NACK) |
Los Share Groups no sustituyen a los consumer groups tradicionales. Ambos modelos coexisten y responden a necesidades distintas. Para un pipeline ETL donde el orden de los eventos es critico, los consumer groups clasicos siguen siendo la opcion correcta. Para distribuir tareas de procesamiento independiente sin restricciones de orden, los Share Groups ofrecen una escalabilidad superior al eliminar la limitante historica del numero de particiones.
Preguntas de entrevista sobre Kafka para data engineers
Las entrevistas tecnicas para posiciones de ingenieria de datos incluyen de manera recurrente preguntas sobre Kafka. Las siguientes cubren los temas evaluados con mayor frecuencia.
Como garantiza Kafka el orden de los mensajes? El orden se garantiza unicamente dentro de una particion. Para mantener el orden entre eventos relacionados, se debe utilizar una clave de particion que agrupe esos eventos en la misma particion. Por ejemplo, customer_id para todos los eventos de un cliente. No existe orden global entre particiones distintas.
Cual es la diferencia entre at-least-once y exactly-once? Con at-least-once, un mensaje puede procesarse mas de una vez si el consumidor falla despues de procesarlo pero antes de confirmar el offset. Exactly-once emplea transacciones atomicas para confirmar el offset del consumidor y la produccion del mensaje transformado dentro de la misma operacion, garantizando que cada mensaje se procesa una unica vez.
Como funciona el rebalanceo de consumer groups? Cuando un consumidor se incorpora o abandona un grupo, Kafka redistribuye las particiones entre los miembros activos. Durante el rebalanceo, el procesamiento se detiene temporalmente. El protocolo Cooperative Sticky Assignor minimiza la interrupcion al reasignar unicamente las particiones afectadas, en lugar de revocar todas las asignaciones.
Que ocurre cuando un consumidor tarda demasiado en procesar?
Si el intervalo entre llamadas a poll() supera el valor de max.poll.interval.ms, Kafka considera al consumidor como inactivo y dispara un rebalanceo. Las soluciones incluyen reducir el tamano del batch, incrementar el intervalo maximo o delegar el procesamiento a un hilo separado.
Por que Kafka 4.x elimino ZooKeeper? ZooKeeper agregaba complejidad operativa considerable: requeria un cluster independiente, tenia sus propias necesidades de monitoreo y limitaba la escalabilidad de metadatos. KRaft incorpora la gestion de metadatos directamente en los brokers, simplificando los despliegues y acelerando la recuperacion ante fallos.
En que escenarios conviene usar Share Groups en lugar de consumer groups? Los Share Groups son adecuados cuando no se requiere ordenamiento de mensajes y se necesita escalar consumidores mas alla del numero de particiones disponibles. Son la opcion natural para cargas de trabajo tipo cola (job queue) como procesamiento de notificaciones, validaciones o tareas de enriquecimiento sin estado.
Como se previenen las hot partitions? Monitoreando el lag y la distribucion de mensajes por particion. Si una clave concentra un volumen desproporcionado de mensajes, las alternativas incluyen claves compuestas (customer_id + fecha), sufijos aleatorios (sacrificando el orden) o reparticionar el topic con un mayor numero de particiones.
Conclusion
Apache Kafka se ha consolidado como la pieza central de las arquitecturas modernas de datos en tiempo real. Los aspectos fundamentales que todo data engineer debe dominar incluyen:
- Kafka 4.x elimina ZooKeeper a favor de KRaft, simplificando despliegues y habilitando la gestion de metadatos a escala de millones de particiones
- La eleccion de la clave de particion define el orden, el paralelismo y la distribucion de carga del pipeline. Usar customer_id para pipelines transaccionales y session_id para datos de clickstream
- Los consumer groups con commit manual de offsets proporcionan semanticas at-least-once, el patron predominante para pipelines de ingestion hacia data warehouses
- CDC con Debezium captura cambios a nivel de fila desde PostgreSQL, MySQL o MongoDB sin impactar la base de datos operacional con consultas de extraccion
- Las transacciones de Kafka habilitan exactly-once dentro del ecosistema Kafka, envolviendo consume-transform-produce en una operacion atomica
- Los Share Groups de Kafka 4.x eliminan la restriccion historica de consumidores por particion para cargas de trabajo donde el orden no es un requisito
- En entrevistas tecnicas, se espera dominio de particionamiento, semanticas de entrega, rebalanceo de consumer groups y las diferencias entre KRaft y ZooKeeper
¡Empieza a practicar!
Pon a prueba tu conocimiento con nuestros simuladores de entrevista y tests técnicos.
Etiquetas
Compartir
Artículos relacionados

Top 25 Preguntas de Entrevista para Ingenieros de Datos en 2026
Guía completa con las 25 preguntas más importantes para entrevistas de ingeniería de datos en 2026. Incluye SQL avanzado, pipelines ETL/ELT, streaming con Kafka, Spark, orquestación y arquitecturas lakehouse.

ETL vs ELT en 2026: Guía Completa de Arquitectura de Pipelines de Datos
Análisis profundo de las arquitecturas ETL y ELT para pipelines de datos en 2026, incluyendo comparativas de costos, ejemplos de código con dbt y criterios de decisión para data engineers.

Apache Spark con Python: construir pipelines de datos paso a paso
Tutorial práctico de PySpark que cubre operaciones con DataFrame, construcción de pipelines ETL y las novedades de Spark 4.0. Incluye ejemplos de código listos para producción orientados a data engineers que preparan entrevistas técnicas.