woensdag 9 december 2009

Rekenen en doorlooptijd van threads

Vandaag aan het rekenen geslagen met de uitkomsten...en een beetje brainstormen...
We hebben :
  • een desktop met 2 CPU's, welke ons 50 actieve threads geeft. (standaard geeft .Net ons 25 Threads per CPU)
  • Een performance gemiddelde van 10K Request/s te halen.
Hoe lang mag een thread duren voordat we in de knel komen het aantal actieve threads ?

10000 Request/s = 10 Request/ms

Als we dus 50 threads hebben, mag elke thread maximaal 5 milliseconde actieve doorlooptijd hebben.... *ouch*

Genoeg voor file IO en geheugen acties, maar als er Databases bij komen kijken is dat toch wel lastig, want dan gaat het allemaal wat langzamer. En in dit geval denk ik niet dat het zal helpen om de DB actie asynchroon uit te voeren, want ergens zal er een thread moeten wachten op een antwoord, en we hebben een tekort aan threads....

De ThreadPool zou een thread vrij moeten geven als hij ergens op wacht, een kijken wat de impact is bij kortlopende DB acties. Maar daar moet ik eerst even een aparte DB server voor neerzetten.

To Be Continued...

donderdag 3 december 2009

Locking en Multi-Threading bij File IO

In de vorige post hebben we gekeken hoe we een eigen web server kunnen neerzetten, nu is de tijd aangebroken om er ook wat nuttigs mee te doen. Een mooie functionaliteit om eens in te bouwen is Request-Logging, redelijk simpel maar ook hier zitten er ook weer een paar zaken die net iets vaak over het hoofd worden gezien. Zeker omdat onze server netjes multi-threaded is, en dat bij het loggen naar een bestand niet helemaal optimaal is, daarnaast doen we 12k Requests per second dus het zijn er ook wat veel.

Wishful Thinking

Loggen met Create en AppendText()

'Wishful thinking', zei mijn wiskunde leraar altijd, als we weer eens creatief rekenwerk deden en de makkelijkste manier kozen om iets op te lossen. Natuurlijk was dit ook altijd een foute oplossing. Poging 1, gewoon omdat het ook eens leuk is om te zien wat het effect dan is van foute code.
Gelukkig is de CPU en Performance meter het met mij eens, dit werkt niet. CPU naar 100% en 12 Request per Second welke gelogged worden. De Applicatie crashed ook wanneer er 2 threads overlappen, en er tegelijkertijd naar dezelfde file wordt geschreven,.
Uitkomst
  • Req per Sec: 12
  • CPU Load: 100%
  • Errors: App. Crash

Global/Static/Singelton Concept

Één Instantie altijd open

Laten we bij een simpele verbetering beginnen, de CPU load. Windows is geen fan van het continue openen en sluiten van bestanden. Wat prima werk voor uitgebreidere acties gaat nu mis op de kwantiteit van de requests. Aangezien we toch altijd naar 1 bestand willen schrijven en dat non stop doen, kunnen we daarvoor net zo goed een gesharede StreamWriter voor gebruiken.
Kijk dit gaat al een stuk beter, misschien zelfs iets te goed. Laat hem maar even lopen, en je bestand vlieg de pan uit, en na een tijdje bij genoeg belasting raakt hij toch met zichzelf in de knoop. De Flush is verplicht, anders krijg je bijna direct een foutmelding. (uiteindelijk wil je niet bij elke WriteLine Flushen, dus ook deze is voor verbetering vatbaar) En dan moeten we ook nog iets verzinnen om van bestand te wisselen, want zoals te zien is wordt die wel erg groot. Zeker met 12k Regels per seconde.
Uitkomst
  • Req per Sec: 9500
  • CPU Load: 15%
  • Errors: Na verloop van tijd, en Filesize

Lock

Dan Locken we de boel toch, en wisselen we het bestand

Poging 3, netjes een lock gebruiken en een check of het bestand te groot wordt. Daarna Flushen, Closen, en een nieuwe aanmaken, zou goed moeten gaan.
En ook hier gaat het in het begin helemaal goed, lekker snel, iets meer processor belasting. Maar bij het wisselen van bestanden laat de server een paar steken vallen, en heeft de CPU het moeilijk. Als we nog eens goed naar de code kijken is dat ook niet zo heel raar, we locken tenslotte alles voor de hele actie.Op het moment dat we een bestand Closen en een nieuwe aanmaken, moeten alle threads op alles wachten. Misschien wel selectiever locken, en kijken of we de rest kunnen laten doorlopen.
Uitkomst
  • Req per Sec: 9500
  • CPU Load: 25% - 75% wisselen
  • Errors: Bij bestand wisselen, dropt performance.

Locks

Creatief met locks en statussen.

Nu kunnen we eens goed gaan kijken hoe we de locks kunnen optimaliseren. Er komen wel direct een aantal complicerende factoren bij kijken, maar het eind resultaat mag er zijn... Zoals we zagen houd een lock de overige threads tegen, en dus willen we zo min mogelijk code binnen een lock moeten uitvoeren. Neem bijvoorbeeld het maken/openen van een nieuwe LogFile, daar zouden de andere threads niet op hoeven wachten. Pas wanneer we de wissel maken van de oude stream naar de nieuwe zouden de andere threads even niet mogen wegschrijven.
Wat hebben we precies gedaan, we hebben 2 aparte locks aangebracht, nl de lock om weg te schrijven naar een stream, zodat tijdens het omschakelen naar een nieuwe stream dit voor geen problemen zorgt. De tweede lock zorgt ervoor dat we geen meerdere nieuwe bestanden krijgen bij 1 wisseling. Want als we alleen op fileSize zouden checken, zou er in de periode van fileSize > maxFileLength en de daarwerkelijke log = newLog, meerdere keren een nieuw bestand gemaakt kunnen worden. Door van 2 verschillende locks gebruik te maken zorgen we er ook voor dat het schrijven naar het log niet hoeft te wachten of de check of er een nieuw log moet komen. De uiteindelijke code welke nu in een lock statement staat is minimaal,
  • Lock(log): Het wisselen van Log Streams
  • Lock(log): Het wegschrijven naar de Stream en het lezen van de Filesize
  • Lock(isBeingCreatedLock): Het controleren of er een nieuw bestand moet komen
  • Lock(isBeingCreatedLock): Het aangeven dat er een nieuw bestand gemaakt is
Logisch gezien zou je de laatste lock kunnen plaatsen in de lock(log), maar dan zou je iets proberen te voorkomen wat in de praktijk nooit kan gebeuren. Let verder even op het gebruik van de shouldCreate variabele, en waarom we die gebruiken. Als laatste zouden we nog de actie welke in de if(shouldCreate) valt, kunnen verplaatsen naar een eigen thread, zeker als we verwachten dat dit heel lang duurt zou dat handig zijn. (nu is er één request welke wat langzamer is tijdens het openen en sluiten van de log bestanden)
Uitkomst
  • Req per Sec: 9500
  • CPU Load: 15% - 75% wisselen
  • Errors: Geen, blijft stabiel
Zoals je ziet is ook dit net even anders als de rechtdoorzee code welke veel geschreven wordt, vaak worden hier ook o.o.t.b. componenten voor gebruikt zodat je hier eigenlijk helemaal niet mee te maken heb. Toch is het goed om soms eens wat verder te kijken, en zeker als je zelf creatief bezig bent, of echte maatwerk integratie/oplossingen bouwt is het belangrijk dat je goed weet wat er precies allemaal onder de motorkap gebeurd. In dat geval heb je ook volledige controle en kennis indien er performance problemen ontstaan.
BB, Marinus

maandag 30 november 2009

Een eigen Webserver in 4 stappen...

[WebServer, ASynch, HttpListener, .Net]

Ok, je zal misschien wat meer als 4 stappen nodig hebben, maar echt heel moeilijk is het niet om je eigen webserver te maken. Met .Net 3.0 is het redelijk tot zeer eenvoudig om de basis neer te leggen, maar wat kan je verwachten, en wat is de impact van de verschillende manieren op het aantal requests per second. Daar gaan we hier eens wat dieper op in, want uiteindelijk is dat toch meestal wel waar het om gaat. Verder komen er een aantal interessante punten en dilemma's naar boven, vooral als je wat meer let op performance. Je werkt namelijk al snel met een compleet andere orde grootte aan requests per second, waar je normaal als ASP.Net programmeur niet snel mee te maken krijgt. Ter vergelijking, een tradionele web server, welke een simpele aspx pagina stuurt naar de client zit rond de 450 Req/Sec, straks zul je zien dat we daar echt ruimschoots aan voorbij gaan. Laten we eerst een kijken hoe we een simpele web server kunnen bouwen, die netjes antwoord geeft...

Poging 1 : Synchroon

Gewoon eens luisteren naar poort 80, en een antwoord terug


Om een idee te krijgen hoe snel een server zou kunnen zijn zonder het direct complexer te maken als het nodig is, beginnen we met synchrone request afhandeling. .Net heeft hiervoor een speciale klasse namelijk de HttpListener, welke netjes de inkomende request uitleest en je een HttpContext geeft.


Dit is volledig synchroon, en indien er meerdere requests binnenkomen, zal de performance niet echt optimaal zijn. Maar toch omdat we eigenlijk niks doen, is het een mooie leiddraad qua performance, op onze test machine kregen we al snel 4000 Req/s. Wat toch al een aangename verrassing was, en beduidend meer dan we gewent waren van een asp.net applicatie. Wat ook niet verwacht was is dat de CPU geen bottleneck is, maar dat die dus ergens anders ligt. We kregen maar een CPU load van ongeveer 6%. Uiteraard indien er meerdere gebruikers komen, en de requests langer duren gaat de performance op deze manier erg snel omlaag. Alles moet op elkaar wachten, en indien er 1 request mis gaat, stopt de hele server. Leuk als test, maar in de praktijk niet echt bruikbaar.

Uitkomst:
  • Req/Sec: 4000

  • CPU Load: 6 %

  • Errors: Geen

Poging 2: Multithread

Per request een thread starten en verwerken.


Poging 2, multi-threaden, elke binnenkomende request krijgt zijn eigen thread en mag ermee doen wat hij wilt.Zodat al het langdurige request/response werk netjes asynchroon verwerkt wordt.


Nu is dit zeker niet de meest optimale code, maar de CPU vloog naar de 90% en performance ging terug naar 150 Req/sec.Wel een hele extreme daling, dus echt een thread per request is geen goed idee.

Uitkomst:
  • Req/Sec: 150

  • CPU Load: 90 %

  • Errors: None

Poging 3: ThreadPool

Alle requests op een hoop.


Kijk ThreadsPooling en WorkQueues, dat zijn termen die goed klinken voor ons scenario. Windows regelt zelf het aantal actieve threads, geeft je een queue waarin je werk kan aanmelden.



Performance schoot inderdaad weer omhoog naar de 12k req/sec, bleef stabiel bij meerdere gebruikers, maar laat hier en daar wel een steekje vallen. Hier worden de verschillende threads wel apart verwerkt ,maar blijven we synchroon naar de poort luisteren. Wat bij een heftige belasting hier en daar een voor verstoppingen kan leiden. Eens kijken of we de binnenkomende requests sneller kunnen verwerken...

Uitkomst:
  • Req/Sec: 12000

  • CPU Load: 12 %

  • Errors: een paar bij meerdere gebruikers

Poging 4: ThreadPool + ASynch Listener

Minder wachttijd voor het oppakken van een request.


Aynchroon luisteren, we kunnen niet zomaar de GetContext verplaatsen naar de ThreadPool (in de DoWork) dat zou betekenen dat er meerdere objecten tegelijk naar dezelfde poort gaan lopen luisteren. We willen uiteindelijk gewoon 1 object hebben wat luistert, alleen moet hij sneller weer gaan luisteren dan in poging 3. er wordt namelijk wel wat werk verzet door de computer, nadat we de request binnen hebben. (o.a. het aanmelden bij de queue, en het maken van een WaitCallBack object) Ook daar is al in voorzien, en kunnen we de BeginGetContext en EndGetContext gebruiken.


Wat je hier ziet is dat we al meteen wanneer de EndGetContext wordt aangeroepen, we weer opnieuw gaan luisteren in de main thread naar een nieuwe request. Tegelijkertijd wordt rustig de vorige request op een de workerQueue gezet, en verwerkt. Performance is eigenlijk hetzelfde als de vorige poging, maar hij laat geen steken meer vallen. Fijn om te zien, dat er bijna tot geen overhead is in het gebruik van Callbacks, Events en ThreadPools. Deze opzet ziet er goed te gebruiken uit, we handelen de requests vlot en asynchroon af, draaien stabiel en hebben een lage CPU load.


Uitkomst:
  • Req/Sec: 12000

  • CPU Load: 12 %

  • Errors: Geen


Uiteindelijk is de code welke nodig is om in .Net een asynchrone web server te bouwen erg weinig. De performance is enorm, waarbij wij nog op een simpele desktop hebben gewerkt. (en dus nog zeker winst valt te halen). In sommige gevallen zeker de moeite waard om als alternatieve optie mee te nemen. De volgende post zullen we eens kijken, hoe we nu echt iets kunnen doen, en wat daar allemaal extra bij komt kijken. Verder gaan we later eens kijken hoe nu om te gaan met langlopende requests, want we gaan er maar steeds van uit dat we een request zo snel mogelijk willen afhandelen. In dat geval kan er iets misgaan met de workerQueue, want deze is vooral goed is kortlopende processen, en als we 2k aan wachtende request hebben, wil je eigenlijk geen 2k aan actieve threads hebben.


Opmerking: indien je zelf deze voorbeelden wilt uitproberen, en je draait Vista krijg je een foutmelding. (Access Denied). Gebruik netsh om de benodigde rechten te geven aan de account waaronder de app draait.
netsh http add urlacl url=http//+:80/ user=domain\user
Marinus

maandag 23 maart 2009

Merging Blogs

Blogs mergen is blijkbaar best lastig, al zijn er veel tools welke RSS feeds mengen.
http://pipes.yahoo.com is eigenlijk best ok, zeker sinds ze het datum sorteer issue opgelost hebben.
Wel is er een probleem met Atom Feeds, en description/summaries wat nog niet helemaal goed gaat.

Maar voorlopig is dit een prima concept oplossing welke we op onze MOSS ook gebuiken om alle Blogs te verzamelen en te tonen.

BB M

MOSS, DMZ en AD

Toch eens die eerste post maken, maar voorlopig een work-in-progress.
Even kort, MOSS ontsluiten via een DMZ naar Internet, maar dan wel de interne account en integratie mogenlijkheden kunnen gebruiken.
Opties:

  1. Creatief worden met Firewalls en AD, one-way forrest trusts en moeilijke configuraties.
  2. Gebruik een RODC (ReadOnly Domain Controller)
  3. Gebruik een reverse proxy ISA 2006 server (met default SPS integratie)

Zal uiteindelijk wel een mengelmoes worden, welke voornamelijk afhangt van welke diensten je wilt onsluiten op je DMZ.

Later meer,
Marinus