HPN #02: le moderne applicazioni web

Di - 12 March 2013 - in
Post image for HPN #02: le moderne applicazioni web

Siamo pronti per un nuovo episodio di questa guida sul miglioramento delle prestazioni delle applicazioni web in Chrome. Di settimana in settimana stiamo aggiungendo un po’ di carne al fuoco, senza rischiare di bruciarla. Con questa puntata cerchiamo di addentrarci sempre più nello specifico di un’applicazione web, forti del bagaglio accumulato finora.

Prima di tutto dobbiamo comunque fare delle considerazioni di carattere generale, in particolare per quanto riguarda le abituali pratiche di oggi. Per fare questo è utile dare uno sguardo ad HTTP Archive, il quale traccia il web (o meglio, i principali siti e applicazioni web) per capire come esso è costruito. In particolare fa un’analisi sulle risorse utilizzate, i tipi di contenuto, gli header e altri metadata. I dati di gennaio 2013 possono essere abbastanza sorprendenti, infatti ogni pagina presenta in media:

  • una dimensione di 1280KB;
  • 88 risorse;
  • connessioni a più di 30 host diversi.

In parole povere, una pagina ha in media più di 1MB di dimensione, è composta da 88 risorse tra immagini, JavaScript e CSS integrati da più di 30 diversi host. La cosa più preoccupante è che questi numeri sono in costante e sempre più rapida crescita: stiamo realizzando applicazioni web sempre più grandi e pesanti.

Richieste di risorse
Qualcuno ora si starà chiedendo perché questo deve preoccuparci: abbiamo a disposizione macchine sempre più capaci e veloci, perché preoccuparsene? Una prima risposta può essere il mobile: in mobilità non abbiamo una connessione veloce e stabile alla rete, quindi caricare grandi quantità di dati per ogni pagina e stabilire un gran numero di connessioni a host diversi può essere un grosso limite. Per una seconda motivazione, forse la più importante, bisogna fare una riflessione più profonda.

Il protocollo TCP che viene utilizzato nel fetch delle risorse è ottimizzato per trasferimenti di file di grandi dimensioni. Riprendendo i dati di HTTP Archive e analizzandoli più approfonditamente notiamo che ogni risorsa ha in media una dimensione di 12KB. Ne deriva che i trasferimenti che facciamo oggi nel web sono piccoli e veloci, esattamente il contrario di quelli per i quali è ottimizzato il protocollo TCP.

Andiamo un po’ più a fondo nell’analisi delle risorse considerando la specifica Navigation Timing del W3C, la quale offre API e visibilità nelle tempistiche e prestazioni dei dati dietro ogni richiesta nel browser. In particolare, vediamo ora i componenti che sono responsabili delle prestazioni delle nostre applicazioni web.

Navigation Timing
Data l’URL di una risorsa nel web, il browser come prima cosa va a guardare la cache locale e quella dell’applicazione. Se è già stata recuperata la risorsa in precedenza e sono stati forniti gli appropriati header (Expires, Cache-Control, ecc), allora probabilmente possiamo utilizzare la cache locale per soddisfare la richiesta. C’è da ricordare, infatti, che la richiesta più veloce è quella non effettuata. Invece, se dobbiamo riconvalidare la risorsa perché è scaduta o semplicemente non l’abbiamo mai vista, dobbiamo stabilire una connessione che, per propria natura, è costosa.

Dato il nome dell’host e il percorso della risorsa, Chrome per prima cosa controlla le connessioni già aperte che può riutilizzare, tramite i socket, che sono composti da {schema, host, porta}. In alternativa, se è stato configurato un proxy o specificato uno script proxy auto-config (PAC), allora il browser controlla le connessioni tramite esso. Gli script PAC offrono diversi proxy basati sull’URL (o altre regole specificate), ognuna delle quali ha il proprio insieme di socket. Infine, se nessuna delle condizioni precedenti è verificata, la richiesta comincia risolvendo l’indirizzo IP, detto anche DNS lookup.

Anche in questo caso possiamo essere fortunati: l’hostname può essere già presente in cache e in questo caso è sufficiente inviare una rapida chiamata di sistema. In caso contrario è necessario inviare una query DNS prima di compiere qualsiasi altra azione. Il tempo richiesto dal DNS lookup può variare molto e dipende dall’internet provider, dalla popolarità del sito web, dalla probabilità che l’hostname si trovi nelle cache intermedie e dal tempo di risposta dei server di quel dominio. Insomma, le variabili sono davvero molte, ma in genere è facile che un DNS lookup richieda diverse centinaia di millisecondi.

Bene, abbiamo finalmente risolto l’indirizzo IP. Ora Chrome può aprire una connessione TCP con la destinazione; questo passo prende il nome di “Three-way handshake” (stretta di mano in tre passaggi). Questi tre passaggi prendono il nome di SYN > SYN-ACK > ACK: nei primi due, tramite il segmento SYN, entrambi gli host comunicano la disponibilità ad aprire la connessione, mentre il terzo serve per permettere anche alla destinazione di effettuare una stima del timeout iniziale. Tralasciando gli aspetti puramente tecnici che sottendono a questo processo, possiamo facilmente dire che esso comporta un pesante ritardo al quale non è possibile sfuggire. Per quantificare, questo ritardo può variare dalle decine alle migliaia di millisecondi, dipende dalla distanza tra il client e l’host e il percorso scelto. Notiamo che abbiamo già accumulato qualche significativo ritardo, ma non abbiamo ancora trasportato nemmeno un byte di dati!

Qui subentra un’altra variabile: la connessione sicura (HTTPS). Completata la stretta di mano TCP, infatti, se la connessione è protetta è necessario effettuare anche l’handshake SSL, che può richiedere due ulteriori cicli di ritardo tra il client e il server. Uno sconto è permesso se la sessione SSL è in cache, nel qual caso il ciclo di ritardo è solamente uno.

Finalmente Chrome è in grado di eseguire la richiesta HTTP (nella figura in alto, siamo al punto requestStart). Una volta ricevuta la richiesta, il server può processarla e inviare i dati al client. Ciò richiede almeno un ciclo di rete più il tempo necessario al server per processare la richiesta. Fatto questo, abbiamo finito… no, non necessariamente. Se la risposta è un redirect HTTP dobbiamo ripetere tutto quanto fatto finora, quindi se avete qualche redirect nelle vostre pagine web il mio consiglio è di rivedere questa decisione.

Vediamo di riassumere un po’ i ritardi visti finora. Lo scenario che prendiamo come esempio è quello tipico di una connessione a banda larga: miss nella cache locale, DNS lookup relativamente rapido, TCP ed SSL handshake e tempo di riposta relativamente rapido da parte del server.

  • 50ms per il DNS;
  • 80ms per il TCP handshake;
  • 160ms per l’SSL handshake (due cicli, ricordate?);
  • 40ms per la richiesta al server;
  • 100ms perché il server processi la richiesta;
  • 40ms per la risposta da parte del server.

In tutto abbiamo accumulato 470 millisecondi per una singola richiesta, che si traduce nell’80% della latenza globale della rete se confrontato con il tempo impiegato dai server per soddisfare le richieste. Tuttavia questa stima è abbastanza ottimistica, perché dobbiamo considerare ancora un paio di variabili:

  • se la risposta del server non si adatta alla finestra di congestione TCP iniziale(4-15KB), vengono introdotti uno o più roundtrip di latenza addizionali;
  • i ritardi SSL possono aumentare ancora di più se è necessario recuperare un certificato mancante o effettuare un controllo dello stato del certificato online (OCSP); in entrambi i casi è necessaria una nuova connessione TCP, che può aggiungere centinaia di millisecondi di latenza.

Finora ci siamo interessati poco del tempo impiegato dal server per processare la richiesta, in fin dei conti risulta essere solamente il 20% del tempo impiegato nel caricamento di una pagina. Ora nasce spontanea una domanda: è realmente importante quanto visto in questa lezione? In fin dei conti abbiamo constatato che con la disponibilità di una rete a banda larga non dobbiamo far altro che attendere mezzo secondo per recuperare le risorse. In realtà queste considerazioni non sono solamente importanti, ma cruciali.

Consideriamo alcune ricerche sull’esperienza utente, che considerano ciò che ci aspettiamo da un’applicazione web, sia online che offline, in termini di prontezza di risposta (ritardo – reazione dell’utente):

  • 0-100ms – Istantanea
  • 100-300ms – Ritardo appena percettibile
  • 300-1000ms – La macchina sta lavorando
  • 1s+ – Cambia il contesto mentale
  • 10s+ – Tornerò più tardi

Questi punti ci dicono molte più cose di quante potremmo aspettarci: in primo luogo, per mantenere alto l’interesse degli utenti è necessario renderizzare le pagine o almeno restituire un feedback visivo entro 250ms, cosa non certo facile. Altri studi effettuati da Google, Amazon, Microsoft e altri riportano il ruolo centrale della velocità nell’esperienza dell’utente in rete: siti più veloci hanno più visualizzazioni di pagina e un maggior coinvolgimento degli utenti.

Riprendiamo quindi in mano i dati risultati dai conti fatti in precedenza, ricordando che il nostro budget di latenza ottimale è di 250ms: la combinazione di DNS lookup, TCP e SSL handshake e i tempi per gli scambi con il server ammontano a 370ms; abbiamo già sforato del 50% il nostro budget e non abbiamo considerato il tempo impiegato dal server per processare la richiesta!

Ora abbiamo individuato il problema: la latenza accumulata in un contesto medio è troppo alta. Nelle prossime puntate andremo ad addentrarci nei dettagli dell’implementazione di tutti questi processi in Chrome.

Leave a Reply

Mattia Migliorini Articolo scritto da

Studente di informatica presso l’Università di Padova, web designer, amante di Linux e dell’open source in generale.
Membro di Ubuntu e di 2viLUG, da gennaio 2012 è collaboratore di Engeene.

Contatta l'autore

Previous post:

Next post: