En pocas palabras
Plataforma de streaming de eventos que almacena mensajes como un log ordenado, replicado y retenido en el tiempo, del que múltiples consumidores leen de forma independiente.
Qué problema resuelve
Integraciones punto a punto que se multiplican, pérdida de eventos y dificultad para que nuevos consumidores reprocesen el historial.
Cuándo utilizarlo
Arquitecturas orientadas a eventos, CDC, auditoría o pipelines de datos. Ejemplo: publicar cada cambio de estado de un crédito para riesgos, contabilidad y analítica.
De la idea a la práctica
Funcionamiento, ejemplos y decisiones para pasar al siguiente nivel.
Conceptos
| Concepto | Descripción |
|---|---|
| Topic | Categoría de eventos (credito-eventos) |
| Partición | Log ordenado e inmutable; unidad de paralelismo y de orden |
| Offset | Posición de un mensaje en la partición |
| Consumer group | Conjunto de consumidores que se reparten las particiones |
| Réplica / ISR | Copias de cada partición; in-sync replicas listas para liderar |
| Retención | Por tiempo, tamaño o compaction (último valor por clave) |
| KRaft | Modo de metadatos sin ZooKeeper; único modo desde Kafka 4.0 |
Un mismo topic puede tener muchos consumer groups, cada uno con su propio offset: riesgos lee en tiempo real y analítica reprocesa los últimos 30 días sin interferir.
Garantías de entrega
| Configuración | Efecto |
|---|---|
acks=all + min.insync.replicas=2 + factor de réplica 3 |
El broker confirma solo cuando hay al menos dos copias |
enable.idempotence=true (por defecto desde 3.0) |
Evita duplicados por reintentos del productor |
Transacciones + isolation.level=read_committed |
Exactly-once en flujos leer-procesar-escribir dentro de Kafka |
| Commit de offset tras procesar | Al menos una vez en el consumidor; requiere idempotencia |
Exactly-once no se extiende a efectos fuera de Kafka (una base de datos, una API): para eso, idempotencia en el consumidor.
Ejemplo: productor con Spring for Apache Kafka
@Service
class PublicadorEventos {
private final KafkaTemplate<String, CreditoEvento> kafka;
PublicadorEventos(KafkaTemplate<String, CreditoEvento> kafka) {
this.kafka = kafka;
}
void publicar(CreditoEvento evento) {
// clave = id del crédito → todos sus eventos van a la misma partición
kafka.send("credito-eventos", evento.creditoId(), evento);
}
}
Para que la publicación sea atómica con la base de datos, usa el transactional outbox en lugar de llamar a send dentro de la transacción de negocio.
Diseño de topics y esquemas
- Un topic por tipo de entidad o flujo, no por consumidor.
- Número de particiones según el paralelismo máximo de consumo previsto; aumentar después es posible, pero cambia la asignación de claves.
- Esquemas versionados (Avro, Protobuf o JSON Schema) en un registro con reglas de compatibilidad (backward por defecto).
- Dead letter topic para mensajes que fallan repetidamente, con alertas y un proceso de reproceso.
Operación
Monitoriza el consumer lag por grupo y partición: es la señal principal de que un consumidor no da abasto. Kafbat UI facilita inspeccionar topics y grupos. Como servicio gestionado existen Confluent Cloud, Amazon MSK, Aiven y Google Cloud Managed Service for Apache Kafka; en GCP, Pub/Sub es una alternativa más simple cuando no se necesita retención larga ni reprocesar por offset.
Consumidor con reintentos y dead letter topic
@Configuration
class KafkaConfig {
@Bean
DefaultErrorHandler manejadorErrores(KafkaTemplate<Object, Object> template) {
var recuperador = new DeadLetterPublishingRecoverer(template); // publica en <topic>-dlt
var backoff = new ExponentialBackOffWithMaxRetries(3);
backoff.setInitialInterval(500);
backoff.setMultiplier(2.0);
var handler = new DefaultErrorHandler(recuperador, backoff);
handler.addNotRetryableExceptions(ValidacionException.class); // a la DLT sin reintentar
return handler;
}
}
@KafkaListener(topics = "credito-eventos", groupId = "contabilidad")
void procesar(CreditoEvento evento, @Header(KafkaHeaders.RECEIVED_KEY) String clave) {
contabilidad.registrar(evento); // idempotente: deduplica por evento.id()
}
Para reintentos largos sin bloquear la partición, Spring Kafka ofrece @RetryableTopic, que reenvía el mensaje a topics de reintento con retardos crecientes.
Configuración del consumidor que conviene entender
| Propiedad | Efecto |
|---|---|
max.poll.records |
Mensajes por lote; lotes grandes con procesamiento lento provocan rebalanceos |
max.poll.interval.ms |
Tiempo máximo entre polls antes de expulsar al consumidor del grupo |
enable.auto.commit |
Desactivado en Spring por defecto; el commit lo gestiona el contenedor tras procesar |
auto.offset.reset |
earliest o latest cuando el grupo no tiene offset |
isolation.level |
read_committed para no leer mensajes de transacciones abortadas |
group.protocol=consumer |
Nuevo protocolo de rebalanceo (KIP-848), menos interrupciones |
Dimensionar particiones
particiones ≥ throughput objetivo / throughput por consumidor
Ejemplo: 3 000 msg/s objetivo; un consumidor procesa 400 msg/s → al menos 8 particiones
Margen para crecer: 12–16
El paralelismo máximo de un consumer group es el número de particiones: consumidores adicionales quedan ociosos. Demasiadas particiones aumentan la carga del clúster y el tiempo de recuperación.
Kafka frente a Google Pub/Sub
| Aspecto | Kafka | Pub/Sub |
|---|---|---|
| Modelo | Log particionado con retención | Cola gestionada con suscripciones |
| Orden | Por partición (clave) | Por ordering key, opcional |
| Reproceso | Mover el offset a cualquier punto | Seek a un instante o snapshot dentro de la retención |
| Operación | Clúster propio o gestionado | Sin servidores |
| Ecosistema | Kafka Connect, Streams, registro de esquemas | Integración nativa con servicios de GCP |
Para un ecosistema de eventos de dominio con muchos consumidores y reproceso frecuente, Kafka; para integración sencilla entre servicios en GCP, Pub/Sub suele bastar.
Criterios de implementación
Define propietario, esquema, retención y política de compatibilidad para cada topic. Prohíbe la creación automática de topics en producción. Trata los eventos como contratos públicos y documenta las decisiones de particionado en un ADR.
Continúa en las fuentes
Documentación oficial para verificar detalles y explorar más.