Feeds:
Entradas
Comentarios

Archive for the ‘EventSourcing’ Category


¡ Bienvenidos de nuevo al mundo de CQRS ! Hoy nos vamos a centrar en ver como podemos implementar la persistencia del subsistema de comandos. La opción más tradicional es persistir una instantánea del modelo de datos directamente en una base de datos relacional mediante un ORM o similar. Los amantes de noSQL querrán grabar dicha instantánea usando sus sistema noSQL favorito. Pero ambas opciones se basan en grabar una instantánea del estado del modelo de datos. Sin embargo existe un sistema más simple y que aprovecha el ya existente mecanismo de eventos. Me estoy refiriendo a Event Sourcing.

Concepto y motivación

Ya hemos decidido que el subsistema de comandos va a emitir eventos de negocio, lo cuales serán procesados por el subsistema de consultas para actualizar su esquema de datos interno. Esto tiene algunas consecuencias cruciales:

  1. El subsistema de comandos no va a ser nunca leído por los clientes del sistema, ya que ellos usan siempre el subsistema de consulta.
  2. El subsistema de consultas es el único cliente que realiza operaciones de lectura sobre el subsistema de comandos.
  3. Lo único que va a pedir el subsistema de consultas al de comandos es la lista de eventos de negocio ocurridos desde la última vez que se produjo una sincronización.
  4. Si ocurre un desastre y perdemos todos los datos del subsistema de consultas, basta con levantarlo sin datos, y hacer que le pida al subsistema de comandos todos los eventos ocurridos desde el principio. Por lo tanto el estado del sistema puede residir por entero en el subsistema de comandos. Más sobre esto en futuros posts.

Visto esto, ¿para qué vamos a molestarnos en persistir un esquema de datos basado en entidades y relaciones? Al fin y al cabo ni los clientes ni el subsistema de consulta están interesados en estas entidades. Además lo único que necesitamos para recuperar el sistema en caso de desastre es la lista de eventos de negocio. Por lo tanto lo único que debería ser persistente es la lista de dichos eventos y no un hipotético modelo entidad/relación o un modelo de objetos.

Y este es precisamente el famoso Event Sourcing: almacenar todo el estado del sistema únicamente como una secuencia ordenada de eventos. Por lo tanto si nos decidimos por un mecanismo de sincronización basado en eventos de negocio, el enfoque de Event Sourcing a la persistencia es muy natural. No necesitamos almacenar entidades, ni las relaciones entre ellas, sino serializar una lista de eventos. Cada evento puede poseer campos, pero nunca vamos a necesitar buscar eventos por dichos campos, sino sólo por orden temporal, o como mucho por tipo de evento. El subsistema de consultas sólo va a realizar accesos como el que sigue: «dame todos los eventos sobre pedidos desde el momento X hasta la actualidad por orden cronológico», o «todos los eventos desde el principio».

Otra ventaja de event sourcing es que tenemos todo el historial de operaciones del sistema, lo que lo hace ideal para auditorias, depuración e informática forense.

No digo que debamos usar siempre event sourcing, pero siempre y cuando usemos eventos de negocio como mecanismo de sincronización, me cuesta trabajo pensar en escenarios donde event sourcing no tenga ventajas sobre grabar el modelo tal cual.

Transaccionalidad y consistencia

A nivel transaccional es bastante sencillo: cada comando es una transacción, cada transacción termina con la generación de un evento. A nivel de consistencia podemos tener varios niveles, en función de cuando el comando (la transacción) se da por terminado:

  • Fin de transacción cuando el evento se genera en memoria. Tanto la persistencia del evento como su transmisión al subsistema de consultas se produce de forma asíncrona, en segundo plano. Esto nos da el máximo de escalabilidad y rendimiento. Pero la consistencia es eventual y  podemos perder eventos (durabilidad baja).
  • Fin de transacción cuando el evento se persiste. La sincronización se produce de forma asíncrona, en segundo plano. De esta forma no perdemos eventos, a costa de tener una consistencia eventual entre las consultas y los comandos. Sin embargo la escalabilidad es bastante buena, a condición de que las escrituras sean rápidas. Es un buen compromiso como norma general.
  • Fin de transacción cuando el evento se persiste, y además el subsistema de consultas ha sido actualizado. Aquí tenemos consistencia estricta y durabilidad, pero penalizamos el rendimiento de las escrituras. Si por cualquier caso el subsistema de consulta está muy estresado, podemos perder disponibilidad en la escritura. Si realmente te encuentras en un escenario que requiere una consistencia tan estricta como esta, tal vez no deberías estar usando ni event sourcing ni CQRS.

Como vemos debemos usar un mecanismo de persistencia que escale muy bien a nivel de escritura (excepto en el primer caso) para que nuestro subsistema de comandos escale. No es tan importante en este caso la escalabilidad a nivel de lectura, ya que de ello se encarga el subsistema de consultas, que puede tener otro mecanismo de persistencia totalmente distinto. Además nos basta con un mecanismo que permita escribir en modo «append only» y soporte consultas cronológicas y por tipo. ¿Cómo implementamos pues la persistencia?

Alternativas de implementación

Podemos, como siempre, usar una BBDD relacional como soporte sobre el que almacenar la secuencia de eventos. Pero aunque factible, no es muy ventajoso. Al fin y al cabo nunca vamos a consultar por columnas, o por clave primaria. Tampoco vamos a tener que hacer joins. Ojo, no digo que no podamos usar SQL, lo que quiero hacer notar es que en este escenario el paradigma relacional no nos ofrece muchas ventajas.

Parece mejor usar un paradigma más sencillo. Por ejemplo, en un clave/valor podríamos usar como clave el número de orden del evento y como valor el propio evento serializado directamente. Una simple consulta por rango de clave nos valdría. Otra opción es usar algo como REDIS, que tiene soporte directo para listas y operaciones atómicas de incremento. Por otro lado, algunos sistemas noSQL, como Cassandra optimizan la escritura sobre la lectura, lo que viene bien en el caso de event sourcing.

Podemos simplificar aun más, y usar directamente el sistema de ficheros. Si lo pensáis bien en Event Sourcing no hay ni modificación ni borrado. Toda modificación en el sistema ocurre a través de un comando, que como resultado cambia el estado de este. Como consecuencia del cambio de estado siempre se produce un nuevo evento, que habremos de añadir a la secuencia de eventos ya existente. Lo mismo ocurre con las operaciones de borrado. A nivel de negocio raramente ocurre un borrado como tal. Podemos cancelar un pedido, o anularlo, o sea ejecutar otro comando que termina produciendo otro evento. Como se ve no se necesita ni modificar ni borrar ningún evento. Esto es importante porque tanto los discos magnéticos como los SSD se comportan bastante bien con sistemas que sólo añaden información por el final. En teoría no debería ser muy complicado hacer un sistema que abra un fichero en modo append only… qué curioso acabo de resucitar el concepto de log transaccional o log de operaciones, que se ha usado durante décadas en sistemas transaccionales robustos. Pero meterse a niveles tan bajos sólo compensa si realmente necesitas mucho rendimiento.

Ya existen sistema de persistencia especializados para event sourcing. Son los llamados Event Storages. Sin embargo todos los que conozco no acceden directamente al sistema de ficheros, sino que mapean el concepto de event sourcing sobre otros motores de persistencia como mySQL, Redis o Cassandra.

Snapshoting

Sin embargo la técnica de event sourcing tiene dos debilidades: ficheros que siempre crecen y arranques de sistema lentos.

Como vimos, no es común hacer borrados de los eventos ya que casi todas las operaciones terminan en un evento nuevo que hay que añadir al sistema de ficheros. Esto hace que el espacio de almacenamiento requerido crezca sin parar. Podemos definir eventos de negocio que ocupen muy poco espacio, y usar técnicas de compresión, pero eso sólo hace que el espacio ocupado crezca más lentamente.

Por otro lado, cada vez que el sistema se para y tiene que ser reiniciado, debe leer todo el log de eventos y reprocesarlos para obtener una instancia del modelo de negocio en memoria. Esto hace que el tiempo de arranque sea largo. Este problema afecta al subsistema de comandos principalmente. El subsistema de consultas suele almacenar directamente el estado del modelo de consultas, y sólo necesita procesar los eventos desde la última vez que se paró. Por lo tanto este problema no es importante en el caso del subsistema de consultas.

En los casos en que estos dos problemas son importantes, no se usa event sourcing puro, sino una técnica híbrida llamada snapshoting. La idea es usar event sourcing, pero en segundo plano, y cada cierto tiempo, ir procesando los eventos para generar una instantánea del estado del sistema que será persistida. Esta instantánea estará desfasada con respecto a la secuencia de comandos. La idea es almacenar tanto el log de eventos como la instantánea, por lo que se puede considerar event sourcing puro. Las ventajas son las siguientes:

  • Evitamos volver a procesar el log de eventos completo en caso de arranque. Cuando el sistema arranca lee la instantánea, y procesa sólo los eventos que ocurrieron posteriormente a la creación de dicha instantánea.
  • Se pueden borrar los eventos anteriores a la creación de la instantánea, a condición de que se pueda generar una secuencia de eventos equivalente a ella a partir de la instantánea.

Conclusión

Aprovechando que estamos usando eventos de negocio para sincronizar ambos subsistemas, y usar Event Sourcing como paradigma de persistencia en sistemas CQRS. Este enfoque se integra de forma natural con CQRS, es sencillo de mantener y evolucionar, y en general es una buena base para conseguir sistemas escalables. A nivel de rendimiento es importante que nuestro mecanismo de persistencia sea capaz de escalar bien en escritura.

En el próximo post nos adentraremos en el mundo del subsistema de consultas.

Read Full Post »