juni 2010 Archieven
Performance-problemen oplossen zonder extra hardware
Laatst was een van onze consultants bij een klant die last had van ernstige performance-problemen. Na uitbreiding van het aantal clientprocessen was het systeem bij tijd en wijle onbruikbaar traag.
In dergelijke gevallen komt dan al snel het aloude programma top om de hoek zetten om te kijken waar het probleem nu werkelijk in zit.
Helaas laat top op systeemniveau niet zien hoe het resourcegebruik is van disk en netwerk; dus wachtsituaties die bij die resources ontstaan zijn niet te zien. Gelukkig laat atop deze wel zien.
Na installatie van atop bleek dat van de 24 CPUs er hooguit 6 werk hadden, en er 18 idle waren. De CPU-belasting kon dus niet het probleem zijn. Het beschikbare geheugen (48GiB) werd voor 70% benut door de cache en er was geen swapping actief. Geheugengebrek was dus ook niet de oorzaak van het performance-probleem. Ook de disks konden de vraag nog aan: ze waren gemiddeld 30% busy. En het netwerk (gigabit) kon de stroom data van circa 10 megabit/s ruimschoots aan.
Je kunt je natuurlijk afvragen: hoe kan een systeem waarvan deze vier hardware resources niet overbelast zijn, toch zo onwerkbaar traag zijn. Vaak denken mensen dat als een systeem te traag is, er door extra of snellere hardware verlichting verkregen kan worden. Helaas is dat niet altijd (of eigenlijk: meestal niet) het geval.
Laten we een voorbeeld nemen. Stel dat er tegelijkertijd 100 applicaties draaien op het systeem die allemaal een record in een bestand willen aanpassen. Ze zullen daarvoor een record lock aanvragen bij het systeem. Als ze allemaal een ander record willen aanpassen, kunnen ze tegelijkertijd op het bestand werken. Maar wat nu als de applicatie zo geschreven is dat (om welke reden dan ook) er geen lock wordt aangevraagd voor een enkel record, maar voor de gehele file? Dan kunnen de 100 applicaties niet meer tegelijkertijd de file aanpassen, en zullen ze een voor een aan het werk moeten gaan. Zelfs al heb je 24 CPUs en zou je dus 24 applicaties echt tegelijk kunnen afhandelen. Als er nu per seconde meer nieuwe aanvragen binnenkomen dan er een-voor-een per seconde kunnen worden afgehandeld, krijg je een steeds groter wordende achterstand.
Je kunt dit vergelijken met een 24-baans snelweg waarvan op een klein stukje van de weg maar één baan mag worden bereden. Als je het brede deel van de snelweg verbreedt van 24 rijstroken naar 48, levert dat helemaal niets op. Het is de wegversmalling die het probleem oplevert. Hier geldt: meer asfalt, niets wijzer. Als je de wegversmalling zou verbreden (waarschijnlijk een veel goedkopere oplossing dan de hele weg verbreden), heb je het probleem op een veel effectievere manier opgelost.
In de praktijk blijkt dat software resources (zoals locks en
semaforen) erg vaak de reden zijn van performance-problemen.
Dergelijke wachtsituaties kun je zien door te kijken naar het
wait-channel in de uitvoer van ps:
$ ps -o pid,wchan:20,s,cmd
PID WCHAN S CMD
32275 wait S -bash
32277 pipe_wait S awk {n+=$0} END {print n}
32278 fcntl_setlk S tstlock
32279 - R tstlock
32280 fcntl_setlk S tstlock
32281 fcntl_setlk S tstlock
32282 fcntl_setlk S tstlock
32283 fcntl_setlk S tstlock
32284 fcntl_setlk S tstlock
32285 fcntl_setlk S tstlock
32299 - R ps -o pid,wchan:20,s,cmd
We kunnen hier duidelijk zien dat er maar een tstlock proces is dat loopt (de status is R). De andere staan allemaal te wachten op het verkrijgen van een file lock (ze staan in de kernel te wachten op fcntl_setlk). Op een systeem met meerdere CPUs staan hier de andere CPUs duimen te draaien...
Moraal van dit verhaal: Kijk bij performance-problemen naar alle resources, en niet alleen naar de hardware resources. Voor je het weet heb je veel geld uitgegeven aan nieuwe hardware die het probleem niet oplost omdat de problemen in de applicatiesoftware zitten. Motto: extra ijzer, niets wijzer. En oh ja, bij die klant kwam het ook goed zonder andere hardware te hoeven kopen.
To bash or to ksh, what is the difference?
In onze Linux/UNIX deel 2 cursus gaat het over shell scripting, met awk en sed daarbij behandeld. We mikken niet op allerlei exotische shells, maar gaan ervan uit dat men op commerciele UNIX'en (Solaris, HP/UX, AIX) een ksh gebruikt, en bij Linux en ..BSD een bash. Toch willen we niet dat de cursus vol gaat zitten met "bij ksh doe je het zus, en bij bash doe je het zo". Er zijn best veel detailverschillen tussen die twee shells, maar die zitten gelukkig bijna allemaal in de categorie "speelgoed voor gevorderden". Je kunt flinke scripts bouwen zonder dat verschillen de kop opsteken.
Dus als je een keer een script moet porten van een ksh-omgeving naar een bash-omgeving, of omgekeerd, dan zou aanpassen van de ''#!/bin/ksh'' versus ''#!/bin/bash'' voldoende kunnen zijn
Het enige verschil waar we in bovengenoemde cursus op wijzen is het gedrag van ''echo'' wanneer je daarbij de regelovergang wilt onderdrukken:
echo 'Maak nu uw keuze: \c' # Posix-standaard
echo -n 'Maak nu uw keuze: ' # Berkeley-stijl
De Berkeley-versie is het meest verbreid, maar ze is niet conform de Posix standaard. Bij Linux zit die Berkeley-vorm zowel in de ''/bin/echo'' executable als in de built-in echo van ''bash''. De Posix-standaard vorm komt uit de Korn Shell, maar vind je daar momenteel niet meer in terug.
Dave Korn vertelde een keer dat hij zoveel mails kreeg dat zijn shell het fout deed, dat hij maar is opgehouden met te antwoorden dat de fout bij de andere partij zit omdat die van de standaard afwijkt. Dus in de huidige ''ksh'' werkt de built-in ''echo'' ook op de ''-n'' manier. Maar als je een mix van oudere en jongere ksh versies in je computerverzameling hebt, dan kan dat nog wel portabliteitsproblemen veroorzaken. De beste oplossing is om, als je speciale effecten in je ''echo'' wilt hebben, helemaal geen ''echo'' commando te gebruiken maar over te stappen op ''printf''. Echo blijft het gemakkelijkste werkpaard voor recht toe recht aan boodschappen.
In de praktijk ben ik ook geregeld gestoten op een ander gemeen verschil tussen ksh en bash, dat zit in de afhandeling van pipes:
commando1 | commando2
De ''bash'' zal het laatste commando in deze reeks uitvoeren in een soort "sub-shell" context, vergelijkbaar met wat ronde haakjes in shell-taal doen, en wat in shell-functies gebeurt. De ''ksh'' voert (in dit geval) het laatste commando uit in de context van de hoofd-shell van het script. Het verschil tussen die twee manieren merk je alleen als dat laatste commando in de shell een blijvende verandering maakt, zoals vullen van een shell-variabele:
VAR=tekst1
echo tekst2 | read VAR
echo $VAR; # ksh geeft nieuwe tekst2, bash geeft oude text1
Als je een shell-variabele wilt vullen met een brokje informatie dat gebouwd moet worden door een pipe line van commando's dan kun je dus beter een alternatief kiezen dat in beide shells werkt:
VAR=$(commando | ...)
Het is een erg handige construtie om na de pipe een ''while''-loop te gebruiken:
VAR=tekst1; LINECOUNT=0
echo 'tekst2
tekst3' | while read VAR; do
let LINECOUNT++
echo VAR in de loop is $VAR
done
echo $LINECOUNT regels gehad en VAR na afloop is $VAR
Binnen de loop gebeurt wat je verwacht, en in beide shells hetzelfde: achtereenvolgens zie je in de loop de ''tekst2'' en ''tekst3'' worden afgedrukt. Maar na afloop meldt ''bash'' dat VAR ''tekst1'' bevat. Dus die is terug naar de waarde van vóór de loop, en consistent met het vorige voorbeeld. ''Bash'' meldt ook een LINECOUNT van 0. ''Ksh'' meldt dat de VAR leeg eindigt. Dat komt omdat de laatste ''read VAR'' niks meer binnenkreeg, en daardoor eindigde de loop. De LINECOUNT is bij ''ksh'' 2, zoals het hoort.
De moraal blijft dus: let op bij een pipeline die een blijvend resultaat binnen de shell-toestand moet opleveren.
