Arquitectura Interna

Diseño de Particiones

El chart Helm configura cada topic del pipeline CDC con 3 particiones y 3 réplicas:

apiVersion: kafka.strimzi.io/v1beta2
kind: KafkaTopic
metadata:
  name: cdc.public.customers
  namespace: kafka-cdc
  labels:
    strimzi.io/cluster: cdc-cluster
spec:
  partitions: 3
  replicas: 3
  config:
    retention.ms: 604800000
    cleanup.policy: compact

¿Por qué 3 particiones?

Factor Decisión

Paralelismo

3 particiones permiten hasta 3 consumidores simultáneos en un consumer group

Replicación

Con RF=3 y min.insync.replicas=2, Kafka tolera la pérdida de 1 broker sin perder datos

Orden

Debezium usa la primary key como partition key, garantizando orden por registro

Escalabilidad

Podés aumentar particiones sin downtime (solo las agregás, nunca las eliminás)

Partition Key en CDC

Debezium usa el primary key del registro como partition key. Esto garantiza que todos los cambios de un mismo registro van a la misma partición, preservando el orden:

Registro con id=1  → Partition 0
Registro con id=2  → Partition 1
Registro con id=3  → Partition 2
Registro con id=4  → Partition 0 (hash wraps)

Consumer Groups y Escalabilidad

El pipeline tiene dos consumer groups principales:

Consumer Group Componente Réplicas

cdc-connect-cluster

KafkaConnect (Debezium + HTTP Sink)

2

camel-cdc-consumer

Apache Camel CDC Processor

2

Balanceo de particiones

Con 3 particiones y 2 consumidores:

Consumer 0: Partition 0, Partition 1
Consumer 1: Partition 2

Si un consumidor cae, Kafka rebalancea automáticamente:

Consumer 1: Partition 0, Partition 1, Partition 2

Al escalar a 3 réplicas, cada consumidor procesa exactamente 1 partición (distribución óptima).

Manejo de Offsets y Checkpoints

Offsets de Kafka

Cada consumer group mantiene su posición (offset) por partición en el topic interno __consumer_offsets:

oc exec -it cdc-cluster-kafka-0 -n kafka-cdc -- bin/kafka-consumer-groups.sh \
  --bootstrap-server localhost:9092 \
  --describe --group camel-cdc-consumer

Checkpoints de Debezium

Debezium mantiene su propio checkpoint via el replication slot de PostgreSQL y los offset topics de KafkaConnect:

config:
  offset.storage.topic: cdc-connect-offsets
  config.storage.topic: cdc-connect-configs
  status.storage.topic: cdc-connect-status
  offset.storage.replication.factor: 3
  config.storage.replication.factor: 3
  status.storage.replication.factor: 3
  • cdc-connect-offsets — posición actual en el WAL de PostgreSQL

  • cdc-connect-configs — configuración de los connectors

  • cdc-connect-status — estado de las tasks

Los 3 topics tienen replication.factor: 3 para sobrevivir la pérdida de un broker.

Replication Slot de PostgreSQL

Debezium crea un replication slot para no perder cambios si el connector se reinicia:

config:
  slot.name: debezium_cdc
  publication.name: cdc_publication
  snapshot.mode: initial
  • slot.name — nombre del slot lógico en PostgreSQL

  • snapshot.mode: initial — al iniciar por primera vez, Debezium captura un snapshot completo de las tablas

Para verificar el slot:

oc exec -it deploy/cdc-postgresql -n kafka-cdc -- psql -U cdcuser -d cdcdb -c \
  "SELECT slot_name, plugin, slot_type, active FROM pg_replication_slots;"

Retención y Reprocesamiento

Políticas de retención

Topic Retención Cleanup Policy

cdc.public.customers

7 días (604800000 ms)

compact

cdc.public.orders

7 días (604800000 ms)

compact

dlq.cdc-errors

30 días (2592000000 ms)

delete

dlq.cdc-camel-errors

30 días (2592000000 ms)

delete

¿Qué es cleanup.policy: compact?

Con compact, Kafka retiene solo el último valor para cada key, eliminando versiones anteriores. Para CDC:

  • La compactación preserva el estado actual de cada registro

  • La compactación elimina updates intermedios

  • Permite reconstruir el estado completo de una tabla consumiendo el topic desde el inicio

Reprocesamiento

Para reprocesar eventos, reseteá el offset del consumer group:

oc exec -it cdc-cluster-kafka-0 -n kafka-cdc -- bin/kafka-consumer-groups.sh \
  --bootstrap-server localhost:9092 \
  --group camel-cdc-consumer \
  --topic cdc.public.customers \
  --reset-offsets --to-earliest \
  --execute
Esto hace que el consumer reprocese todos los mensajes del topic. Usá con precaución en producción.

Configuración de Replicación

El cluster Kafka tiene los siguientes parámetros de replicación para garantizar durabilidad:

config:
  offsets.topic.replication.factor: 3
  transaction.state.log.replication.factor: 3
  transaction.state.log.min.isr: 2
  default.replication.factor: 3
  min.insync.replicas: 2
  • min.insync.replicas: 2 — un write es confirmado solo si 2 de las 3 réplicas lo persisten

  • default.replication.factor: 3 — Kafka crea todos los topics nuevos con RF=3

Esto significa que el cluster puede tolerar la pérdida de 1 broker sin perder datos ni disponibilidad.

Cómo funciona

Escritura en Kafka: de producer a disco

Cuando Debezium (o cualquier producer) envía un mensaje a Kafka, internamente ocurre:

  1. El producer serializa el key y value, calcula hash(key) % numPartitions para determinar la partición destino.

  2. El producer agrupa mensajes en batches por partición (configurable via batch.size y linger.ms) para optimizar I/O.

  3. El producer envía el batch al broker leader de la partición via TCP.

  4. El leader escribe el batch en un segment file en disco — un append-only log. Kafka rota los segments al llegar a 1GB (configurable).

  5. Cada mensaje recibe un offset secuencial: un entero de 64 bits, monotónicamente creciente, único dentro de la partición.

  6. Los followers fetch el batch del leader, lo escriben en sus propios segments, y envían un ACK al leader.

  7. Con acks=all (default en Strimzi) y min.insync.replicas=2, el leader solo confirma al producer cuando al menos 2 réplicas persisten el dato.

Lectura en Kafka: consumer groups

  1. Cada consumer del grupo se conecta al group coordinator (un broker elegido para gestionar el grupo).

  2. El coordinator ejecuta un partition assignment protocol (range o round-robin) distribuyendo las particiones entre los consumers.

  3. Cada consumer hace poll() periódicamente al leader de sus particiones asignadas, recibiendo batches de mensajes desde su último offset.

  4. El consumer procesa los mensajes y luego commitea el offset al topic interno __consumer_offsets.

  5. Si un consumer se desconecta (no envía heartbeat en session.timeout.ms), el coordinator dispara un rebalance y reasigna las particiones huérfanas a los consumers restantes.

Compactación: cleanup.policy=compact

Para topics CDC con cleanup.policy: compact:

  1. Kafka ejecuta periódicamente un log cleaner que escanea los segments cerrados.

  2. Para cada key, retiene solo el mensaje con el mayor offset (el más reciente).

  3. El log cleaner elimina físicamente del disco los mensajes con key duplicada y offset menor.

  4. Un mensaje con value null (tombstone) marca el key para eliminación completa después del periodo delete.retention.ms.

  5. Resultado: el topic contiene exactamente un mensaje por key — el estado actual de cada registro de la base de datos.

Network Policies

Las reglas NetworkPolicy protegen el namespace kafka-cdc y restringen el tráfico de ingress:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: kafka-cdc-allow-internal
  namespace: kafka-cdc
spec:
  podSelector: {}
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kafka-cdc
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: openshift-cluster-observability-operator
      ports:
        - protocol: TCP
          port: 9404
  • Solo pods dentro de kafka-cdc pueden comunicarse entre sí

  • El namespace de observabilidad puede acceder al puerto de métricas (9404) para scraping de Prometheus

Pod Disruption Budgets

Los PDBs garantizan que las operaciones de mantenimiento (drains, upgrades) no dejen el pipeline sin capacidad:

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: kafka-brokers-pdb
  namespace: kafka-cdc
spec:
  minAvailable: 2
  selector:
    matchLabels:
      strimzi.io/cluster: cdc-cluster
      strimzi.io/kind: Kafka
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: kafka-connect-pdb
  namespace: kafka-cdc
spec:
  minAvailable: 1
  selector:
    matchLabels:
      strimzi.io/cluster: cdc-connect
      strimzi.io/kind: KafkaConnect
  • kafka-brokers-pdb — siempre hay al menos 2 brokers de los 3 disponibles

  • kafka-connect-pdb — siempre hay al menos 1 worker de Connect de los 2 disponibles

Documentación Oficial