En el desarrollo de sistemas distribuidos, uno de los conceptos más escurridizos es la garantía de que un mensaje sea procesado exactamente una vez. Muchos equipos asumen que las herramientas de mensajería como Kafka o RabbitMQ ofrecen esta propiedad de forma nativa, pero la realidad es más compleja. El procesamiento exactamente una vez no es una característica del transporte, sino un objetivo que debe construirse a nivel de aplicación. En este artículo exploraremos por qué esto es así, cómo la idempotencia transforma el problema y qué patrones de producción permiten construir sistemas fiables en Go.

La raíz del malentendido está en separar la entrega del procesamiento. Un broker puede garantizar que un mensaje se entrega al menos una vez, pero no puede saber si el consumidor lo procesó correctamente. Si el servicio se cae justo después de procesar y antes de enviar el acuse, el broker reintentará y el mensaje se procesará de nuevo. Esto es un comportamiento intencionado, no un error. La pregunta correcta no es cómo evitar duplicados, sino cómo hacer que sean inocuos. Ahí entra la idempotencia: una operación que produce el mismo estado final sin importar cuántas veces se ejecute.

Implementar idempotencia en Go con PostgreSQL es una de las soluciones más robustas. La técnica consiste en crear una tabla de claves de idempotencia con una restricción única, e intentar insertar esa clave en la misma transacción que el procesamiento. Si la inserción falla por violación de unicidad, el mensaje ya fue procesado y se ignora. Este patrón, conocido como Inbox, convierte la base de datos en la única fuente de verdad. Es importante evitar el error común de usar el paquete incorrecto de pgx, ya que la detección del código de error 23505 puede fallar silenciosamente si se importa desde pgconn en lugar de pgx/v5/pgconn.

Para sistemas de alta concurrencia, Redis puede actuar como una capa rápida de bloqueo antes de consultar la base de datos. Mediante un script Lua atómico se verifica si la clave ya existe y, si no, se marca como pendiente con un TTL. Esto evita que cientos de peticiones concurrentes golpeen la base de datos al mismo tiempo. Sin embargo, Redis nunca debe ser la fuente de verdad para la idempotencia; solo es una optimización que reduce la carga.

Cuando trabajamos con Kafka, es común escuchar que ofrece exactly-once semantics, pero esto solo se cumple dentro del ecosistema de Kafka (productor-tópico-consumidor). Tan pronto como el consumidor interactúa con una base de datos externa, se pierde esa garantía. Por eso es fundamental deshabilitar el auto commit y gestionar manualmente el offset: procesar primero, luego confirmar. Si el procesamiento falla, el offset no se avanza y el mensaje se reintenta.

Uno de los problemas más complejos es el dual-write: actualizar la base de datos y publicar un evento son dos operaciones que deben ser atómicas. El patrón Transactional Outbox resuelve esto almacenando el evento a publicar en la misma transacción de la base de datos. Un worker independiente lee de la tabla outbox, publica el evento y actualiza el estado. Para escalar estos workers de forma segura, PostgreSQL ofrece FOR UPDATE SKIP LOCKED, que permite que múltiples instancias tomen lotes de eventos sin conflicto.

La integración con sistemas externos (pasarelas de pago, APIs de terceros) es el punto más delicado. Aquí la idempotencia debe ser provista por el proveedor a través de claves de idempotencia. Sin ellas, una duplicación puede provocar cargos dobles. Verificar que cada API externa soporte este mecanismo es indispensable antes de poner en producción cualquier flujo transaccional.

En Q2BSTUDIO entendemos que la fiabilidad no se logra con magia, sino con arquitecturas bien diseñadas. Desarrollamos aplicaciones a medida que incorporan estos patrones desde el inicio, aprovechando servicios cloud AWS y Azure para escalar sin comprometer la consistencia. Además, integramos inteligencia artificial para empresas y agentes IA que requieren procesamiento fiable de eventos. Nuestro equipo también implementa ciberseguridad y servicios de inteligencia de negocio con Power BI, siempre poniendo la idempotencia y la resiliencia como pilares.

El apagado controlado es otro aspecto crítico. Kubernetes suele dar apenas 30 segundos antes de enviar SIGKILL. Es necesario capturar SIGTERM, detener la aceptación de nuevos mensajes y esperar a que las transacciones en curso terminen. La observabilidad también juega un papel clave: registrar cada duplicado detectado permite medir la efectividad de la estrategia de idempotencia. Si no puedes medirlos, no puedes probar que funciona.

En resumen, el mito de exactly-once como propiedad del transporte debe ser reemplazado por una estrategia pragmática: asumir al menos una entrega, construir idempotencia con bases de datos relacionales, usar patrones Inbox/Outbox y optimizar con Redis. La fiabilidad no consiste en evitar fallos, sino en seguir siendo correcto a pesar de ellos. Con las herramientas adecuadas y un enfoque disciplinado, es posible construir sistemas que manejen duplicados sin traumatismos.