Marcio Cunha

Gestión de Concurrencia Optimista en Sistemas Distribuidos Basados en Event Sourcing

Aprende a gestionar colisiones de datos en arquitecturas basadas en eventos utilizando control optimista. Comprende el control de versiones, las contrapartidas operativas y las estrategias prácticas para mantener la consistencia sin bloquear tu aplicación.

Marcio Cunha•5 min
También disponible en:EnglishPortuguês
Resumen
  • El control optimista asume que los conflictos de escritura son raros y valida la versión de los datos solo en el momento de guardar el nuevo estado.
  • Los sistemas de Event Sourcing registran cada cambio como un evento inmutable, lo que simplifica el seguimiento del número de versión de cada entidad.
  • El uso incorrecto de claves de idempotencia en flujos asíncronos puede enmascarar fallas de concurrencia y duplicar efectos secundarios.
  • Las estrategias de compensación y reintento evitan la pérdida de datos cuando dos transacciones modifican la misma fuente simultáneamente.
  • La elección entre bloqueo pesimista y optimista define si el sistema prioriza la seguridad absoluta o la alta disponibilidad para múltiples usuarios.

El Desafío de la Concurrencia en Arquitecturas Descentralizadas

Imagina que dos personas intentan editar el mismo documento al mismo tiempo en un editor colaborativo en la nube. En los sistemas de bases de datos tradicionales, el sistema suele bloquear la fila de la tabla hasta que la primera persona termina, impidiendo que la segunda continúe. En la práctica, este bloqueo reduce la velocidad del software y genera cuellos de botella difíciles de escalar cuando miles de usuarios acceden al sistema simultáneamente. En arquitecturas basadas en Event Sourcing, donde el estado del sistema se deriva de una secuencia de eventos inmutables en lugar de actualizaciones destructivas, este problema adquiere una nueva dimensión. La concurrencia deja de ser un mero detalle de base de datos y pasa a ser un desafío fundamental de coordinación entre servicios distribuidos que operan de forma asíncrona.

Cómo Funciona la Concurrencia Optimista en la Práctica

La gestión de concurrencia optimista parte de una premisa simple y optimista: en la inmensa mayoría de las veces, dos usuarios o procesos no van a alterar exactamente la misma información al mismo tiempo. En lugar de bloquear el registro por adelantado, el sistema permite que cualquiera lea y modifique los datos libremente. Sin embargo, el mecanismo de guardado exige la presentación de un número de versión o marca de tiempo que acompañaba al registro en el momento de la lectura. En la práctica, si la base de datos nota que la versión actual es diferente de la que trajo el usuario, significa que otra persona alteró el registro en el ínterin. El sistema rechaza el cambio actual, notificando a la aplicación para que recargue los datos actualizados y vuelva a intentar la operación.

El Papel de los Streams de Eventos en la Validación de Versiones

En el corazón de un sistema de Event Sourcing, cada entidad de negocio posee un flujo exclusivo de eventos, a menudo llamado stream. Cada evento agregado a este flujo recibe un número secuencial, que actúa como la versión exacta de la entidad en ese momento específico. Cuando llega un comando para ser procesado, el manejador de comandos carga todos los eventos anteriores, reconstruye el estado actual de la entidad y verifica cuál es el número de versión esperado. Al adjuntar un nuevo evento al stream, la infraestructura exige que el número de versión sea exactamente el siguiente en la fila. Si otro proceso grabó un evento en el mismo segundo, la versión esperada cambia, la base de datos rechaza la transacción y el sistema evita corrupciones silenciosas de datos.

Implementando el Control de Versión con Código Funcional

Para ilustrar cómo opera este mecanismo en el código, podemos observar una estructura típica en lenguajes modernos que aplican el patrón de verificación de versión basada en expectativas. El ejemplo a continuación demuestra una función que intenta adjuntar un evento a un almacenamiento de eventos solo si el número de versión coincide con el estado actual almacenado.

interface EventRecord {
streamId: string;
version: number;
payload: any;
}

async function appendEventsOptimistically(
streamId: string,
expectedVersion: number,
newEvents: any[]
): Promise<boolean> {
const currentVersion = await eventStore.getLatestVersion(streamId);

if (currentVersion !== expectedVersion) {
throw new Error('Conflicto de concurrencia detectado: el estado fue modificado.');
}

const eventsToSave: EventRecord[] = newEvents.map((event, index) => ({
streamId,
version: expectedVersion + index + 1,
payload: event
}));

await eventStore.save(eventsToSave);
return true;
}

Este fragmento de código ilustra la comprobación esencial antes de cualquier escritura persistente. Si la versión actual difiere de la esperada, el sistema aborta la operación de guardado de forma segura, permitiendo que la capa de aplicación decida si debe reejecutar la lógica de negocio o solicitar una nueva intervención del usuario.

Estrategias para Manejar Conflictos y Reintentos

Detectar el conflicto es solo la mitad del camino; el sistema debe saber qué hacer cuando ocurre el rechazo. En la práctica, existen dos enfoques principales para resolver colisiones sin frustrar al usuario final. La primera es la política de reintento automático, donde la aplicación captura el error de concurrencia optimista, vuelve a leer los nuevos eventos del stream, reaplica la intención de negocio sobre el estado actualizado e intenta guardar de nuevo. El segundo enfoque implica delegar la resolución al usuario o a una cola de revisión manual, útil en escenarios complejos donde las decisiones automáticas pueden sobrescribir datos críticos de negocio. La elección depende directamente del dominio de la aplicación y del nivel de tolerancia a retrasos en el procesamiento.

Contrapartidas y Cuidado Operativo en Entornos Distribuidos

Adoptar la concurrencia optimista elimina la lentitud de los bloqueos tradicionales, pero introduce nuevos desafíos operativos que exigen atención redoblada. Si la tasa de colisiones es muy alta —por ejemplo, cientos de procesos intentando actualizar el mismo recurso central al mismo tiempo—, el número de reintentos se disparará, sobrecargando la CPU y la red con lecturas y escrituras repetidas. En estos raros casos de contención extrema, rediseñar el modelo de dominio para dividir el agregado en partes más pequeñas o utilizar colas de procesamiento serializado puede ser mucho más eficiente que insistir en la concurrencia optimista. Comprender los límites de la arquitectura garantiza que la elección tecnológica sirva al negocio, y no al revés.

Consideraciones Finales

La gestión de concurrencia optimista en sistemas basados en Event Sourcing es una herramienta poderosa para garantizar la integridad de los datos sin sacrificar la escalabilidad horizontal. Al tratar las versiones de los flujos de eventos como la única fuente de verdad para las validaciones de escritura, los equipos de ingeniería logran construir sistemas resilientes y capaces de gestionar múltiples flujos asíncronos simultáneos. El secreto del éxito radica en monitorear la tasa de conflictos y planificar estrategias inteligentes de reintento o particionamiento de datos cuando el volumen de accesos crezca más allá de lo previsto.