13 tricks til en hurtigere Apache webserver
Der vil selvsagt altid være meget fokus på at kunne få så meget performance som muligt ud af sine webservere. I denne guide vil jeg prøve at forklare om nogle af de performance tiltag, du kan lave på din Apache 2.x webserver, som ultimativt vil resultere i, at din server kan levere en bedre oplevelse til dine besøgende. Eksemplerne er taget ud fra min optimering af Apache på FreeBSD, men for det meste vil det være identisk på andre UNIX-platforme.
De ting, som jeg kommer ind på i denne guide, er som oftest ting, der kræver, at du har rettigheder til at kunne modificere Apaches konfiguration på webserveren. Det betyder, at du nok ikke skal forvente at kunne lave disse rettelser, hvis du ikke har din egen server / VPS med administratoradgang - men i stedet ligger på et delt webhotel.
1. Start med at finde din baseline
Før du går i gang med at lave optimeringen af din webserver, er det en god ide at lave et par benchmark målinger, så du har en baseline at gå ud fra. Det er nemlig altid sådan med performance optimering, at man må prøve at ændre lidt af gangen og så måle, om det har givet den ønskede forbedring. Hvis man ændrer for mange ting på samme tid, kan man risikere ikke at vide, hvad der har gjort udslaget - hvad enten det er positivt eller negativt.
Der findes mange værktøjer til at lave benchmark målinger imod en webserver, men for at komme hurtigt i gang anvender jeg i dette eksempel 'ab', som er et lille benchmark tool, der følger med Apache. Hvis du har mod på det, kan du også kigge nærmere på 'siege', 'httperf', 'wrk' eller nogle af de andre benchmark værktøjer, som findes derude.
Følgende kommando sender 500 requests afsted med 50 samtidige brugere, resultatet kan du se længere nede, og det er altså min baseline. Jeg kører kommandoen fra den samme server som Apache kører fra for på den måde at få minimal forstyrrelse fra netværket.
ab -n 500 -c 50 http://www.pslife.dk:80/
Resultatet viser, at 50% af mine requests er blevet besvaret på under 144ms, og 100% af dem er besvaret på under 180ms. Det er egentlig nogenlunde, men det kan godt komme længere ned. Optimalt bør benchmarkingen selvfølgelig udføres før serveren bliver sat i drift - hvis det allerede er for sent, så prøv at kør kommandoen et par gange, så vil du se, at den kan variere lidt, med stor sandsynlighed på grund af at der er andre besøgende.
En af de faktorer, som spiller mest ind i forbindelse med tuning af Apache, er RAM-forbruget. Her er det vigtigt at konfigurere sin webserver, så den kun bruger det ram, den har til rådighed. Alternativet er i værste fald, at serveren bliver tvunget til at bruge swap-pladsen, hvilket tvinger din performance i knæ. Der er flere forskellige indstillinger, som har direkte indflydelse på RAM-forbruget - blandt andet hvor mange (og hvilke) moduler, du har slået til i webserveren, og hvordan din 'MaxRequestWorkers' konfiguration ser ud.
2. Fjern de moduler som du ikke har brug for
En Apache webserver kan hjælpe med rigtig mange ting, og ofte får man installeret den med alt for mange forskellige moduler. Hvert modul er med til at forøge mængden af hukommelse, som hver Apache-proces bruger, derfor kan det godt betale sig at slå de moduler fra, som man alligevel ikke har tænkt sig at bruge.
Før du går i gang med at kigge nærmere på modulerne og eventuelt fjerne nogle af dem, skal du lige lave en baseline over, hvor meget RAM dine apache processer bruger før din tuning. Dette kan du gøre ved at køre følgende kommando:
ps aux | grep 'httpd' | awk '{print $6/1024 "MB";}'
For at se hvilke moduler, der er aktive lige nu i din webserver, kan du skrive følgende kommando, som resulterer i en liste over både statiske og delte moduler.
apachectl -M (på linux er det enten 'httpd -m' eller 'apache2 -m' afhængigt af OS'et).
Efter min egen gennemgang af modulerne, ser min liste ud som herunder. Det betyder naturligvis ikke, at din liste behøver at se ud på samme måde, da det jo afhænger af, hvad du bruger webserveren til.
core_module (static)
so_module (static)
http_module (static)
authn_file_module (shared)
authn_core_module (shared)
authz_host_module (shared)
authz_user_module (shared)
authz_core_module (shared)
access_compat_module (shared)
auth_basic_module (shared)
socache_shmcb_module (shared)
reqtimeout_module (shared)
filter_module (shared)
deflate_module (shared)
mime_module (shared)
log_config_module (shared)
logio_module (shared)
env_module (shared)
expires_module (shared)
headers_module (shared)
setenvif_module (shared)
version_module (shared)
ssl_module (shared)
mpm_prefork_module (shared)
unixd_module (shared)
autoindex_module (shared)
vhost_alias_module (shared)
dir_module (shared)
alias_module (shared)
rewrite_module (shared)
php5_module (shared)
Når du har listen, er det bare med at starte fra en ende af og lave en vurdering af, om du bruger de forskellige moduler. Det kan være en god ide at slå et enkelt modul fra af gangen og se om Apache vil starte op, nogle af dem hænger nemlig sammen med andre moduler. For at slå de forskellige moduler fra, skal du blot udkommentere dem med et # i httpd.conf filen, der som standard på FreeBSD befinder sig i /usr/local/etc/apache24. Efter du har fjernet et modul, skal Apache lige reloades, det kan du gøre ved hjælp af kommandoen:
apachectl graceful
Når du har fjernet de moduler, som du mener at kunne undvære, så kører du kommandoen der gav dig baselinen over hukommelsesforbruget på de forskellige apache-processer igen. Du skal bruge et cirka gennemsnitstal over resultatet, når du i næste punkt skal justere MaxRequestWorkers -værdien, denne indstilling hed oprindeligt 'MaxClients', men har altså fået nyt navn.
Kommandoen så sådan her ud:
ps aux | grep 'httpd' | awk '{print $6/1024 " MB";}'
3. Juster 'MaxRequestWorkers' værdien til dit miljø
Nu når du har skåret lidt ned på hvor meget hukommelse de enkelte httpd-processer bruger, så er næste skridt at konfigurere webserveren, således at der bliver plads til flest mulige samtidige processer uden at serveren kommer ud i de førnævnte problemer med eksempelvis swap. Indtil for ganske kort tid siden hed denne indstilling 'MaxClients', men den er i 2014 blevet omdøbt til MaxRequestWorkers - ideen bag den er dog præcis den samme som altid.
For at kunne lave denne fintuning af det maksimale antal klienter, skal vi lige have fundet nogle flere nøgletal; den totale mængde hukommelse der er i serveren og den mængde hukommelse, der bliver brugt, når apache ikke kører (men andre processer gør).
Regnestykket ser sådan her ud:
Total RAM - (RAM som OS bruger + RAM som andre processer bruger) / Gennemsnits størrelsen på httpd-processerne = antallet af MaxRequestWorkers som serveren maksimalt kan håndtere.
Som standard står MaxRequestWorkers til 250, hvilket betyder, at hvis hver httpd-proces fylder 40MB, så kræver det 10GB ram udelukkende reserveret til Apache for at kunne leve op til kravet. Tilret så det passer til dit miljø, du finder indstillingen i /usr/local/etc/apache24/extra/httpd-mpm.conf - som selvfølgelig skal være sat op til at blive inkluderet fra httpd.conf før indstillingerne vil træde i kraft.
Når du åbner httpd-mpm.conf vil du bemærke, at indstillingen står der flere gange under forskellige MPM (Multi-Processing Modules). Hvis du er i tvivl om hvilken MPM din Apache bruger, så kan du køre følgende kommando:
apachectl -V
Og det er så selvfølgelig i konfigurationen for denne MPM, at du skal rette indstillingen. Igen er det nødvendigt med en reload af Apache, før indstillingerne slår igennem.
4. Juster 'MaxConnectionsPerChild' for bedre hukommelsesstyring
Denne indstilling styrer hvor mange connections en httpd child-proces skal udføre, før den terminerer sig selv og en ny proces startes op. Apache fungerer på den måde, at hver httpd proces reserverer den mængde RAM, som den største request har fyldt og som udgangspunkt aldrig frigiver det igen - men det giver MaxConectionsPerChild en mulighed for at rydde lidt op i.
Som standard står denne indstilling til 0, hvilket betyder, at der aldrig vil blive ryddet op i processerne og deres hukommelsesallokation. Denne indstilling bør stadig sættes i den høje ende, eksempelvis 50000 eller 100000.
5. Skal man slå 'KeepAlive' fra eller til?
KeepAlive indstillingen gør det muligt at lave 'persistent connections', på godt dansk så kan brugerne nøjes med at oprette forbindelse til webserveren en gang og efterfølgende lave flere requests igennem samme forbindelse. Fordelen ved at have den slået til er, at man sparer brugeren for at oprette en ny forbindelse ved hvert request, og serveren slipper eventuelt for at spawne en ny proces - ulempen er, at man har en httpd proces liggende og vente på, om brugeren laver en request mere eller ej, hvilket koster hukommelse. Derudover er det værd at have vores tidligere tuning af 'MaxRequestWorkers' i baghovedet.
Hvis man læser rundt omkring på nettet, så splitter denne indstilling folk i to lejre, den ene mener, at man skal slå den helt fra og den anden mener, at man får et performance boost ved at have den slået til. I princippet afhænger det af, hvilken type side du har på webserveren. Hvis der er mange billeder og filer, der skal sendes til brugeren fra webserveren, så vil KeepAlive i mange tilfælde være en fordel. Hvis man sender filer og andet via et CDN eller har en form for cache foran Apache, så er KeepAlive måske ikke værd at have slået til.
Det nemmeste er selvfølgelig selv at teste forskellen, det kan du eksempelvis gøre via 'ab' som jeg beskrev i starten af guiden. For mit vedkommende giver det at have slået KeepAlive til og med en 'KeepAliveTimeout' på 1 sekund, dobbelt så hurtige svartider som hvis det er slået fra.
6. Undgå 'AllowOverride All': flyt dine htaccess regler ind i konfigurationen
Hvis man gerne vil have maksimal performance ud af sin webserver, så bliver man nødt til at kigge på, hvordan man gør helt normale ting. De fleste af os har helt sikkert en .htaccess fil liggende, og det er faktisk med til at hive performancen i den forkerte retning.
Årsagen findes ganske enkelt i, at når man har slået AllowOverride til, så kigger apache i alle katalogerne efter en .htaccess fil. Dette gør den ved at udføre en lstat() funktion for hvert enkelt underkatalog. For at tage et eksempel:
Hvis dit DocumentRoot er sat til /home/skou/WWW/ og dine billeder ligger i /home/skou/WWW/images, så vil Apache først lave en lstat() på /home, så på /home/skou, derefter /home/skou/WWW og til sidst på /home/skou/WWW/images, hvor den så måske - eller måske ikke - vil finde en .htaccess fil. Dette tager selvfølgelig tid og ressourcer, når det skal gøres for hvert request. Skrækscenariet er hvis man, måske med god grund, har en biblioteksstruktur der er meget lang - eksempelvis /home/skou/WWW/images/blog/2015/02/20/ - så skal Apache altså udføre denne lstat() funktion rigtig mange gange for hvert request.
Løsningen er ganske simpel: i stedet for at bruge .htaccess filer, skal du i stedet skrive alle de ting, som du normalt ville have i en .htaccess fil ind direkte i konfigurationen for den vhost, som den befinder sig under. En smart måde at gøre det på kan rent faktisk være ved at inkludere din .htaccess fil direkte i httpd-vhosts.conf filen på følgende måde:
Include /home/skou/WWW/.htaccess
Derefter kan du sætte 'AllowOverride none' og glæde dig over, at du nu har lavet et tiltag, der vil kunne mærkes. Eneste ulempe er, at hvis du vil lave ændringer til de ting du har i din .htaccess fil, så er det nødvendigt med en reload (apachectl graceful) af webserveren. Hvis man kører WordPress eller andre CMS'er der gør brug af .htaccess, så skal man lige have førnævnte genstart med i tankerne. Der er fordele og ulemper ved alt.
7. Optimer brugen af 'FollowSymLinks' og 'SymLinksIfOwnerMatch'
Disse to indstillinger er lidt i samme kategori som AllowOverride i og med, at der også bliver lavet lstat() systemkald, hvis de ikke bliver anvendt optimalt. Der kan selvfølgelig altid være en årsag til, at man vælger en specifik konfiguration, så igen er følgende anbefalinger ud fra det setup jeg kører, men jeg tror at det samme vil gøre sig gældende for de fleste andre.
Først og fremmest så sørg for altid at have slået FollowSymLinks til, på den måde behøver Apache ikke først at undersøge, om en fil eller katalog i virkeligheden er et symbolsk link, den kan bare straks anvende ressourcen.
Du kan konfigurere FollowSymLinks for en vhost på følgende måde:
<Directory /home/skou/WWW/>
AllowOverride None
Options FollowSymLinks
</Directory>
'SymLinksIfOwnerMatch' er som standard slået fra, så det er ikke nødvendigt med særskilt konfiguration - men den gør i princippet det, at den kun følger symbolske links, hvis ejeren af symlinket er den samme som den fil eller katalog, som det symbolske link peger på. Dette kræver igen, at der skal laves systemkald for at Apache kan finde de nødvendige oplysninger, og det kræver ressourcer.
8. Sørg for at 'HostnameLookups' er slået fra
Det er nok de færreste af os, der har behov for at have hostnavne til at stå i logfilerne i stedet for bare IP-adresserne på vores besøgende. Som standard er HostnameLookups slået fra og med god grund: hvis den er slået til, så vil Apache lave mindst et DNS opslag for hvert kald og det tager unødvendigt ekstra tid.
HostnameLookups Off
9. Korrekt konfiguration af 'DirectoryIndex'
Selvom jeg endnu har til gode at se en webserver være konfigureret med en forkert DirectoryIndex, så synes jeg alligevel, at det er værd at nævne. Det som DirectoryIndex indstillingen gør er, at den fortæller Apache, hvilke filer den skal vise automatisk fra et katalog og i hvilken rækkefølge. Fejlen som man kan lave her er, at det rent faktisk er muligt at angive en wildcard søgning, så i stedet for eksempelvis at skrive index.php og index.html, så kan man nøjes med at skrive index, og så vil Apache kigge efter en hvilken som helst fil, der hedder index som filnavn. Det sidste betyder, at Apache skal ud og 'søge' i stedet for præcis at kunne pinpointe, hvilke filer den skal kigge efter.
En korrekt konfiguration, som laves i httpd.conf, kan eksempelvis se sådan her ud:
<IfModule dir_module>
DirectoryIndex index.php index.html index.htm default.html
</IfModule>
En dårlig konfiguration kan se sådan her ud, men igen så kan man jo have en årsag til, at det er nøvendigt at have en sådan konfiguration:
<IfModule dir_module>
DirectoryIndex index default
</IfModule>
10. Optimer netværksbelastningen med DEFLATE, gzip, expires og headers
De fleste browsere giver i dag mulighed for, at man kan modtage komprimeret indhold og dermed spare netværkstid. I Apache kan dette gøres med 'deflate' modulet og gzip. Når modulet er slået til og konfigureret på webserveren, sørger den for at pakke indholdet ned, før det bliver sendt afsted til klienten - og det sker i øvrigt kun, hvis klienten rent faktisk understøtter det, oplysningen om kompatibiliteten sender browseren via headers til webserveren.
Et eksempel på konfiguration af deflate for en vhost kan se sådan her ud:
<Location /home/skou/WWW/>
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript
SetEnvIfNoCase Request_URI \
\.(?:gif|jpe?g|png)$ no-gzip dont-vary
Header append Vary User-Agent env=!dont-vary
</Location>
En af de andre muligheder for at optimere svartiderne er ved at bruge den cache, som de fleste moderne browsere har indbygget. I Apache kan man med fordel anvende modulet 'Expires' til at sætte de rigtige headers i de kald, som sendes retur til browseren.
I nedenstående eksempel, som blot er et udsnit af den pågældende VirtualHost, har jeg sat den til at cache statiske ting som css, javascript og billeder i 192 timer, altså 8 dage, fra det tidspunkt, hvor browseren har hentet dem. Derefter ved browseren, at den skal spørge webserveren efter indholdet igen. Der er en række andre indstillingsmuligheder til deflate og expire, dem kan du læse mere om her: mod_deflate manual, mod_expires manual.
<VirtualHost *:80>
Servername www.skou.dk
DocumentRoot /home/skou/WWW/
HostnameLookups off
ExpiresActive On
ExpiresByType image/gif "access plus 192 hours"
ExpiresByType image/jpeg "access plus 192 hours"
ExpiresByType image/png "access plus 192 hours"
ExpiresByType text/css "access plus 192 hours"
ExpiresByType application/javascript "access plus 192 hours"
ExpiresByType application/x-shockwave-flash "access plus 12 hours"
<Location /home/skou/WWW/>
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript
SetEnvIfNoCase Request_URI \
\.(?:gif|jpe?g|png)$ no-gzip dont-vary
Header append Vary User-Agent env=!dont-vary
</Location>
</VirtualHost>
11. Brug separate servere til dynamisk og statisk indhold
Selvom Apache er en super god webserver, faktisk den mest brugte på verdensplan, så er den også meget avanceret. Det betyder, at det i mange tilfælde kan være smart at lade en mere 'let' webserver overtage arbejdet med at håndtere de dele af en webside, som er statisk, det kan eksempelvis være billeder, javascript-filer og CSS-filer. På den måde frigiver man ressourcer fra Apache, som i stedet kan bruges på at håndtere dynamisk indhold, som eksempelvis er genereret via PHP.
Personligt har jeg i mange år anvendt lighttpd til statisk indhold, som er en lille hurtig webserver, der håndterer statisk indhold ganske fortræffeligt - den er faktisk rigtig hurtig. Den kører som en enkelt proces og tager ikke så meget hukommelse som en apache proces. En anden mulighed er ganske enkelt at købe sig adgang til et CDN (Content Delivery Network), som så vil sørge for at håndtere dine statiske ting.
Hvis man vil tage skridtet videre, kan man overveje at sætte en dedikeret cache-server foran sit webserver setup. Her ser det ud til, at Varnish og Nginx er to af dem, som mange sværger til i øjeblikket.
Der er som altid fordele og ulemper ved det hele. Ved at tilføje en ekstra cache-server, et CDN eller den lette webserver, tilføjer man ekstra kompleksitet til sit setup, fordi der så er flere delkomponenter, som altid skal være tilgængelige. Men det er altså den velkendte balancegang med, at man må gå lidt på kompromis med nogle ting, hvis man vil have nogle andre ting.
12. Overvej dit logbehov og tilret efter behovet
En af de ting, som mange glemmer at lave, er en vurdering af, hvilket behov der er for logning. Jeg har set tilfælde, hvor det slet ikke har været nødvendigt at have access logs, og hvor det derfor kunne slås helt fra - men i de fleste tilfælde vil man nok gerne have en eller anden form for logning. Her kommer justeringen af logniveauet ind i billedet.
Hvis man har loadet log_config modulet i Apache, hvilket er et krav for, at webserveren overhovedet kan logge, så har man også mulighed for at indstille logniveauet til en række forskellige niveauer, som giver flere eller færre oplysninger.
De mulige indstillinger i Apache 2.4 er: debug, info, notice, warn, error, crit, alert, emerg. Som standard bliver logfilerne skrevet med 'warn'' niveauet, men leg lidt med indstillingen og find ud af, hvilket niveau der passer dig bedst.
13. Andre overvejelser omkring logning
Der er en række andre ting, som det også kan være en god ide at overveje, når du kigger på dine logs. For det første er det en rigtig god ide at bruge 'piped logning', som i Apache kan gøres ved hjælp af det medfølgende software 'rotatelogs'. Når du vælger at bruge rotatelogs, sørger du for, at dine logfiler ikke bliver uhensigtsmæssige store, og det kan faktisk godt spille en rolle.
Det er ganske nemt at aktivere rotatelogs for en logfil, herunder kan du se et enkelt eksempel, men du kan se mange flere på rotatelogs siden hos Apache.
CustomLog "|bin/rotatelogs /var/log/logfile 86400" common
Hvis du vil gå hele vejen med din optimering af logsne, kan du også overveje, om det er muligt for dig at skrive logsne til en anden disk end den, som bliver brugt til at læse og skrive indhold til og fra.
Bonus: Slå 'OPTIONS' opslagene fra i access loggen
Ok indrømmet, jeg er ikke sikker på, at det her på nogen måde øger performancen, men det er rimelig irriterende at se på 'OPTIONS' entries i Apaches access log. Opslagene er nogle interne opslag, som Apache bruger til at holde styr på sine child processer. Hvis du synes, at det lyder vildt spændende, kan du læse mere om det i Apaches wiki.
Kaldene ser typisk sådan her ud:
127.0.0.1 - - [21/Feb/2015:18:42:56 +0100] "OPTIONS * HTTP/1.0" 200 -
127.0.0.1 - - [21/Feb/2015:18:42:57 +0100] "OPTIONS * HTTP/1.0" 200 -
127.0.0.1 - - [21/Feb/2015:18:43:06 +0100] "OPTIONS * HTTP/1.0" 200 -
127.0.0.1 - - [21/Feb/2015:18:43:07 +0100] "OPTIONS * HTTP/1.0" 200 -
127.0.0.1 - - [21/Feb/2015:18:43:08 +0100] "OPTIONS * HTTP/1.0" 200 -
127.0.0.1 - - [21/Feb/2015:18:43:09 +0100] "OPTIONS * HTTP/1.0" 200 -
127.0.0.1 - - [21/Feb/2015:18:43:10 +0100] "OPTIONS * HTTP/1.0" 200 -
127.0.0.1 - - [21/Feb/2015:18:43:11 +0100] "OPTIONS * HTTP/1.0" 200 -
127.0.0.1 - - [21/Feb/2015:18:43:12 +0100] "OPTIONS * HTTP/1.0" 200 -
127.0.0.1 - - [21/Feb/2015:18:43:13 +0100] "OPTIONS * HTTP/1.0" 200 -
Hvis du bare vil undgå, at de dukker op i din log, kan du gøre følgende for at slå dem fra, hvor 127.0.0.1 er den IP-adresse, som kaldet kommer fra, den kan også være IP-adressen på webserveren eller ::1.
I <IfModule log_config_module>-sektionen tilføjes følgende:
SetEnvIf Remote_Addr "127.0.0.1" dontlog
Og til CustomLoggen tilføjes dontlog til sidst på den her måde:
CustomLog "/var/log/httpd-access.log" common env=!dontlog
Derefter skal du lige lave en reload af webserveren, før den nye indstilling slår igennem.
Konklusion
Performance optimering indeholder som regel altid dilemmaer, og det er ofte nødvendigt at lave en afvejning af de positive og negative udfald, som tuning kan medføre på den ene eller den anden måde. Jeg håber, at denne lille guide har kunne give dig nogle fifs til, hvordan man kan forbedre performance på Apache 2.4 og måske også har givet dig lidt mod på selv at grave dybere ned i nogle af områderne.
Husk, at tricket til tuning altid er at danne sig et overblik over sin baseline, derefter tuner man, laver en ny måling, tuner lidt mere og sådan kan man blive ved.
God fornøjelse med din optimering!
Vil du vide mere?
Her er nogle links til et par steder, hvor du kan læse mere omkring et par af de emner jeg har været igennem her:
» Apache 'ab'
» Apache httpd wiki
» Apache 2.4 manual
Andet software:
» Varnish cache
» Lighttpd webserver
» Nginx webserver