Marcio Cunha

Procesamiento de Transacciones Distribuidas con Two-Phase Commit y Patrones SAGA en Microservicios Go

Aprenda a garantizar la consistencia de datos en microservicios usando Go. Analizamos los límites de Two-Phase Commit y la resiliencia asíncrona de los patrones SAGA.

Marcio Cunha•5 min
También disponible en:EnglishPortuguês
Resumen
  • Los sistemas distribuidos exigen romper monolitos, generando desafíos complejos de sincronización de datos entre bases aisladas.
  • El protocolo Two-Phase Commit ofrece consistencia estricta, pero sacrifica drásticamente la disponibilidad y bloquea recursos durante fallas.
  • El patrón SAGA reemplaza bloqueos síncronos por una cadena de transacciones locales combinadas con acciones compensatorias.
  • El lenguaje Go permite implementar transacciones resilientes combinando goroutines y canales para gestionar flujos asíncronos.
  • La elección entre consistencia inmediata o eventual depende directamente del modelo de negocio y de la tolerancia a latencia del sistema.

El Desafío de la Consistencia de Datos en Microservicios

Cuando separamos un sistema monolítico grande en varios microservicios más pequeños, cada pieza obtiene su propia base de datos aislada. En la práctica, esto significa que una simple compra en el comercio electrónico —que antes actualizaba el inventario, cobraba la tarjeta y generaba la factura en una sola transacción de base de datos— ahora necesita coordinar múltiples servicios independientes en la red. Si la red falla a mitad de camino, el sistema queda con datos inconsistentes, como el cliente cobrado pero el producto sin stock.

Garantizar que todos los pasos tengan éxito o que todo se deshaga es el gran problema de las transacciones distribuidas. En la ingeniería de software, buscamos la consistencia para evitar que el dinero desaparezca o que los pedidos queden perdidos en el limbo digital. Para resolver esto, la industria creó enfoques como el protocolo de dos pasos y secuencias de pasos compensatorios, cada uno con profundos trade-offs de rendimiento y complejidad.

Cómo Funciona Two-Phase Commit en la Práctica

El Two-Phase Commit, conocido como 2PC, es un protocolo clásico que intenta garantizar que varias bases de datos actualicen sus registros al mismo tiempo. En la primera fase, llamada preparación, un coordinador central pregunta a todas las bases de datos involucradas si pueden guardar los datos. Cada base verifica sus bloqueos, asegura que hay espacio y responde con un voto positivo o negativo. En la segunda fase, si todos votaron sí, el coordinador emite el comando para confirmar la escritura de forma definitiva.

La gran trampa de Two-Phase Commit es el bloqueo. Mientras las bases esperan la orden final del coordinador, mantienen los registros bloqueados para evitar cambios concurrentes. En la práctica, si el coordinador cae o la red se vuelve lenta durante esta ventana de espera, los recursos quedan no disponibles, degradando el rendimiento general del sistema. Debido a esta fragilidad ante caídas de red, las arquitecturas modernas evitan el 2PC en entornos de alta escala.

La Alternativa del Patrón SAGA para Alta Disponibilidad

Para escapar de los bloqueos rígidos del 2PC, la arquitectura moderna adopta el patrón SAGA. En lugar de una sola transacción global que bloquea todo, SAGA divide el proceso en una secuencia de transacciones locales. Cada microservicio ejecuta su operación en su propia base de datos y emite un evento para el siguiente paso. En la práctica, esto significa que la consistencia deja de ser inmediata y pasa a ser eventual, es decir, el sistema converge hacia el estado correcto de forma asíncrona.

El gran diferencial de SAGA es la compensación. Si la tercera etapa de un flujo de cinco falla, el sistema no puede simplemente activar un rollback automático, ya que los pasos anteriores ya confirmaron los cambios en bases de datos separadas. Lo que SAGA hace es disparar transacciones compensatorias en orden inverso, como emitir un reembolso en la tarjeta o devolver el artículo al inventario. Es como deshacer un pastel crudo que ya fue colocado en el horno mediante un proceso controlado de corrección.

Implementando Transacciones Asíncronas en Go

El lenguaje Go ofrece excelentes herramientas para manejar flujos asíncronos y concurrencia a través de goroutines (tareas ligeras que se ejecutan en paralelo) y canales de comunicación. Al construir una SAGA en Go, modelamos cada paso del flujo como un comando independiente que puede ser ejecutado, monitoreado y revertido si es necesario. Crear estructuras limpias ayuda a mantener el código legible y fácil de probar unitariamente.

package main

import (
	"context"
	"fmt"
	"time"
)

type Step struct {
	Name         string
	Execute      func(ctx context.Context) error
	Compensate   func(ctx context.Context) error
}

func RunSaga(ctx context.Context, steps []Step) error {
	var executed []Step
	for _, step := range steps {
		fmt.Printf("Ejecutando paso: %s\n", step.Name)
		if err := step.Execute(ctx); err != nil {
			fmt.Printf("Error en paso %s. Iniciando compensaciones...\n", step.Name)
			reverseSteps(ctx, executed)
			return err
		}
		executed = append(executed, step)
	}
	return nil
}

func reverseSteps(ctx context.Context, steps []Step) {
	for i := len(steps) - 1; i >= 0; i-- {
		step := steps[i]
		if step.Compensate != nil {
			_ = step.Compensate(ctx)
		}
	}
}

func main() {
	ctx := context.Background()
	steps := []Step{
		{
			Name: "ReservarInventario",
			Execute: func(c context.Context) error { return nil },
			Compensate: func(c context.Context) error { fmt.Println("Inventario devuelto."); return nil },
		},
	}
	_ = RunSaga(ctx, steps)
}

El código anterior demuestra una estructura básica de orquestación de SAGA en Go, donde un slice almacena los pasos exitosos para garantizar que la reversión ocurra en el orden correcto si algo sale mal. Este enfoque garantiza un control total sobre el flujo sin depender de protocolos de red complejos y bloqueantes.

Orquestación versus Coreografía en Sistemas Distribuidos

Al implementar el patrón SAGA, los ingenieros deben decidir entre dos modelos de control: orquestación o coreografía. En la coreografía, los microservicios hablan entre sí mediante eventos publicados en un bus de mensajes, sin un controlador central. Cada servicio escucha lo que le interesa, hace su trabajo y avisa al siguiente. En la práctica, esto reduce el acoplamiento estructural pero dificulta la visualización del flujo completo a medida que el sistema crece.

En la orquestación, por el contrario, existe un componente dedicado —llamado orquestrador— cuya única responsabilidad es dictar el orden de los pasos y decidir cuándo activar las compensaciones. Aunque añade un punto central de dependencia, el orquestrador simplifica drásticamente la depuración y el seguimiento de fallas en entornos empresariales complejos. Elegir entre ambos caminos depende de la madurez del equipo y de la visibilidad requerida por las reglas de negocio.

Consideraciones Finales sobre Consistencia en Arquitecturas Modernas

El procesamiento de transacciones distribuidas deja claro que la ingeniería de software es el arte de gestionar concesiones y trade-offs. Mientras TwoPhase Commit busca una consistencia estricta que penaliza la disponibilidad, los patrones SAGA abrazan la realidad imperfecta de las redes distribuidas a través de consistencia eventual y acciones compensatorias. Comprender estas diferencias evita arquitecturas frágiles y garantiza sistemas capaces de escalar con seguridad.

Dominar estas herramientas en lenguajes eficientes como Go permite construir microservicios resilientes, preparados para manejar fallas inevitables de infraestructura sin corromper los datos de los usuarios. La planificación cuidadosa de los flujos y la claridad en las reglas de compensación siguen siendo los pilares fundamentales para el éxito de cualquier aplicación moderna a gran escala.