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.
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