Schaalbaarheid: event-driven praktijk

Tijdens het (overigens zeer geslaagde) Café over schaalbaarheid afgelopen donderdag was er wat discussie over een event-gedreven implementatie door een aantal Sogyo’ers de afgelopen maanden ontworpen, gemaakt en inmiddels in productie gebracht.

Bovenstaande afbeelding geeft inzicht in de structuur van de applicatie. Vanuit een hardware-aansturing worden events opgevangen en via een centrale dispatcher-component gedistribueerd naar aangesloten componenten (in dit geval: nul of meer clients, een “domain model” component, een datawarehouse component (DWH) en de hardware zelf). De applicatie kan tot een paar honderd events per seconde te verwerken krijgen. De keuze is om via een centrale dispatcher component (geïmplementeerd in een paar regels code) alle events die binnenkomen uit de verschillende componenten tevens weer naar alle componenten te verspreiden. Er vindt dus in de dispatcher geen filtering plaats.

Dit was volgens een aantal mensen geen schaalbare oplossing. Ik wil dat bij deze weerleggen: ik denk namelijk dat deze oplossing tot honderden (in theorie duizenden) aangesloten clients zal schalen. Het bewijs in de vorm van een test ga ik nog leveren, hier allereerst de onderbouwing op papier (volgens Dijkstra overigens de enige juiste manier ;)).

De events worden asynchroon verstuurd. De dispatcher heeft dus per event dat afgevuurt wordt slechts één statement uit te voeren per aangesloten listener (DWH, hardware of client). Zoals wel bekend is het uitvoeren van één statement niet iets dat bijzonder lang hoeft te duren (een processor lopend op 2 GHz zou dat in principe in een halve nanoseconde moeten kunnen doen). Laten we er echter van de andere kant af rekenen. Ik had het over 200 events per seconde. Dat betekent dat elk event binnen 5 milliseconden afgehandeld moet worden. In het ideale geval zou de simpele dispatcher dan dus (5 milliseconden / 0.5 nanoseconde) = 10 miljoen (!) subscribers kunnen bedienen. Laten we uitgaan van een minder ideale situatie: een factor 1000 overhead voor communicatie bijvoorbeeld. Dan hebben we het nog altijd over 10.000 subscribers die bediend kunnen worden in deze tijdspanne.

Voor diegene die het dan nog niet zien, laten we een paar mogelijke eenvoudige uitbreidingen aangeven op de dispatcher. Ten eerste, voor drukke momenten wel handig: een queue. Dit maakt dat we de dispatcher ongeacht van de inkomende stroom continue op volle toeren kunnen laten draaien. Een andere interessante: parallel verwerken van events. Ik gaf al aan dat de notificaties asynchroon verstuurd worden. Echter, je kunt nog een niveau van asynchroniteit introduceren: voor elk event een aparte thread opstarten die de distributie regelt. Dit maakt eventuele delays in communicatie bij notificaties ook irrelevant.

Kortom: lineaire schaalbaarheid is iets krachtigs, zie ook:  http://en.wikipedia.org/wiki/Linear_time#Linear_time. Als we dus de simpele regel van “uitbreiding is optellen” (en niet: uitbreiden is vermenigvuldigen of kwadrateren) hanteren dan komt alles wel (of het nou half zes is of niet ;)).