La storia di ciò che è stato spedito, di come è stato misurato l'impatto e dei compromessi che sono stati fatti.
Data di pubblicazione: 20 giugno 2019
Cerca quasi qualsiasi argomento su Google e ti verrà presentata una pagina di risultati significativi e pertinenti immediatamente riconoscibile. Quello che probabilmente non sai è che questa pagina dei risultati di ricerca, in determinati scenari, viene pubblicata da una potente tecnologia web chiamata service worker.
L'implementazione del supporto dei service worker per la Ricerca Google senza influire negativamente sulle prestazioni ha richiesto il lavoro di decine di ingegneri in più team. Questa è la storia di ciò che è stato spedito, di come è stato misurato il rendimento e di quali compromessi sono stati fatti.
Motivi principali per esplorare i service worker
L'aggiunta di un service worker a un'app web, come qualsiasi modifica all'architettura del tuo sito, deve essere eseguita con un insieme chiaro di obiettivi in mente. Per il team della Ricerca Google, l'aggiunta di un service worker valeva la pena di essere esplorata per alcuni motivi chiave.
Memorizzazione nella cache limitata dei risultati di ricerca
Il team della Ricerca Google ha scoperto che è comune che gli utenti cerchino gli stessi termini più di una volta in un breve periodo di tempo. Anziché attivare una nuova richiesta di backend solo per ottenere quelli che probabilmente saranno gli stessi risultati, il team di ricerca voleva sfruttare la memorizzazione nella cache e soddisfare localmente le richieste ripetute.
L'importanza della freschezza non può essere sottovalutata e a volte gli utenti cercano ripetutamente gli stessi termini perché si tratta di un argomento in evoluzione e si aspettano di vedere risultati aggiornati. L'utilizzo di un service worker consente al team della Ricerca di implementare una logica granulare per controllare la durata dei risultati di ricerca memorizzati nella cache locale e ottenere l'equilibrio esatto tra velocità e freschezza che ritiene più utile per gli utenti.
Esperienza offline significativa
Inoltre, il team della Ricerca Google voleva offrire un'esperienza offline significativa. Quando un utente vuole saperne di più su un argomento, vuole andare direttamente alla pagina della Ricerca Google e iniziare a cercare, senza preoccuparsi di una connessione a internet attiva.
Senza un service worker, visitare la pagina di ricerca Google in modalità offline porterebbe semplicemente alla pagina di errore di rete standard del browser e gli utenti dovrebbero ricordarsi di tornare e riprovare una volta ripristinata la connessione. Con un service worker, è possibile pubblicare una risposta HTML offline personalizzata e consentire agli utenti di inserire immediatamente la query di ricerca.

I risultati non saranno disponibili finché non sarà presente una connessione a internet, ma il service worker consente di posticipare la ricerca e inviarla ai server di Google non appena il dispositivo torna online utilizzando l'API Background Sync.
Memorizzazione nella cache e pubblicazione di JavaScript più intelligenti
Un altro motivo era ottimizzare la memorizzazione nella cache e il caricamento del codice JavaScript modulare che alimenta i vari tipi di funzionalità nella pagina dei risultati di ricerca. Il bundling JavaScript offre una serie di vantaggi che hanno senso quando non è coinvolto alcun service worker, quindi il team della Ricerca non voleva semplicemente interrompere completamente il bundling.
Utilizzando la capacità di un service worker di versionare e memorizzare nella cache blocchi granulari di JavaScript in fase di runtime, il team di ricerca sospettava di poter ridurre la quantità di churn della cache e garantire che il JavaScript riutilizzato in futuro possa essere memorizzato nella cache in modo efficiente. La logica all'interno del service worker può analizzare una richiesta HTTP in uscita per un bundle che contiene più moduli JavaScript e soddisfarla ricomponendo più moduli memorizzati nella cache locale, "spacchettando" il bundle quando possibile. In questo modo si risparmia larghezza di banda e si migliora la reattività complessiva.
L'utilizzo di JavaScript memorizzato nella cache e gestito da un service worker offre anche vantaggi in termini di prestazioni: in Chrome, viene memorizzata e riutilizzata una rappresentazione analizzata e in bytecode di JavaScript, il che riduce il lavoro da svolgere in fase di runtime per eseguire JavaScript sulla pagina.
Sfide e soluzioni
Ecco alcuni degli ostacoli che è stato necessario superare per raggiungere gli obiettivi dichiarati del team. Sebbene alcune di queste sfide siano specifiche della Ricerca Google, molte sono applicabili a un'ampia gamma di siti che potrebbero prendere in considerazione l'implementazione di un service worker.
Problema: overhead del service worker
La sfida più grande, e l'unico vero ostacolo al lancio di un service worker sulla Ricerca Google, era garantire che non facesse nulla che potesse aumentare la latenza percepita dagli utenti. La Ricerca Google prende molto sul serio le prestazioni e in passato ha bloccato il lancio di nuove funzionalità se contribuivano anche solo a decine di millisecondi di latenza aggiuntiva per una determinata popolazione di utenti.
Quando il team ha iniziato a raccogliere i dati sul rendimento durante i primi esperimenti, è diventato evidente che ci sarebbe stato un problema. L'HTML restituito in risposta alle richieste di navigazione per la pagina dei risultati di ricerca è dinamico e varia notevolmente a seconda della logica che deve essere eseguita sui server web di Ricerca. Al momento non è possibile per il service worker replicare questa logica e restituire immediatamente l'HTML memorizzato nella cache. La cosa migliore che può fare è trasmettere le richieste di navigazione ai server web di backend, il che richiede una richiesta di rete.
Senza un service worker, questa richiesta di rete viene eseguita immediatamente dopo la navigazione dell'utente. Quando viene registrato un service worker, deve sempre essere avviato
e deve avere la possibilità di eseguire i suoi
gestori di eventi fetch,
anche quando non c'è alcuna possibilità che questi gestori di recupero facciano altro che andare
alla rete. Il tempo necessario per avviare ed eseguire il codice del service worker
è un overhead puro aggiunto a ogni navigazione:

Ciò comporta uno svantaggio di latenza troppo elevato per giustificare altri vantaggi. Inoltre, il team ha scoperto che, in base alla misurazione dei tempi di avvio dei service worker su dispositivi reali, si è verificata una distribuzione ampia dei tempi di avvio, con alcuni dispositivi mobili di fascia bassa che impiegano quasi lo stesso tempo per avviare il service worker di quello necessario per effettuare la richiesta di rete per l'HTML della pagina dei risultati.
Soluzione: utilizza il precaricamento della navigazione
La funzionalità singola e più importante che ha consentito al team della Ricerca Google di procedere con il lancio del service worker è il precaricamento della navigazione. L'utilizzo del precaricamento della navigazione è un vantaggio fondamentale in termini di prestazioni per qualsiasi service worker che deve utilizzare una risposta dalla rete per soddisfare le richieste di navigazione. Fornisce un suggerimento al browser per iniziare a effettuare immediatamente la richiesta di navigazione, contemporaneamente all'avvio del service worker:

Se il tempo necessario per l'avvio del service worker è inferiore al tempo necessario per ricevere una risposta dalla rete, non dovrebbe esserci alcun overhead di latenza introdotto dal service worker.
Il team di ricerca doveva anche evitare di utilizzare un service worker su dispositivi mobili di fascia bassa, dove il tempo di avvio del service worker poteva superare la richiesta di navigazione. Poiché non esiste una regola rigida per definire un dispositivo "di fascia bassa ", hanno ideato l'euristica di controllo della RAM totale installata sul dispositivo. Qualsiasi valore inferiore a 2 gigabyte di memoria rientrava nella categoria di dispositivi di fascia bassa, in cui il tempo di avvio del service worker sarebbe inaccettabile.
Un altro aspetto da considerare è lo spazio di archiviazione disponibile, poiché l'intero insieme di
risorse da memorizzare nella cache per un uso futuro può occupare diversi megabyte. L'interfaccia
navigator.storage
consente alla pagina di ricerca Google di capire in anticipo se i tentativi di
memorizzare nella cache i dati rischiano di non riuscire a causa di errori della quota di spazio di archiviazione.
Il team di ricerca ha quindi a disposizione più criteri da utilizzare per determinare se utilizzare o meno un service worker: se un utente accede alla pagina della Ricerca Google utilizzando un browser che supporta il precaricamento della navigazione e dispone di almeno 2 gigabyte di RAM e di spazio di archiviazione senza costi sufficiente, viene registrato un service worker. I browser o i dispositivi che non soddisfano questi criteri non avranno un service worker, ma continueranno a visualizzare la stessa esperienza di Ricerca Google di sempre.
Un vantaggio secondario di questa registrazione selettiva è la possibilità di spedire un service worker più piccolo ed efficiente. Il targeting dei browser abbastanza moderni per eseguire il codice del service worker elimina l'overhead della transpilazione e dei polyfill per i browser meno recenti. In questo modo sono stati eliminati circa 8 kilobyte di codice JavaScript non compresso dalle dimensioni totali dell'implementazione del service worker.
Problema: ambiti dei service worker
Una volta che il team di ricerca ha eseguito un numero sufficiente di esperimenti sulla latenza ed era sicuro che l'utilizzo del precaricamento della navigazione offrisse un percorso praticabile e neutro in termini di latenza per l'utilizzo di un service worker, alcuni problemi pratici hanno iniziato a passare in primo piano. Uno di questi problemi riguarda le regole di ambito del service worker. L'ambito di un service worker determina le pagine che può potenzialmente controllare.
L'ambito funziona in base al prefisso del percorso URL. Per i domini che ospitano una singola app web, questo non è un problema, in quanto in genere si utilizza un service worker con l'ambito massimo di /, che potrebbe assumere il controllo di qualsiasi pagina del dominio.
Tuttavia, la struttura degli URL della Ricerca Google è un po' più complicata.
Se al service worker venisse assegnato l'ambito massimo di /, finirebbe per
poter controllare qualsiasi pagina ospitata in www.google.com (o l'equivalente regionale), e ci sono URL in quel dominio che non hanno nulla a che fare con
la Ricerca Google. Un ambito più ragionevole e restrittivo sarebbe /search, che almeno eliminerebbe gli URL completamente non correlati ai risultati di ricerca.
Purtroppo, anche questo percorso URL /search è condiviso tra le diverse varianti
dei risultati della Ricerca Google, con i parametri di query URL che determinano il tipo specifico
di risultato di ricerca mostrato. Alcune di queste versioni utilizzano codebase completamente diverse
rispetto alla tradizionale pagina dei risultati di ricerca web. Ad esempio, la ricerca immagini e la ricerca Shopping vengono entrambe pubblicate nel percorso dell'URL /search con parametri di ricerca diversi, ma nessuna delle due interfacce era pronta per la spedizione della propria esperienza con i service worker (ancora).
Soluzione: crea un framework di invio e routing
Sebbene esistano alcune proposte che consentono di utilizzare qualcosa di più potente dei prefissi del percorso URL per determinare gli ambiti dei service worker, il team della Ricerca Google non riusciva a implementare un service worker che non faceva nulla per un sottoinsieme di pagine che controllava.
Per risolvere questo problema, il team di Ricerca Google ha creato un framework di invio e routing personalizzato che poteva essere configurato per verificare criteri come i parametri di query della pagina client e utilizzarli per determinare quale percorso di codice specifico seguire. Anziché regole di codifica rigida, il sistema è stato progettato per essere flessibile e consentire ai team che condividono lo spazio URL, come la ricerca immagini e la ricerca Shopping, di inserire la propria logica del service worker in un secondo momento, se decidono di implementarla.
Problema: risultati e metriche personalizzati
Gli utenti possono accedere alla Ricerca Google utilizzando i propri Account Google e la loro esperienza con i risultati di ricerca può essere personalizzata in base ai dati specifici dell'account. Gli utenti che hanno eseguito l'accesso vengono identificati da cookie del browser specifici, uno standard consolidato e ampiamente supportato.
Uno svantaggio dell'utilizzo dei cookie del browser, tuttavia, è che non sono esposti all'interno di un service worker e non è possibile esaminare automaticamente i loro valori e assicurarsi che non siano cambiati a causa della disconnessione di un utente o del cambio di account. Sono in corso lavori per portare l'accesso ai cookie nei service worker, ma al momento della stesura di questo documento, l'approccio è sperimentale e non è ampiamente supportato.
Una mancata corrispondenza tra la visualizzazione dell'utente attualmente connesso del service worker e l'utente effettivo connesso all'interfaccia web della Ricerca Google potrebbe comportare risultati di ricerca personalizzati in modo errato o metriche e log attribuiti in modo errato. Uno qualsiasi di questi scenari di errore rappresenterebbe un problema serio per il team di Google Search.
Soluzione: invia i cookie utilizzando postMessage
Anziché attendere il lancio delle API sperimentali e fornire l'accesso diretto ai cookie del browser all'interno di un service worker, il team della Ricerca Google ha optato per una soluzione provvisoria: ogni volta che viene caricata una pagina controllata dal service worker, la pagina legge i cookie pertinenti e utilizza postMessage() per inviarli al service worker.
Il service worker controlla quindi il valore corrente del cookie rispetto al valore previsto e, in caso di mancata corrispondenza, adotta misure per eliminare i dati specifici dell'utente dal proprio spazio di archiviazione e ricarica la pagina dei risultati di ricerca senza alcuna personalizzazione errata.
I passaggi specifici che il service worker esegue per ripristinare le impostazioni di base sono particolari per i requisiti della Ricerca Google, ma lo stesso approccio generale potrebbe essere utile ad altri sviluppatori che gestiscono dati personalizzati basati sui cookie dei browser.
Problema: esperimenti e dinamismo
Come accennato, il team della Ricerca Google si affida molto all'esecuzione di esperimenti in produzione e al test degli effetti di nuovi codici e funzionalità nel mondo reale prima di attivarli per impostazione predefinita. Può essere un po' difficile con un service worker statico che si basa molto sui dati memorizzati nella cache, poiché l'attivazione e la disattivazione degli esperimenti per gli utenti spesso richiedono la comunicazione con il server di backend.
Soluzione: script del service worker generato dinamicamente
La soluzione adottata dal team è stata quella di utilizzare uno script service worker generato dinamicamente, personalizzato dal server web per ogni singolo utente, anziché un singolo script service worker statico generato in anticipo. Le informazioni sugli esperimenti che potrebbero influire sul comportamento del service worker o sulle richieste di rete in generale sono incluse direttamente in questi script del service worker personalizzati. La modifica dei set di esperienze attive per un utente viene eseguita tramite una combinazione di tecniche tradizionali, come i cookie del browser, e la pubblicazione di codice aggiornato nell'URL del service worker registrato.
L'utilizzo di uno script del service worker generato dinamicamente semplifica anche la fornitura di un meccanismo di escape nell'improbabile eventualità che un'implementazione del service worker abbia un bug fatale da evitare. La risposta dinamica del service worker potrebbe essere un'implementazione no-op, che disattiva il service worker per alcuni o tutti gli utenti attuali.
Problema: coordinamento degli aggiornamenti
Una delle sfide più difficili che deve affrontare qualsiasi deployment di service worker nel mondo reale è trovare un compromesso ragionevole tra l'evitare la rete a favore della cache e, allo stesso tempo, garantire che gli utenti esistenti ricevano aggiornamenti e modifiche critici subito dopo il deployment in produzione. Il giusto equilibrio dipende da molti fattori:
- Se la tua app web è un'app a pagina singola di lunga durata che un utente tiene aperta a tempo indeterminato, senza passare a nuove pagine.
- Qual è la cadenza di deployment per gli aggiornamenti del server web di backend.
- Se l'utente medio tollererebbe l'utilizzo di una versione leggermente obsoleta della tua app web o se la priorità assoluta è la freschezza.
Durante la sperimentazione dei service worker, il team della Ricerca Google si è assicurato di mantenere in esecuzione gli esperimenti in una serie di aggiornamenti di backend pianificati, per garantire che le metriche e l'esperienza utente corrispondessero più da vicino a ciò che gli utenti di ritorno avrebbero visto nel mondo reale.
Soluzione: bilanciare l'aggiornamento e l'utilizzo della cache
Dopo aver testato diverse opzioni di configurazione, il team di Ricerca Google ha scoperto che la seguente configurazione offre il giusto equilibrio tra aggiornamento e utilizzo della cache.
L'URL dello script del service worker viene pubblicato con l'intestazione
Cache-Control: private, max-age=1500 (1500 secondi o 25 minuti) di risposta
ed è
registrato con updateViaCache impostato su "all"
per garantire che l'intestazione venga rispettata. Il backend web della Ricerca Google è, come puoi immaginare, un insieme di server di grandi dimensioni distribuiti a livello globale che richiedono un tempo di attività il più vicino possibile al 100%. L'implementazione di una modifica che influirebbe sui contenuti dello script del service worker viene eseguita in modo progressivo.
Se un utente accede a un backend aggiornato e poi passa rapidamente a un'altra pagina che accede a un backend che non ha ancora ricevuto il service worker aggiornato, si ritroverà a passare da una versione all'altra più volte. Pertanto, dire al browser di controllare la presenza di uno script aggiornato solo se sono trascorsi 25 minuti dall'ultimo controllo non ha un impatto negativo significativo. Il vantaggio di attivare questo comportamento è la riduzione significativa del traffico ricevuto dall'endpoint che genera dinamicamente lo script del service worker.
Inoltre, nell'intestazione HTTP della risposta dello script del service worker viene impostata un'intestazione ETag, in modo che, quando viene eseguito un controllo degli aggiornamenti dopo 25 minuti, il server possa rispondere in modo efficiente con una risposta HTTP 304 se nel frattempo non sono stati apportati aggiornamenti al service worker.
Sebbene alcune interazioni all'interno dell'app web Ricerca Google utilizzino navigazioni in stile app a pagina singola (ad es. tramite l'API History), per la maggior parte, la Ricerca Google è un'app web tradizionale che utilizza navigazioni "reali". Ciò entra in gioco quando il team ha deciso che sarebbe stato
efficace utilizzare due opzioni che accelerano il ciclo di vita
dell'aggiornamento del service worker:
clients.claim()
e
skipWaiting().
Fare clic sull'interfaccia di Ricerca Google in genere porta a nuovi documenti HTML. La chiamata skipWaiting garantisce che un service worker aggiornato
abbia la possibilità di gestire le nuove richieste di navigazione immediatamente dopo
l'installazione. Allo stesso modo, la chiamata a clients.claim() significa che il service worker aggiornato ha la possibilità di iniziare a controllare le pagine della Ricerca Google aperte e non controllate dopo l'attivazione del service worker.
L'approccio scelto dalla Ricerca Google non è necessariamente una soluzione adatta a tutti. È il risultato di un test A/B accurato di varie combinazioni di opzioni di pubblicazione fino a quando non è stata trovata quella più adatta.
Gli sviluppatori la cui infrastruttura di backend consente di implementare gli aggiornamenti più
rapidamente potrebbero preferire che il browser controlli la presenza di uno script service worker aggiornato
il più spesso possibile, ignorando sempre la cache HTTP.
Se crei un'app a una sola pagina che gli utenti potrebbero tenere aperta per un lungo periodo di tempo, l'utilizzo di skipWaiting() probabilmente non è la scelta giusta per te, perché rischi di riscontrare incoerenze della cache se consenti l'attivazione del nuovo service worker mentre sono presenti client di lunga durata.
Concetti principali
Per impostazione predefinita, i service worker non sono neutrali in termini di prestazioni
L'aggiunta di un service worker alla tua app web comporta l'inserimento di un ulteriore frammento di JavaScript che deve essere caricato ed eseguito prima che l'app web riceva risposte alle sue richieste. Se queste risposte provengono da una cache locale anziché dalla rete, il sovraccarico di esecuzione del service worker è in genere trascurabile rispetto al miglioramento delle prestazioni ottenuto con l'approccio cache-first. Tuttavia, se sai che il service worker deve sempre consultare la rete quando gestisce le richieste di navigazione, l'utilizzo del precaricamento della navigazione è un vantaggio cruciale in termini di prestazioni.
I service worker sono (ancora!) un miglioramento progressivo
Oggi il supporto dei service worker è molto più promettente di un anno fa. Tutti i browser moderni ora supportano almeno in parte i service worker, ma purtroppo alcune funzionalità avanzate dei service worker, come la sincronizzazione in background e il precaricamento della navigazione, non sono implementate universalmente. Il controllo delle funzionalità per il sottoinsieme specifico di funzionalità di cui sai di aver bisogno e la registrazione di un service worker solo quando sono presenti è comunque un approccio ragionevole.
Allo stesso modo, se hai eseguito esperimenti in natura e sai che i dispositivi di fascia bassa hanno prestazioni scarse con il sovraccarico aggiuntivo di un service worker, puoi astenersi dal registrare un service worker anche in questi scenari.
Devi continuare a trattare i service worker come un miglioramento progressivo che viene aggiunto a un'app web quando vengono soddisfatti tutti i prerequisiti e il service worker aggiunge qualcosa di positivo all'esperienza utente e al caricamento complessivo.
Misura tutto
L'unico modo per capire se la spedizione di un service worker ha avuto un impatto positivo o negativo sull'esperienza dei tuoi utenti è sperimentare e misurare i risultati.
I dettagli della configurazione di misurazioni significative dipendono dal fornitore di analisi che utilizzi e da come conduci normalmente gli esperimenti nella configurazione della distribuzione. Un approccio, che utilizza Google Analytics per raccogliere metriche, è descritto in dettaglio in questo case study basato sull'esperienza di utilizzo dei service worker nell'app web Google I/O.
Non-goals
Sebbene molti membri della community di sviluppo web associno i service worker alle app web progressive, la creazione di una "PWA di Ricerca Google" non era un obiettivo iniziale del team. L'app web Ricerca Google non fornisce metadati in un manifest dell'app web e non incoraggia gli utenti a seguire il flusso Aggiungi alla schermata Home. Il team della rete di ricerca è soddisfatto degli utenti che accedono alla propria app web con i punti di accesso classici per la Ricerca Google.
Anziché cercare di trasformare l'esperienza web della Ricerca Google nell'equivalente di ciò che ti aspetteresti da un'applicazione installata, l'obiettivo della release iniziale era migliorare progressivamente il sito web esistente.
Ringraziamenti
Grazie a tutto il team di sviluppo web della Ricerca Google per il lavoro svolto sull'implementazione del service worker e per aver condiviso il materiale di base che ha portato alla stesura di questo articolo. Un ringraziamento speciale va a Philippe Golle, Rajesh Jagannathan, R. Samuel Klatchko, Andy Martone, Leonardo Peña, Rachel Shearer, Greg Terrono e Clay Woolam.
Aggiornamento (ottobre 2021): da quando è stato pubblicato originariamente questo articolo, il team di Ricerca Google ha rivalutato i vantaggi e i compromessi della sua attuale architettura dei service worker. Il service worker descritto sopra verrà ritirato. Man mano che l'infrastruttura web della Ricerca Google si evolve, il team potrebbe rivedere la progettazione del service worker.