Blog Jose Cuellar

//Tech Lead, Senior Backend Developer & Life-long learner

Transactional Outbox & Polling Publisher Patterns (2/3)

por Jose el 24 abril, 2020

A lo largo del tiempo las bases de datos NoSQL han ido mejorado sus características para ofrecernos ACID según el tipo (Single Row, Single Shard o Distrituded):

Modern databases offer distributed ACID, Sid Choudhury

El problema principal, no radica en que una determinada base de datos garantice o no ACID en sus transacciones (lógicamente, debería serlo para minimizar el riesgo de inconsistencias), sino dividir las operaciones de un mismo workflow (creación completa del pedido con todas sus operaciones) en varias transacciones locales independientes entre los diferentes contextos y bases de datos, sean las que sean.  

De modo que garantizar las comunicaciones entre los contextos es vital para permitir las ejecuciones distribuidas entre ellos manteniendo la consistencia de los datos.

Normalmente debemos notificar el evento una vez realizada la transacción local. Para ello, ejecutamos la transacción y a continuación publicamos el evento a nuestro message-broker:

El problema es que la transacción y el envío del evento, no son atómicos: se ejecutan independientemente pudiendo provocar posibles inconsistencias de datos si alguno de ambos falla:

El patrón Transactional Outbox nos permitirá una única transacción atómica garantizando una entrega At-Least-Once y dándonos la posibilidad de reprocesarlos en cualquier momento.

Lo aplicaremos guardando en la misma transacción tanto la operación que deseemos realizar (que garantice ACID), como los eventos que pueda generar. Los eventos quedarán persistidos en la base de datos:

Mediante Polling Publisher publicaríamos los eventos a nuestro message-broker. Manteniendo el estado de los eventos en el outbox comprobando el ACK en el momento de la publicación (asegurando así la recepción del evento). O incluso, el consumidor podría actualizar el evento a procesado correctamente mediante un CorrelationId o MessageId.

Aseguraremos At-least-once/Once-or-more (los mensajes no se perderán, aunque podrían duplicarse). Por ese motivo, nuestros consumidores deberían ser idempotentes (la ejecución de una o varias veces tendrá el mismo resultado).

Existen herramientas interesantes ya desarrolladas que nos permitirán adaptar outbox pattern de forma sencilla:

Como he comentado, existen motores de bases de datos distribuidas que nos permiten consistencia de datos en sus transacciones (con ciertas penalizaciones de rendimiento):

Azure Cosmos Db nos da la opción de strong consistency (single region/single operation) y garantizando ACID mediante TransactionalBatch. Ofreciéndonos las Change Feed Functions en el que nos notificará de cualquier inserción o actualización de un contenedor específico. Para ello, necesitaremos una colección donde guardaremos el estado de procesado de las notificaciones (lease containers).

De este modo (con algunos matices) podemos acercarnos al patrón outbox de forma sencilla (aunque, idealmente deberíamos crear una colección específica de eventos generados y disponer de un polling publisher):

Para evitar dicha penalización de rendimiento y garantizar la consistencia: podríamos escuchar los eventos generados creando los datos específicos de consulta en alguna otra base de datos o tecnología. Notificando así además a otros servicios (dada la importancia en la consistencia de datos en transacciones de negocio distribuidas como veremos en el siguiente post):

Hasta aquí uno de los patrones más recomendados habitualmente para garantizar el envío de eventos correspondientes a transacciones locales de cada servicio.

Lecturas recomendadas: