¿Por qué usar secuencias en Orleans?
Ya hay una amplia gama de tecnologías que permiten crear sistemas de procesamiento de secuencias. Entre ellas se incluyen sistemas para almacenar de forma duradera datos de secuencias (por ejemplo, Event Hubs y Kafka) y sistemas para expresar operaciones de proceso a través de datos de secuencias (por ejemplo, Azure Stream Analytics, Apache Storm y Apache Spark Streaming). Son excelentes sistemas que permiten crear canalizaciones de procesamiento de secuencias de datos eficaces.
Limitaciones de los sistemas existentes
Sin embargo, esos sistemas no son adecuados para el proceso de forma libre específica a través de los datos de secuencias. Los sistemas de proceso de secuencias mencionados anteriormente le permiten especificar un grafo unificado de flujo de datos de las operaciones que se aplican de la misma manera a todos los elementos de la secuencia. Se trata de un modelo eficaz cuando los datos son uniformes y quiere expresar el mismo conjunto de operaciones de transformación, filtrado o agregación en estos datos. Pero hay otros casos de uso en los que es necesario expresar operaciones radicalmente diferentes en distintos elementos de datos. Y, en algunos de ellos, como parte de este procesamiento, es necesario en ocasiones realizar una llamada externa, como invocar alguna API de REST arbitraria. Los motores unificados de procesamiento de secuencias de flujos de datos no admiten esos escenarios, los admiten de forma limitada y restringida o son ineficaces al admitirlos. Esto se debe a que están intrínsecamente optimizados para un gran volumen de elementos similares y suelen estar limitados en términos de expresividad y procesamiento. Las secuencias de Orleans tienen como destino esos otros escenarios.
Motivación
Todo comenzó con solicitudes de usuarios de Orleans para admitir la devolución de una secuencia de elementos desde una llamada de método de grano. Como se puede imaginar, eso era solo la punta del iceberg. Necesitaban mucho más que eso.
Un escenario típico de las secuencias de Orleans es cuando se tienen secuencias por usuario y se quiere realizar un procesamiento diferente para cada usuario, dentro del contexto de un usuario individual. Es posible que tengamos millones de usuarios, pero algunos de ellos están interesados en el tiempo y pueden suscribirse a alertas meteorológicas para una ubicación determinada, otros están interesados en eventos deportivos y otros hacen un seguimiento del estado de un vuelo determinado. El procesamiento de esos eventos requiere una lógica diferente, pero no quiere ejecutar dos instancias independientes del procesamiento de secuencias. Algunos usuarios solo están interesados en un stock determinado y solo si se presenta una determinada condición externa, una condición que puede no formar parte necesariamente de los datos de secuencias (y, por tanto, debe comprobarse dinámicamente en tiempo de ejecución como parte del procesamiento).
Los usuarios cambian sus intereses todo el tiempo, por lo que sus suscripciones a secuencias específicos de eventos vienen y van dinámicamente y la topología de las secuencias cambia dinámica y rápidamente. Además, la lógica de procesamiento por usuario evoluciona y cambia dinámicamente, en función del estado del usuario y de los eventos externos. Los eventos externos pueden modificar la lógica de procesamiento de un usuario determinado. Por ejemplo, en un sistema de detección de trampas en el juego, cuando se detecta una nueva forma de hacer trampas, la lógica de procesamiento debe actualizarse con la nueva regla para detectar esta nueva infracción. Esto debe hacerse sin interrumpir la canalización de procesamiento en curso. Los motores de procesamiento masivo de secuencias de flujos de datos no se crearon para admitir estos escenarios.
Huelga decir que este sistema tiene que ejecutarse en varias máquinas conectadas a la red, no en un solo nodo. Por lo tanto, la lógica de procesamiento debe distribuirse de forma escalable y elástica en un clúster de servidores.
Nuevos requisitos
Hemos identificado cuatro requisitos básicos para nuestro sistema de procesamiento de secuencias que le permitirán tener como destino los escenarios anteriores.
- Lógica de procesamiento de secuencias flexible
- Compatibilidad con topologías altamente dinámicas
- Granularidad de secuencias detallada
- Distribución
Lógica de procesamiento de secuencias flexible
Queremos que el sistema admita diferentes formas de expresar la lógica de procesamiento de flujos. Los sistemas existentes mencionados anteriormente requieren que el desarrollador escriba un grafo de cálculo declarativo de flujo de datos, normalmente siguiendo un estilo de programación funcional. Esto limita la expresividad y la flexibilidad de la lógica de procesamiento. Las secuencias de Orleans son indiferentes a la forma en que se expresa la lógica de procesamiento. Se puede expresar como un flujo de datos (por ejemplo, mediante extensiones reactivas (Rx) en .NET); como programa funcional; como consulta declarativa; o en una lógica imperativa general. La lógica puede ser con estado o sin estado, puede o no tener efectos secundarios y puede desencadenar acciones externas. Todo el poder lo tiene el desarrollador.
Compatibilidad con topologías dinámicas
Queremos que el sistema permita topologías en evolución dinámica. Los sistemas existentes mencionados anteriormente se suelen limitar a solo topologías estáticas que se fijan en el momento de la implementación y no pueden evolucionar en tiempo de ejecución. En el ejemplo siguiente de expresión de flujo de datos, todo es agradable y sencillo hasta que tiene que cambiarlo.
Stream.GroupBy(x=> x.key).Extract(x=>x.field).Select(x=>x+2).AverageWindow(x, 5sec).Where(x=>x > 0.8) *
Cambie la condición de umbral en el filtro Where, agregue la instrucción Select o agregue otra rama en el grafo de flujo de datos y genere un nuevo flujo de salida. En los sistemas existentes, esto no es posible sin anular toda la topología y reiniciar el flujo de datos desde cero. Prácticamente, esos sistemas controlarán el cálculo existente y podrán reiniciarse desde el punto de control más reciente. Sin embargo, este reinicio es perjudicial y costoso para un servicio en línea que produce resultados en tiempo real. Este reinicio se vuelve especialmente inviable cuando se trata de un gran número de expresiones de este tipo que se ejecutan con parámetros similares, pero diferentes (por usuario, por dispositivo, etc.) y que cambian continuamente.
Queremos que el sistema permita la evolución del grafo de procesamiento de secuencias en tiempo de ejecución, agregando nuevos vínculos o nodos al grafo de cálculo o cambiando la lógica de procesamiento dentro de los nodos de cálculo.
Granularidad de secuencias detallada
En los sistemas existentes, la unidad más pequeña de abstracción suele ser todo el flujo (topología). Sin embargo, muchos de nuestros escenarios de destino requieren que un nodo o vínculo individual en la topología sea una entidad lógica por sí misma. De este modo, cada entidad se puede administrar de forma independiente. Por ejemplo, en la topología de secuencia grande que consta de varios vínculos, los distintos vínculos pueden tener características diferentes y se pueden implementar en diferentes transportes físicos. Algunos vínculos pueden pasar por sockets TCP, mientras que otros pasan a través de colas de confianza. Diferentes vínculos pueden tener diferentes garantías de entrega. Los distintos nodos pueden tener diferentes estrategias de puntos de control y su lógica de procesamiento se puede expresar en diferentes modelos o incluso en lenguajes diferentes. Normalmente, esta flexibilidad no es posible en los sistemas existentes.
La unidad de abstracción y argumento de flexibilidad es similar a una comparación de SoA (arquitecturas orientadas a servicios) frente a actores. Los sistemas de actores permiten más flexibilidad, ya que cada actor es básicamente un "servicio diminuto" administrado de forma independiente. De forma similar, queremos que el sistema de transmisión permita este control detallado.
Distribución
Y, por supuesto, nuestro sistema debe tener todas las propiedades de un "buen sistema distribuido". Esto incluye:
- Escalabilidad: admitir un gran número de secuencias y elementos de proceso.
- Elasticidad: permitir agregar o quitar recursos para aumentar o reducir en función de la carga.
- Confiabilidad: ser resistente a los errores.
- Eficiencia: usar de manera eficaz los recursos subyacentes.
- Capacidad de respuesta: permitir escenarios casi en tiempo real.
Estos eran los requisitos que teníamos en mente para construir Orleans Streaming.
Aclaración: Orleans actualmente no admite de manera directa la escritura de expresiones declarativas de flujo de datos como en el ejemplo anterior. Las API actuales de Orleans Streaming son bloques de creación de bajo nivel, como se describe aquí. Proporcionar expresiones declarativas de flujo de datos es nuestro objetivo futuro.