MaxClients, MaxKeepAliveRequests e KeepAliveTimeout: ottimizzazione di Apache HTTP Server

Apache HTTP Server Logo

Oggi sappiamo quanto sia importante la velocità di caricamento della pagina sia in termini di usabilità ed user experience, sia in termini SEO, in quanto i motori di ricerca hanno inserito la velocità di caricamento delle pagine nei loro algoritmi di ranking.

Di recente ho avuto modo di lavorare sull'ottimizzazione di un server web, ed è interessante notare come sia possibile ottenere ottimi risultati intervenendo su tre semplici parametri di configurazione del web server Apache:

  • MaxClients
  • MaxKeepAliveRequests
  • KeepAliveTimeout

parametri la cui importanza è spesso sottovalutata e che non sempre garantiscono le migliori prestazioni alle loro impostazioni di default.

MaxClients indica il numero massimo di processi del web server che possono essere eseguiti contemporaneamente. Cosa significa? Semplificando al massimo, ogni volta che un nuovo visitatore apre un sito web ospitato sul server, viene eseguito un nuovo processo di Apache, che si occupa di soddisfare tutte le richieste di quello specifico visitatore. Se ad esempio immaginiamo di identificare ad esempio il singolo client con una pagina aperta in un browser web, MaxClients 150, equivale a dire che in pratica sono consentite al massimo 150 richieste di pagina simultanee. Cosa succede se le richieste sono 151, ed un nuovo utente si connette con il suo browser? Semplicemente ques'ultimo resta in attesa che si liberi una connessione, ovvero che una delle 150 connessioni precedenti vengano chiuse. Ed è qui in effetti che intervengono gli altri due parametri, MaxKeepAliveRequests e KeepAliveTimeout. MaxKeepAliveRequests in pratica regola il numero massimo di richieste che possono essere effettuate durante una specifica connessione prima che questa venga chiusa, in altri termini, indica il numero massimo di richieste che il singolo processo di Apache può soddisfare prima di essere terminato e di chiudere quella connessione (una richiesta non è necessariemante identificabile con una singola pagina aperta nel browser, ma corrisponde ad un singolo file caricato dal server; se ad esempio una pagina è formata da 1 file html, 10 immagini, 3 file css e 2 file javascript, caricare questa pagina effettua un numero di richieste pari a 15). KeepAliveTimeout regola invece il numero massimo di secondi che è necessario attendere tra una richiesta e la successiva, prima di considerare chiusa quella specifica connessione da quello specifico client.

Torniamo all'esempio del browser web. Supponiamo di avere MaxKeepAliveRequests 100. Cosa significa? Significa semplicemente che alla 101esima richiesta da parte del nostro browser, il web server terminerà quel processo Apache e ne lancerà un altro, come se la richiesta provenisse da un differente utente (browser). Per l'utenet che visualizza la pagina web, la procedura è del tutto trasparente, e continuerà a navigare ignorando se e quanti processi Apache differenti vengono aperti, a meno che nel frattempo non sia stato superato il numero massimo di clients (MaxClients) concessi, nel qual caso l'utente vedrà il proprio browser restare in attesa di risposta dal server. Quanto dovrà attendere? Questo dipende da quante connessioni sono consentite per singolo client (MaxKeepAliveRequests) e a quanti secondi è impostato il parametro KeepAliveTimeout. Si potrebbe pensare a KeepAliveTimeout come al tempo medio che un ipotetico visitatore resterà su di una data pagina prima di cliccare su di un link ed effettuare quindi una nuova richiesta ad Apache. In genere 4-6 secondi può essere considerato un tempo accettabile. L'utente arriva sul sito, butta una rapida occhiata ai contenuti della pagina, cerca i link di interesse, clicca e quindi si ferma a leggere sulla pagina interessata. Se l'utente è un visitatore abituale, non impiegherà più di qualche secondo per individuare quello che gli interessa e cliccare. Se invece l'utente avrà bisogno di più tempo perchè ad esempio non conosce il sito web in questione e vorrà leggere con più attenzione i singoli contenuti, è logico attendersi che Apache sia in grado di liberare liberare quel processo per soddisfare le richieste ad esempio di un secondo utente.

Come si può intuire quindi MaxClients, MaxKeepAliveRequests e KeepAliveTimeout sono tutti parametri tra loro strettamente connessi.

A questo punto si potrebbe pensare che impostare MaxClients ad un valore molto possa essere la soluzione migliore. Ad esempio, MaxClients 500 potrebbe voler dire che il nostro server può accettare e quindi soddisfare qualcosa come 500 utenti simultanei. Ed in effetti teoricamente è vero, ma c'è un piccolo problema, ovvero ogni processo Apache occupa una certa quantità di memoria, quindi ad un certo punto, se la memoria fisica del nostro server si esaurisce, il server inizia a "swappare" ovvero ad utilizzare la memoria di swap su disco e questa è una cosa che nel migliore dei casi porta ad un notevole rallentamento del sistema, ma sovente anche ad un blocco del server. L'obbiettivo è quindi quello di far in modo che il server non effettui mai lo swap, e di regolare MaxClients di conseguenza. Questo risulta interessante sia quando necessario dimensionare (in termini di RAM) un server in modo che possa soddisfare in modo accettabile un determinato numero di connessioni simultanee, sia quando si tratta di ottimizzare un server con una quantità di RAM già assegnata.

Per conoscere quanta memoria viene occupata mediamente sul nostro server dal singolo processo Apache, possiamo utilizzare il comando htop e controllare la colonna "RES":

htop Apache processes

Come si vede nell'immagine qui sopra, si può ipotizzare che mediamente su questo specifico server (attenzione che il valore varia da server a server in base alla configurazione del server stesso) il singolo processo Apache richieda qualcosa come 30000 byte di memoria. Questo significa che se si tratta di un server con 1024MB di Ram, di cui il 50% vogliamo sia dedicata ad Apache, avremmo qualcosa come 1024 * 1024 * 0,5 = 524288 kilobyte di memoria disponbile, ovvero  un numero massimo teorico di 524288 / 30000 = 17,4763. Quindi appena 17 processi Apache simultanei, un valore che potrebbe risultare a dir poco sorprendente considerando che spesso i valori di default sono superiori a 100. Si noti comunque che in genere all'aumentare del numero di processi Apache attivi, diminuisce la memoria utilizzata dal singolo processo, quindi 30000 potrebbe essere in realtà un valore eccessivamente alto per singolo processo. E' molto probabile che con 50-60 processi attivi, la memoria occupata dal singolo processo sia dell'ordine dei 20000 kilobyte. In ogni caso quanto appena individuato dovrebbe essere considerato solo un riferimento da utilizzare come punto di partenza da cui poi effettuare dei test pratici, dai quali non è possibile prescindere.

Perché dedicare solo il 50% della memoria totale ad Apache? Perché è necessario tenere in considerazione che nel server non vi è in esecuzione solo Apache, ma una parte della memoria sarà occupata dal sistema operativo stesso, un'altra parte sarà dedicata ad altri servizi, come ad esempio ad un database server quale MySQL, quindi non sarà possibile assegnare il 100% della memoria disponibile ad Apache; anche nel caso di una macchina dedicata al solo server web Apache sarà necessario prendere in considerazione che una parte della memoria sarà occupata dal sistema operativo.

Non è detto comunque che assegnare il 50% della memoria totale della macchina ad Apache sia un parametro corretto in tutte le occasioni. Il valore ottimale varia da server a server, e dovrebbe tenere in considerazione il tipo di applicazioni che sono installate sul server, come ad esempio la complessità e dimensione dei database stessi, se si tratta per lo più di applicazioni statiche o dinamiche, quali servizi sono installati e quali ottimizzazioni attive (ad esempio quali meccanismi di cache), ecc…

MaxClients, MaxKeepAliveRequests, KeepAliveTimeout non sono gli unici parametri di configurazione di Apache sui quali è possibile intervenire, tuttavia sono quelli che più di altri possono fare la differenza. Nella macchina del test, ho impostato anche MaxRequestsPerChild 3000 rispetto al valore predefinito pari a 0 (infinito).

Con queste premesse, vediamo come sia possibile ottimizzare il server Apache, effettuando dei test pratici. Il server in esame è un server Ubuntu, i parametri di default per il valori di cui sopra erano i seguenti:

  • MaxClients  150
  • MaxKeepAliveRequests  100
  • KeepAliveTimeout  15

Per comodità nei miei test ho utilizzato il servizio web Load Impact (www.loadimpact.com). Un risultato ottimale dovrebbre presentare un andamento lineare del grafici. Un server che scala linearmente e non esponenzialmente all'aumentare delle connessioni è senza dubbio un risultato più che desiderabile. Il server testato era un server a basso carico, per il quale difficilmente si sarebbero superate le 70 connessioni simultanee, quindi per ridurre i tempi del test mi sono limitato ad un numero massimo di connessioni pari a 70. E' chiaro che per siti / server ad alto traffico, sarà necessario dimensionare i test in modo che questi rispecchino le condizioni di carico reali.

Dato che si sta testando il comportamento specifico di Apache, sarà opportuno disattivare sistemi di cache quali Varnish o Squid. Inoltre, meglio riavviare Apache prima di ogni test, ed eventualmente ripetere il singolo test almeno un paio di volte.

Vediamo i risultati ottenuti. In Ubuntu Linux, i parametri di configurazione vanno modificati in /etc/apache2/apache.conf. Al termine di ogni modifica al file di configurazione è necessario riavviare il server web, con il comando /etc/init.d/apache2 restart.

TEST 1

<IfModule mpm_prefork_module>
    StartServers          10
    MinSpareServers       5
    MaxSpareServers       10
    MaxClients            33
    MaxRequestsPerChild   3000
</IfModule>


MaxKeepAliveRequests 100
KeepAliveTimeout 6

LoadImpact Apache config test 1 result

Il primo test è stato effettuato con un valore volutamente basso di MaxClients, onde verificarne l'impatto sulle prestazioni del server. Come si può vedere, il collo di bottiglia è proprio il numero limitato di connessioni simultanee possibili; già con 20 clients i tempi medi di attesa per il caricamento delle pagine diventano inaccettabili superando i 6 secondi, ed in effetti la banda utilizzata rimane pressochè costante oltre le 20 connessioni, indice del fatto che il numero di richieste soddisfatte al secondo non varia.

TEST2

<IfModule mpm_prefork_module>
    StartServers          10
    MinSpareServers       5
    MaxSpareServers       10
    MaxClients            75
    MaxRequestsPerChild   3000
</IfModule>

MaxKeepAliveRequests 100
KeepAliveTimeout 6

LoadImpact Apache config test 2 result

Il server si comporta egregiamente fino a 40 clients, poi ancora sembra il numero limitato di clients simultanei il collo di bottiglia. Nonostante MaxClients sia impostato a 75, già con 50 connessioni simultanee si ottiene un decremento prestazionale. Poichè la quantità di banda utilizzata rimane costante tra 40 e 50 connessioni simultanee, è probabile che ancora il MaxClients sia troppo basso.

TEST 3

<IfModule mpm_prefork_module>
    StartServers          10
    MinSpareServers       5
    MaxSpareServers       10
    MaxClients            90
    MaxRequestsPerChild   3000
</IfModule>

MaxKeepAliveRequests 100
KeepAliveTimeout 6

LoadImpact Apache config test 3 result

In questo test ho incrementato MaxClients a 90. Per quanto riguarda i risultati del test, fino a 50 richieste simultanee il server scala linearmente, un buon risultato quindi. Con il test successivo aumenterò il carico sul sistema fino a 70 richieste simultanee, il carico massimo cui desideriamo il nostro server sia in grado di rispondere in modo ottimale.

TEST 4

<IfModule mpm_prefork_module>
    StartServers          10
    MinSpareServers       5
    MaxSpareServers       10
    MaxClients            90
    MaxRequestsPerChild   3000
</IfModule>

MaxKeepAliveRequests 100
KeepAliveTimeout 4

LoadImpact Apache config test 4 result

Si noti che rispetto al Test 3, KeepAliveTimeout è stato ridotto a 4. Il sito si comporta egregiamente fino a circa 60 richeiste simultanee, dopo di che, analizzando la banda utilizzata, pare sia ancora il MaxClient il collo di bottiglia.

TEST 5

<IfModule mpm_prefork_module>
    StartServers          10
    MinSpareServers       5
    MaxSpareServers       10
    MaxClients            100
    MaxRequestsPerChild   3000
</IfModule>

MaxKeepAliveRequests 100
KeepAliveTimeout 6

LoadImpact Apache config test 5 result

In questo test MaxClients è stato incrementato a 100. Sembra tuttavia esserci un problema attorno a 55 - 60 richieste simultanee. La banda utilizzata da 60 a 70 clients (grafico linea rossa) rimane pressochè invariata, mentre i tempi di risposta crescono in modo esponenziale a partire da 50 clients. Evidentemente qualcosa non va. In effetti, analizzando il carico sul sitema durante l'esecuzione del test, ho potuto verificare che ad un certo punto la memoria di sistema si esauriva e veniva utilizzato il file di swap, causa del calo prestazionale evidenziato:

[filefield-description]

TEST 6

<IfModule mpm_prefork_module>
    StartServers          10
    MinSpareServers       5
    MaxSpareServers       10
    MaxClients            100
    MaxRequestsPerChild   3000
</IfModule>

MaxKeepAliveRequests 200
KeepAliveTimeout 4

LoadImpact Apache config test 6 result

In questo testo, invece di decrementare MaxClients, ho modificato MaxKeepAliveRequests, incrementato a 200, onde permettere a ciascun processo Apache di soddisfare un maggior numero di richieste, e decrementato KeepAliveTimeout a 4 secondi. Come è possibile verificare dal risultato dei grafici, il sistema ora scala linearmente fino a 70 clients simultanei, ed è quindi in grado di sopportare in modo ottimale il carico massimo che avevamo ipotizzato all'inizio della procedura di configurazione.

Considerazioni

Ottimizzare Apache è una procedura che richiede una certa esperienza, nonchè una buona dose di pazienza. Non è possibile definire dei valori che possano andar bene in tutte le situazioni, perché molti sono i fattori che entrano in gioco, a cominciare dal tipo di servizio / sito / applicazione web installata sul server e che desideriamo ottimizzare.

Un buon punto di partenza è individuare la RAM disponibile da assegnare ad Apache, il consumo di RAM per singolo processo e partire da questi parametri per effettuare dei test pratici sul server.

E' comunque interessante notare come si possano ottenere dei notevoli incrementi prestazionali solo agendo sulla configurazione di Apache, ed in particolare su MaxClients, MaxKeepAliveRequests e KeepAliveTimeout. Prima quindi di andare ad utilizzare soluzioni più complesse di ottimizzazione, è bene quindi assicurarsi di aver opportunamente configurato Apache.