2011-11-22 12 views
9

Ho sperimentato con JOliver's Event Store 3.0 come componente potenziale in un progetto e ho provato a misurare il throughput degli eventi attraverso l'Event Store.Event Store 3.0 - Throughput/Performance

Ho iniziato a utilizzare un cablaggio semplice che essenzialmente ha iterato attraverso un ciclo for creando un nuovo flusso e commettendo un evento molto semplice comprendente un ID GUID e una proprietà stringa per un DB R2 MSSQL2K8. Il dispatcher era essenzialmente un no-op.

Questo approccio è riuscito a ottenere operazioni ~ 3K/secondo in esecuzione su un HP G6 DL380 a 8 vie con il DB su un G7 DL580 a 32 vie separato. Le macchine di prova non erano vincolate alle risorse, nel mio caso l'aspetto di blocco è il limite.

Qualcuno ha avuto qualche esperienza nel misurare il throughput di Event Store e che tipo di cifre sono state raggiunte? Speravo di ottenere almeno 1 ordine di grandezza in più di throughput al fine di renderlo un'opzione praticabile.

risposta

6

Sono d'accordo sul fatto che il blocco dell'IO diventerà il collo di bottiglia più grande. Uno dei problemi che riesco a vedere con il benchmark è che stai operando su un singolo stream. Quante radici aggregate hai nel tuo dominio con eventi 3K + al secondo? Il design principale di EventStore è per operazioni con multithreading su più aggregati che riducono contese e blocchi per applicazioni read-world.

Inoltre, quale meccanismo di serializzazione stai utilizzando? JSON.NET? Non ho ancora implementato un protocollo di buffer (ma), ma ogni benchmark mostra che PB è significativamente più veloce in termini di prestazioni. Sarebbe interessante eseguire un profiler contro la tua applicazione per vedere dove sono i maggiori colli di bottiglia.

Un'altra cosa che ho notato è che si introduce un hop di rete nell'equazione che aumenta la latenza (e il tempo di blocco) rispetto a qualsiasi singolo flusso. Se stessimo scrivendo su un'istanza SQL locale che utilizza unità a stato solido, potrei vedere i numeri molto più alti rispetto a un'istanza remota SQL che esegue unità magnetiche e che hanno i dati e i file di registro sullo stesso piatto.

Infine, l'applicazione di benchmark ha utilizzato System.Transactions o l'impostazione predefinita non ha transazioni? (L'EventStore è sicuro senza l'uso di System.Transactions o qualsiasi tipo di transazione SQL.)

Ora, con tutto ciò detto, non ho dubbi che ci siano aree nell'Event Store che potrebbero essere notevolmente ottimizzate con un un po 'di attenzione In effetti, sto prendendo a calci alcune revisioni di schemi compatibili con le versioni precedenti per la versione 3.1 per ridurre le scritture sui numeri eseguite all'interno di SQL Server (e motori RDBMS in generale) durante una singola operazione di commit.

Una delle più grandi domande di progettazione che ho affrontato quando si avvia la riscrittura 2.x che funge da fondamento per 3.x è l'idea di IO asincrono, non bloccante. Sappiamo tutti che node.js e altri server Web non bloccanti superano i server Web con thread di un ordine di grandezza. Tuttavia, il potenziale di complessità introdotto sul chiamante è aumentato ed è qualcosa che deve essere fortemente considerato perché è un cambiamento fondamentale nel modo in cui operano la maggior parte dei programmi e delle biblioteche. Se e quando passiamo a un modello event-out, non bloccante, sarebbe più in un intervallo di tempo 4.x.

Bottom line: pubblica i tuoi benchmark in modo che possiamo vedere dove sono i colli di bottiglia.

+1

Grazie per la risposta Jonathan. Per chiarire;) Ogni commit è una nuova origine evento, quindi sto commettendo 3K distinte origini evento distinte al secondo. Ommettere il salto in rete non ha migliorato le cose ma è un punto valido. Per quanto riguarda le transazioni, non mi sto esplicitamente arruolando in una transazione, ma potrebbe non essere la stessa cosa che non usare le Transazioni. Sto usando JSON per la serializzazione anche se, dato che non siamo vincolati alla CPU, non penso che ci stia ancora limitando. Ho pubblicato il test harness su GitHub (https://github.com/MattCollinge/EventStore-Performance-Tests.git). – MattC

6

Ottima domanda Matt (+1), e vedo che il Sig. Oliver stesso ha risposto come risposta (+1)!

Ho voluto introdurre un approccio leggermente diverso che io stesso sto giocando per aiutare con il collo di bottiglia 3.000 commette al secondo che stai vedendo.

Il pattern CQRS, che la maggior parte delle persone che utilizzano l'EventStore di JOliver sembra stia tentando di seguire, consente un numero di sotto-modelli "scalati". Il primo che di solito si accoda è il fatto che l'evento si commette da solo, il che significa che si sta verificando un collo di bottiglia. "Coda off" che significa offload dai commit effettivi e inserendoli in un processo di I/O ottimizzato per la scrittura non bloccante o " coda".

mia libera interpretazione è: trasmissione

Command -> Handlers di comando -> trasmissione Evento -> Gestori di eventi -> Conservare Evento

In realtà ci sono due punti di scale-out qui in questi modelli: la Gestori di comandi e Gestori di eventi. Come indicato sopra, la maggior parte inizia con il ridimensionamento delle porzioni del Gestore eventi o il commit nel tuo caso nella libreria EventStore, perché questo è in genere il collo di bottiglia più grande a causa della necessità di mantenerlo da qualche parte (ad esempio il database di Microsoft SQL Server).

Io stesso sto utilizzando alcuni provider diversi per testare le prestazioni migliori per "accodare" questi commit. CouchDB e .NET AppFabric Cache (che ha un'ottima funzionalità GetAndLock()). [OT] Mi piacciono molto le funzionalità di cache durevole di AppFabric che ti consentono di creare server di cache ridondanti che eseguono il backup delle tue regioni su più macchine, pertanto la tua cache rimane attiva finché c'è almeno 1 server attivo e funzionante. [/ OT]

Quindi, immaginate che gli Event Handler non scrivano direttamente i commit su EventStore. Invece, hai un gestore che li inserisce in un sistema "in coda", come Windows Azure Queue, CouchDB, Memcache, AppFabric Cache, ecc. Il punto è scegliere un sistema con pochi o nessun blocco per mettere in coda gli eventi, ma qualcosa che è durevole con la ridondanza integrata (Memcache è il mio preferito per le opzioni di ridondanza). È necessario disporre di tale ridondanza, nel caso in cui, se un server dovesse cadere, l'evento verrà comunque accodato.

Per impegnarsi definitivamente da questo "Evento in coda", ci sono diverse opzioni. Mi piace il pattern Queue di Windows Azure per questo, a causa dei molti "lavoratori" che puoi avere costantemente alla ricerca di lavoro in coda. Ma non deve essere Windows Azure: ho imitato il pattern Queue di Azure nel codice locale utilizzando una "Queue" e "Worker Roles" in esecuzione in background thread. Scala in modo piacevole.

Supponiamo che 10 addetti abbiano costantemente esaminato questa "coda" per qualsiasi evento Aggiornato dall'utente (io di solito scrivo un singolo ruolo di lavoratore per tipo di evento, rende più facile il ridimensionamento quando si monitora le statistiche di ciascun tipo). Due eventi vengono inseriti nella coda, i primi due lavoratori prelevano istantaneamente un messaggio ciascuno e li inseriscono (li impegna) direttamente nel tuo EventStore nello stesso momento - il multithreading, come Jonathan menziona nella sua risposta. Il collo di bottiglia con tale modello sarebbe qualsiasi supporto di database/archivio eventi selezionato. Supponiamo che il tuo EventStore utilizzi MSSQL e che il collo di bottiglia sia ancora 3.000 RPS. Va bene, perché il sistema è costruito per "recuperare" quando questi RPS si riducono a, diciamo 50 RPS dopo un burst di 20.000. Questo è il modello naturale che CQRS consente: "Eventuale coerenza".

Ho detto che c'erano altri schemi di scale-out nativi ai modelli CQRS. Un altro, come ho detto sopra, è il Command Handlers (o Command Events). Questo è quello che ho fatto, specialmente se si dispone di un dominio di dominio molto ricco come fa uno dei miei clienti (dozzine di controlli di convalida intensivi del processore su ogni comando). In tal caso, eseguirò la coda dei comandi stessi, per essere processati in background da alcuni ruoli di lavoro.Questo ti dà anche una buona scalabilità, perché ora puoi eseguire il thread dell'intero backend, incluso il EvetnStore degli eventi.

Ovviamente, il lato negativo è che si perdono alcuni controlli di convalida in tempo reale. Risolvilo solitamente segmentando la convalida in due categorie durante la strutturazione del mio dominio. Uno è Ajax o validazioni "leggere" in tempo reale nel dominio (un po 'come un controllo Pre-Command). E gli altri sono controlli di convalida hard-failure, che sono fatti solo nel dominio ma non sono disponibili per il controllo in tempo reale. Dovresti quindi eseguire il codice per errore nel modello di dominio. Significa, sempre codice per una via d'uscita se qualcosa fallisce, di solito sotto forma di una e-mail di notifica all'utente che qualcosa è andato storto. Poiché l'utente non è più bloccato da questo comando in coda, è necessario ricevere una notifica se il comando non riesce.

E i tuoi controlli di convalida che devono essere inviati al "back-end" vanno nel tuo database Query o "di sola lettura", riiiight? Non entrare in EventStore per verificare, ad esempio, un indirizzo email univoco. Faresti la tua convalida contro il tuo datastore di sola lettura altamente disponibile per le query del tuo front-end. Diamine, è necessario che un singolo documento CouchDB sia dedicato solo a un elenco di tutti gli indirizzi e-mail nel sistema come parte Query di CQRS.

CQRS è solo suggerimenti ... Se davvero bisogno di controllo in tempo reale di un metodo di validazione pesante, allora si può costruire una query (in sola lettura) negozio intorno a quella, e di accelerare la validazione - sul palco precomando, prima di viene inserito nella coda. Un sacco di flessibilità. E direi che convalidare cose come i nomi utente vuoti e le e-mail vuote non è nemmeno un problema di dominio, ma una responsabilità dell'interfaccia utente (che scarica la necessità di eseguire la convalida in tempo reale nel dominio). Ho architettato alcuni progetti in cui ho avuto una validazione dell'interfaccia utente molto ricca sui miei MVC/MVVM ViewModels. Ovviamente il mio dominio ha avuto una convalida molto severa, per garantire che sia valido prima dell'elaborazione. Ma spostando i mediocri controlli di convalida dell'input, o quella che chiamo convalida "light-weight", nei livelli ViewModel si ottiene un feedback quasi istantaneo all'utente finale, senza raggiungere il mio dominio. (Ci sono trucchi per mantenerlo sincronizzato con il tuo dominio).

Quindi, in breve, è possibile esaminare l'interruzione di tali eventi prima che vengano eseguiti. Questo si adatta perfettamente alle funzionalità multi-thread di EventStore come Jonathan menziona nella sua risposta.

+1

Risposta interessante. Grazie per averlo scritto! –

0

Abbiamo costruito una piccola piastra per una massiccia concorrenza utilizzando Erlang/Elixir, https://github.com/work-capital/elixir-cqrs-eventsourcing utilizzando Eventstore. Dobbiamo ancora ottimizzare le connessioni db, il pooling, ecc ... ma l'idea di avere un processo per aggregato con più connessioni db è allineata alle vostre esigenze.