2011-11-22 11 views
9

He estado experimentando con JOliver's Event Store 3.0 como un componente potencial en un proyecto y he estado tratando de medir el rendimiento de los eventos a través de la tienda de eventos.Event Store 3.0 - Rendimiento/rendimiento

Empecé a usar un arnés simple que esencialmente iteraba a través de un bucle for creando una nueva secuencia y cometiendo un evento muy simple que constaba de una Id. De GUID y una propiedad de cadena a una base de datos MSSQL2K8 R2. El despachador fue esencialmente un no-op.

Este enfoque logró lograr ~ 3K operaciones/segundo ejecutándose en un HP G6 DL380 de 8 vías con el DB en un G7 DL580 de 32 vías por separado. Las máquinas de prueba no tenían recursos limitados, el bloqueo parece ser el límite en mi caso.

¿Alguien ha tenido alguna experiencia en la medición del rendimiento de la tienda de eventos y qué tipo de cifras se han logrado? Tenía la esperanza de obtener al menos 1 orden de magnitud más de rendimiento para que sea una opción viable.

Respuesta

6

Estoy de acuerdo en que bloquear IO va a ser el mayor cuello de botella. Uno de los problemas que puedo ver con el punto de referencia es que estás operando contra una sola transmisión. ¿Cuántas raíces agregadas tienes en tu dominio con 3K + eventos por segundo? El diseño principal de EventStore es para operaciones multiproceso contra agregados múltiples que reduce contención y bloqueos para aplicaciones de mundo leído.

Además, ¿qué mecanismo de serialización está utilizando? JSON.NET? No tengo una implementación de Buffers de Protocolo (todavía), pero cada punto de referencia muestra que PB es significativamente más rápido en términos de rendimiento. Sería interesante ejecutar un generador de perfiles en su aplicación para ver dónde están los cuellos de botella más grandes.

Otra cosa que noté fue que estás introduciendo un salto de red en la ecuación que aumenta la latencia (y el tiempo de bloqueo) contra una sola transmisión. Si estuviera escribiendo en una instancia de SQL local que utiliza unidades de estado sólido, podría ver que los números son mucho más altos en comparación con una instancia de SQL remota que ejecuta unidades magnéticas y que tienen los archivos de datos y de registro en el mismo plato.

Por último, ¿su aplicación de referencia usó System.Transactions o no realizó ninguna transacción por defecto? (EventStore es seguro sin el uso de System.Transactions o cualquier tipo de transacción SQL.)

Ahora, con todo esto dicho, no tengo ninguna duda de que hay áreas en el EventStore que podrían optimizarse dramáticamente con un un poco de atención De hecho, estoy lanzando algunas revisiones de esquema compatibles con versiones anteriores para la versión 3.1 para reducir el número de escrituras realizadas dentro de SQL Server (y los motores RDBMS en general) durante una única operación de confirmación.

Una de las mayores preguntas de diseño que tuve que enfrentar al comenzar con la reescritura 2.x que sirve como base para 3.x es la idea de IO asincrónico, no bloqueante. Todos sabemos que node.js y otros servidores web que no bloquean vencer a los servidores web con hilos en un orden de magnitud. Sin embargo, el potencial de complejidad que se presenta a la persona que llama aumenta y es algo que se debe considerar seriamente porque es un cambio fundamental en la forma en que operan la mayoría de los programas y bibliotecas. Si y cuando nos mudemos a un modelo con y sin bloqueo, sería más en un marco de tiempo 4.x.

En pocas palabras: publique sus puntos de referencia para que podamos ver dónde están los cuellos de botella.

+1

Gracias por la respuesta Jonathan. Para aclarar;) Cada Commit es un nuevo EventSource, por lo que estoy comprometiendo 3K distintos EventSources por segundo. Ommitir el salto de red no mejoró las cosas, pero es un punto válido. En lo que respecta a las transacciones, no me estoy alistando explícitamente en una transacción, pero eso puede no ser lo mismo que no usar Transacciones. Estoy usando JSON para la serialización, aunque como no estamos atados a la CPU, no creo que eso nos esté limitando todavía. He publicado el arnés de prueba en GitHub (https://github.com/MattCollinge/EventStore-Performance-Tests.git). – MattC

6

Excelente pregunta Matt (+1), y veo que el propio Sr. Oliver respondió como la respuesta (+1)!

Quería dar un enfoque ligeramente diferente con el que estoy jugando para ayudar con el cuello de botella de 3.000 compromisos por segundo que está viendo.

El patrón CQRS, que la mayoría de las personas que usan EventStore de JOliver parecen estar intentando seguir, permite una serie de subpatrones de "escalamiento horizontal". La primera persona que suele hacer cola es cuando el evento se compromete, lo que está viendo es un cuello de botella. "Queue off" significa descargado de las confirmaciones reales e insertándolas en algún proceso de E/S no bloqueante optimizado para escritura o " cola".

Mi interpretación libre es: Retransmisión

Comando -> controladores de comandos -> evento de difusión -> controladores de eventos -> Evento tienda

En realidad, hay dos puntos de la escala de salida aquí en estos patrones: la Controladores de comandos y Controladores de eventos. Como se señaló anteriormente, la mayoría comienza con escalar las porciones del Manejador de eventos o los Compromisos en su caso a la biblioteca EventStore, porque este suele ser el mayor cuello de botella debido a la necesidad de persistir en algún lugar (por ejemplo, la base de datos Microsoft SQL Server).

Yo mismo estoy usando algunos proveedores diferentes para probar el mejor rendimiento para "poner en cola" estas confirmaciones. CouchDB y .NET AppFabric Cache (que tiene una gran función GetAndLock()). [OT] Realmente me gustan las funciones de caché duradera de AppFabric que le permiten crear servidores de caché redundantes que hacen copias de seguridad de sus regiones en varias máquinas; por lo tanto, su caché permanece activa mientras haya al menos 1 servidor funcionando. [/ OT]

Por lo tanto, imagine que sus controladores de eventos no escriben las confirmaciones en EventStore directamente. En su lugar, tiene un controlador que los inserta en un sistema de "cola", como Windows Azure Queue, CouchDB, Memcache, AppFabric Cache, etc. El punto es elegir un sistema con poco o ningún bloque para poner en cola los eventos, pero algo eso es durable con redundancia incorporada (Memcache es mi menos favorito para las opciones de redundancia). Debe tener esa redundancia, en caso de que un servidor caiga, todavía tiene el evento en cola.

Para finalmente comprometerse con este "evento en cola", hay varias opciones. Me gusta el patrón Queue de Windows Azure para esto, debido a los muchos "trabajadores" que puede tener constantemente buscando trabajo en la cola. Pero no tiene que ser Windows Azure: he imitado el patrón Queue de Azure en código local utilizando una "Cola" y "Roles de trabajo" ejecutándose en subprocesos de fondo. Se escala muy bien.

Supongamos que hay 10 trabajadores buscando constantemente esta "cola" para cualquier evento actualizado por el usuario (generalmente escribo un rol de trabajador único por tipo de evento, facilita el escalado a medida que supervisa las estadísticas de cada tipo). Dos eventos se insertan en la cola, los dos primeros trabajadores recogen un mensaje al instante e insertan (Commit them) directamente en su EventStore al mismo tiempo - multihilo, como mencionó Jonathan en su respuesta. Su cuello de botella con ese patrón sería cualquier respaldo de base de datos/tienda de eventos que seleccione. Digamos que su EventStore está usando MSSQL y el cuello de botella sigue siendo 3,000 RPS. Eso está bien, porque el sistema está diseñado para 'ponerse al día' cuando esos RPS se reducen a, digamos 50 RPS después de una ráfaga de 20,000. Este es el patrón natural que permite CQRS: "Consistencia eventual".

Dije que había otros patrones de escalabilidad nativos de los patrones CQRS. Otro, como mencioné anteriormente, son los controladores de comandos (o eventos de comando). Esto también lo he hecho, especialmente si tiene un dominio de dominio muy rico como lo hace uno de mis clientes (docenas de comprobaciones de validación intensivas en el procesador en cada comando). En ese caso, en realidad pondré en cola los Comandos, para ser procesados ​​en segundo plano por algunos roles de trabajador.Esto también le da un buen patrón de escalabilidad, porque ahora todo su backend, incluidas las confirmaciones EvetnStore de los eventos, puede enhebrarse.

Obviamente, la desventaja de eso es que pierdes algunas verificaciones de validación en tiempo real. Lo resuelvo generalmente segmentando la validación en dos categorías al estructurar mi dominio. Uno es Ajax o validaciones "livianas" en tiempo real en el dominio (algo así como una verificación previa al comando). Y los otros son comprobaciones de validación de falla física, que solo se realizan en el dominio pero no están disponibles para la verificación en tiempo real. Entonces necesitaría codificar por error en el modelo de Dominio. Es decir, siempre codifique una salida si algo falla, por lo general en la forma de un correo electrónico de notificación que le devuelve al usuario que algo salió mal. Como el usuario ya no está bloqueado por este comando en cola, se le debe notificar si el comando falla.

Y sus comprobaciones de validación que deben ir al 'backend' van a su base de datos Query o de "solo lectura", riiiight? No vaya a EventStore para verificar, por ejemplo, una dirección de correo electrónico única. Haría su validación contra su almacén de datos de solo lectura de alta disponibilidad para las consultas de su interfaz. Diablos, haga que un solo documento de CouchDB se dedique solo a una lista de todas las direcciones de correo electrónico del sistema como su porción de Consulta de CQRS.

CQRS es solo sugerencias ... Si realmente necesita verificar en tiempo real un método de validación pesado, entonces puede construir un almacén de consultas (de solo lectura) y acelerar la validación, en la etapa Precomando, antes se inserta en la cola. Mucha flexibilidad. Y hasta diría que validar cosas como los nombres de usuario vacíos y los correos electrónicos vacíos no es ni siquiera una cuestión de dominio, sino una responsabilidad de la interfaz de usuario (descargando la necesidad de realizar la validación en tiempo real en el dominio). Diseñé algunos proyectos en los que obtuve validación de UI muy completa en MVC/MVVM ViewModels. Por supuesto, mi dominio tiene una validación muy estricta, para garantizar que sea válido antes del procesamiento. Pero mover las mediocres comprobaciones de validación de entrada, o lo que yo llamo validación "ligera", hacia arriba en las capas de ViewModel brinda esa retroalimentación casi instantánea al usuario final, sin llegar a mi dominio. (También hay trucos para mantenerlo sincronizado con su dominio).

Por lo tanto, en resumen, posiblemente analice los eventos antes de comprometerlos. Esto encaja muy bien con las características de subprocesamiento múltiple de EventStore como Jonathan menciona en su respuesta.

+1

Interesante respuesta. Gracias por escribirlo! –

0

Creamos una pequeña plantilla para concurrencia masiva utilizando Erlang/Elixir, https://github.com/work-capital/elixir-cqrs-eventsourcing usando Eventstore. Todavía tenemos que optimizar las conexiones de db, la agrupación, etc., pero la idea de tener un proceso por agregado con múltiples conexiones de db se alinea con sus necesidades.