Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Designing Data-Intensive Applications: 11. Stream Processing #16

Open
calbertora opened this issue Apr 21, 2022 · 0 comments
Open

Designing Data-Intensive Applications: 11. Stream Processing #16

calbertora opened this issue Apr 21, 2022 · 0 comments

Comments

@calbertora
Copy link

Chapter 11: Stream Processing

En el capítulo pasado vimos como el procesamiento en batch tomaba archivos de entrada y producía archivos de salida, que podían ser las entradas de otros procesos. Pero hasta ahora se asumió que dichos archivos eran finitos.

Para lograr eso, se debe poner un límite a los archivos, digamos logs de una fecha a otra, o interacciones de usuarios en un día, o compras en una hora.

El procesamiento en stream es acercar ese límite a casi continuo, digamos datos con límites de 1 segundo.

Transmitting Event Streams

Para el procesamiento de streams las entradas son eventos, los cuales son pequeños objetos con toda la información necesaria. Puede ser un string, un JSON o incluso una forma binaria.

Al igual que con el procesamiento en batch, un evento puede ser la entrada de un proceso, y su salida puede ser consumida por uno o más procesos.

Messaging Systems

La forma de conectar producers y consumers es por medio de un sistema de mensajes. Los más usados son:
Mensajes directos entre producers y consumers
Message brokers: son colas de mensajes que se usan para acumular mensajes desde el producer, y enviarlos hacia el consumer.
Multiple consumers: En este caso se suelen usar load balancers (donde cada mensaje es entregado a un solo consumer), Fan-out (donde todos los mensajes son enviados a todos los consumers)

También se pueden usar sistemas para reenvío de mensajes en caso que los consumers no puedan procesarlos.

Partitioned Logs

Los logs de los mensajes pueden ser usados para guardar la información de este en el sistema de archivos. Esto permite que dichos logs puedan ser replicados y particionados en otros nodos.

Para saber qué mensajes procesa un consumer, se usan offsets de los mensajes. Así, si un consumer tiene algún crash, puede saber desde qué mensajes debe empezar a procesar nuevamente.

Generalmente los discos son usados cíclicamente, por lo que los mensajes no serán guardados por siempre, ya que al llenar su capacidad, estos empezaran a sobreescribir mensajes viejos.

Si algún consumer se llega a quedar rezagado, el servicio puede continuar operando sin problema.

Databases and Streams

Como vimos anteriormente, los streams y las bases de datos tienen mucho en común. Por ejemplo el guardar, replicar y particionar la información.

Keeping Systems in Sync

Es importante tener nuestros datos sincronizados. Por ejemplo si tenemos una BD y una Cache y ambos no están sincronizados, se pueden llegar a tener problemas. Pero tener datos sincronizados es una tarea muy difícil (réplicas lentas, race conditions etc etc).

Change Data Capture

CDC Es la manera en que se pueden replicar los datos cada que cambien desde un punto inicial, haciendo así que, la base de datos donde se guarden los datos originalmente, funcione como leader, y el resto de sistemas sean actualizado por medio de los cambios en los logs.

Aunque los logs pueden ocupar mucho espacio en disco, hay técnicas de compactación (log compaction) que ayudan a que esto pueda evitarse. Estas funcionan analizando los records que tienen las mismas keys, y guardando solo los últimos cambios.

Event Sourcing

El event sourcing al igual que CDC guarda los logs de los cambios, pero se diferencian en el nivel de abstracción, ya que este está enfocado más en el dominio de la aplicación.

El Event Sourcing es capaz de reproducir el estado actual de una aplicación a partir de los logs.

State, Streams, and Immutability

Cuando se guarda el changelog es posible obtener el estado actual de la aplicación, simplemente tomando todos los cambios desde el punto 0 hasta el actual. La ventaja de esto es que podemos saber el estado de la aplicación prácticamente en cualquier momento de la historia. Esto es importante ya que se pueden hacer auditorías, se puede validar en qué momento algo empezó a salir mal y por qué.

Incluso es posible crear diferentes vistas desde el mismo log (CQRS). Así podemos conectar y desconectar sistemas e ir liberando recursos, sin tener que cambiar los datos.

Para controlar temas de concurrencia, como vimos antes, podemos usar por ejemplo actualizaciones de vistas de manera sync, ya que, como los consumers son async, es probable que un usuario escriba un dato, y al actualizar no vea sus cambios hasta que el sistema encargado de hacerlo, consuma el servicio y lea el changelog.

Un problema que tenemos con la inmutabilidad de los datos, es que muchas veces por regulación o por alguna otra razón, debemos borrar los datos relacionados con un registro, completamente. Esto hace que debamos estar preparados para esto, ya que, borrar datos es bastante complejo en un sistema de changelog.

Processing Streams

Algunas de las cosas que se pueden hacer con los datos de un changelog son:
Escribir los eventos en una base de datos
Push events como alertas, notificaciones push o tableros en tiempo real
Alimentar otros streams con datos
En el resto del capítulo se va a tener en cuenta este último

Uses of Stream Processing

Algunos de los muchos posibles usos del Stream Processing son:
CEP (Complex Event Processing): Búsqueda de patrones complejos en los stream events
Stream Analytics: Poder aplicar analíticas estadísticas a los eventos
Mantenimiento de vistas materializadas, índices de búsqueda.
Búsqueda en streams: Parecido al CEP, pero buscando en cada evento algún patrón.
Paso de mensajes y RPC: Poder enviar mensajes a otros nodos sobre ciertos eventos.

Reasoning About Time

Usualmente en el procesamiento de Streams se debe trabajar con tiempo (cantidad de eventos en un minuto, promedio de errores en una hora etc etc). Para esto se debe diferenciar bien la hora en que se procesa (tiempo de máquina) con la hora del evento.

Es importante saber cuándo usar uno u otro ya que, se deben poner límites al momento de procesar, como por ejemplo, cuándo debo dejar de procesar eventos de una ventana de tiempo.

Stream Joins

Al igual que los batch jobs, los Streams también pueden crear joins entre los cuales están:
Stream-Stream: Cada stream job mantiene su estado por cierto tiempo, para que al ser consultado tenga sus valores actualizados
Stream-table: El join se puede ejecutar con una tabla usando los changelogs
Table-table: Parecido a una vista materializada

Los tipos anteriores necesitan una dependencia con el tiempo, en cuanto al manejo de su estado, para así saber qué punto en el tiempo es usado el join.

Fault Tolerance

La tolerancia a los fallos se logra usando algunas estrategias:
Microbatching and checkpoint: Se toman batch pequeños y con checkpoints para recuperarse a partir de estos
Atomic commit revisited: Todos los eventos se declaran ok sí solo sí el evento terminó exitosamente
Idempotence: Tratar que cada evento sea determinístico (se puede lograr agregando metadata)
La idea que luego de un fallo, siempre se pueda recuperar el estado.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant