Optimiser l'efficacité de la compression avec des dictionnaires partagés

La compression des données est une technique éprouvée d'optimisation des performances qui réduit la taille des ressources de page éligibles. Pendant quelque temps, il était courant d'utiliser principalement gzip sur les serveurs Web pour compresser les ressources textuelles courantes telles que les fichiers HTML, CSS et JavaScript, puis les envoyer au client afin qu'elles puissent être décompressées. Cela permet de réduire le temps de chargement des ressources sans affecter le comportement souhaité de la page.

Bien que gzip soit, en soi, très efficace, d'autres améliorations ont été apportées à la compression sur le Web ces dernières années. En 2016, l'algorithme Brotli a été intégré à Chrome, offrant globalement de meilleurs taux de compression pour les ressources éligibles. Fin 2017, tous les navigateurs récents étaient compatibles avec Brotli, et sa compatibilité avec les serveurs s'est généralisée. Plus récemment, Chrome a intégré la compression ZStandard.

Mais le travail ne s'arrête pas là ! L'équipe Chrome s'efforce de rendre utilisables sur le Web les dictionnaires partagés, qui sont désormais disponibles en phase d'évaluation pour Brotli et ZStandard. Les dictionnaires partagés peuvent compléter la compression Brotli et ZStandard afin d'offrir des taux de compression nettement plus élevés pour les sites Web qui envoient fréquemment du code mis à jour. Dans certains cas, ils peuvent offrir des taux de compression supérieurs ou égaux à 90%. Cet article explique plus en détail le fonctionnement des dictionnaires partagés et explique comment vous inscrire aux phases d'évaluation afin de les utiliser pour Brotli et ZStandard sur votre site Web.

Explication des dictionnaires partagés

La compression est un processus qui consiste à rechercher des séquences redondantes dans une entrée et à utiliser ces informations pour créer une sortie bien plus petite, qui peut être inversée par la suite. La compression fonctionne bien sur le Web, car elle réduit considérablement les temps de chargement des ressources. Brotli et ZStandard peuvent tous deux accroître leur efficacité en utilisant un dictionnaire de compression, un ensemble de modèles supplémentaires que ces algorithmes peuvent utiliser lors de la compression. En fait, l'efficacité élevée de Brotli est obtenue dans une certaine mesure à l'aide d'un dictionnaire interne.

Toutefois, des dictionnaires personnalisés sélectionnés par l'utilisateur peuvent être utilisés avec Brotli et ZStandard, qui contiennent des schémas propres à certaines ressources. En pratique, un dictionnaire personnalisé est un fichier externe qui peut être appliqué à n'importe quelle entrée. Les dictionnaires peuvent être très spécifiques au code de production d'une application, voire n'importe quel contenu. L'application d'un dictionnaire donné à son entrée peut avoir un impact important sur l'efficacité globale de la compression. Les dictionnaires très semblables au contenu d'une entrée généreront des sorties avec des taux de compression plus élevés que les dictionnaires avec des contenus génériques ou différents.

Voici un exemple d'efficacité d'un dictionnaire de compression personnalisé: supposons que votre site Web utilise le framework Angular et que la version que vous utilisez actuellement est la version 1.7.9. Cette version du framework Angular coûte environ 172 Kio non compressée. Une fois compressé avec les paramètres par défaut de Brotli, sa taille devient d'environ 53 Kio. Cela permet d'obtenir un taux de compression de près de 70 %. Imaginons toutefois que vous décidiez de passer à Angular 1.8.3 ultérieurement. Cette version d'Angular ayant à peu près la même taille que la version 1.7.9, vous pouvez vous attendre à un taux de compression à peu près identique à celui de la version précédente.

C'est là qu'un dictionnaire personnalisé peut s'avérer utile en utilisant un processus appelé compression delta , qui consiste à utiliser un dictionnaire d'une version précédente d'une ressource pour compresser une version ultérieure. Dans l'exemple précédent, si vous avez compressé la version 1.8.3 d'Angular en utilisant la version 1.7.9 comme dictionnaire, le résultat serait d'un peu plus de 4 Kio. Cela représente un taux de compression de près de 98%. À l'évidence, les dictionnaires de compression peuvent avoir un impact important sur les performances de chargement, et leur efficacité a déjà été démontrée dans des applications réelles.

Cependant, faire fonctionner cette procédure sur le Web présente un défi. L'avantage est que si vous utilisez un dictionnaire pour compresser une ressource, vous avez besoin de ce même dictionnaire pour la décompresser. Ce flux a déjà été tenté sur le Web (à savoir SDCH), mais il a été difficile à implémenter en toute sécurité. Cette dernière proposition de compression par dictionnaire partagé répond à ces préoccupations tout en offrant un avantage considérable pour les ressources statiques et dynamiques.

Comment Chrome annonce la prise en charge des dictionnaires partagés

Tous les navigateurs affichent les algorithmes de compression compatibles via l'en-tête de requête Accept-Encoding. Le contenu de l'en-tête est une liste d'encodages compatibles, séparés par une virgule:

Accept-Encoding: gzip, br, zstd

Cet en-tête Accept-Encoding spécifique indique que le navigateur qui demande la ressource prend en charge les algorithmes de compression gzip, Brotli et ZStandard. Un serveur Web qui répond à la requête peut alors décider de l'algorithme à utiliser pour y répondre.

Lorsque la compatibilité avec le dictionnaire partagé est activée et qu'un dictionnaire pertinent est disponible pour une ressource, des jetons supplémentaires sont ajoutés à l'en-tête Accept-Encoding. Ces jetons sont br-d pour Brotli et zstd-d pour Zstandard. Chrome inclut également le hachage d'un dictionnaire disponible, qui est abordé plus loin.

Accept-Encoding: gzip, br, zstd, br-d, zstd-d
Available-Dictionary: :pZGm1Av0IEBKARczz7exkNYsZb8LzaMrV7J32a2fFG4=:

Si un serveur Web est configuré pour reconnaître ce jeton et qu'il reconnaît le dictionnaire, il peut répondre à cette requête avec une ressource qui a été compressée à l'aide du dictionnaire pour l'encodage applicable. En pratique, la méthode employée varie selon que la requête concerne une ressource statique ou dynamique.

Compression de dictionnaire partagé pour les ressources statiques

Une ressource de page statique est une ressource qui produit toujours la même réponse pour une URL demandée. Les fichiers JavaScript et CSS sont des exemples courants de ressources de page statique compressibles. Ces ressources sont généralement gérées par version à des fins de mise en cache, parfois par hachage du contenu du fichier dans le nom de fichier (par exemple, styles.abcd1234.css) ou via une autre méthode d'empreinte numérique de la ressource. Ces types de ressources se prêtent parfaitement à la compression delta fournie par les dictionnaires partagés, car les ressources statiques sont souvent mises en cache pendant de longues périodes et sont généralement mises à jour à une certaine fréquence.

Vous pouvez spécifier un dictionnaire pour une ressource statique en définissant l'en-tête de réponse Use-As-Dictionary pour celle-ci. L'en-tête utilise l'une des quelques paires clé/valeur, mais la seule obligatoire est match, qui accepte la syntaxe URLPattern spécifiant le chemin d'accès à la ressource où le dictionnaire doit être utilisé:

Use-As-Dictionary: match="/dist/styles.*.css"

Considérez l'en-tête Use-As-Dictionary comme un mécanisme qui s'applique aux versions futures d'une ressource correspondant au modèle spécifié. Imaginons que votre site Web propose tous ses styles dans un seul fichier CSS. Par souci de simplicité, supposons que la première version de cette ressource se trouve dans /dist/styles.v1.css et qu'elle soit envoyée avec un en-tête de réponse Use-As-Dictionary contenant une valeur match de /dist/styles.*.css.

Après un certain temps, vous mettez à jour le CSS de votre site Web et envoyez une nouvelle version, disponible à l'adresse /dist/styles.v2.css. Étant donné que la valeur match utilisée dans l'en-tête de réponse Use-As-Dictionary de la version précédente s'applique à cette requête, le navigateur enverra un en-tête Available-Dictionary contenant un hachage du dictionnaire encodé sous la forme d'une séquence d'octets de champ structuré:

Accept-Encoding: gzip, br, zstd, br-d, zstd-d
Available-Dictionary: :pZGm1Av0IEBKARczz7exkNYsZb8LzaMrV7J32a2fFG4=:

À ce stade, il appartient au serveur de configurer la compression de son côté pour s'assurer que le dictionnaire correspondant est utilisé. La ressource compressée avec ce dictionnaire est alors envoyée, et le dictionnaire disponible dans le cache du navigateur de l'utilisateur est utilisé pour le décompresser.

Si vous publiez souvent de nouveaux codes pour votre site Web, la compression delta peut être très efficace. Cependant, le processus est flexible. Si le navigateur ne détermine pas qu'un dictionnaire est disponible dans le cache du navigateur de l'utilisateur, il ne spécifie pas les jetons br-d ou zstd-d supplémentaires dans l'en-tête Accept-Encoding. Dans ce cas, le flux de compression standard s'applique.

Compression de dictionnaire partagé pour les ressources dynamiques

Les ressources dynamiques peuvent également bénéficier de la compression de dictionnaires partagés. Les ressources dynamiques sont celles qui changent en fonction du contexte, comme un site Web d'actualités où la page principale est fréquemment mise à jour sous forme de coupures publicitaires. Les documents HTML sont souvent des ressources dynamiques. Dans ce cas, le dictionnaire peut contenir la majeure partie de la structure HTML courante du site et du code modèle menant à des pages compressées, où seules les parties uniques de chaque page sont envoyées.

En raison de la nature des ressources générées dynamiquement, un dictionnaire doit être chargé sur le client pour une utilisation ultérieure. Le chargement d'un dictionnaire à l'avance signifie que l'application d'une compression de dictionnaire partagé aux ressources dynamiques est spéculative. L'espoir dans ce cas est que votre site Web reçoive suffisamment de trafic pour que le coût du dictionnaire puisse être amorti sur un grand nombre de navigations. Si vous décidez de l'essayer, la première étape consiste à spécifier l'emplacement du dictionnaire au moyen d'un élément <link> dans le code HTML de la page:

<link rel="dictionary" href="/dictionary.dat">

Lorsque Chrome rencontre cet élément <link>, il peut extraire le dictionnaire une fois que la page est inactive, et avec une faible priorité pour éviter les conflits de bande passante. La réponse du dictionnaire proprement dit doit spécifier un en-tête Use-As-Dictionary et préciser le chemin d'accès aux ressources dynamiques auquel elle s'applique:

Use-As-Dictionary: match="/product/*"

À partir de là, le flux est en grande partie le même que pour les ressources statiques. Le navigateur verra que le dictionnaire lui-même s'applique aux ressources correspondantes et associe un en-tête Available-Dictionary à la requête avec un hachage du contenu du dictionnaire, de la même manière que pour le flux de ressources statiques expliqué précédemment.

Compresser les ressources statiques au moment de la compilation

Si vous connaissez les bundles, vous connaissez peut-être leurs différents plug-ins qui permettent de compresser des ressources au moment de la compilation, puis de les diffuser. Par exemple, Apache vous permet d'utiliser des directives pour diffuser ces ressources précompressées au moment de la requête.

La plupart des bundlers basés sur Node.js qui acceptent la compression utilisent la bibliothèque Zlib intégrée de Node.js. Zlib prend en charge Brotli et les bundlers qui l'utilisent proposent généralement une interface permettant de transmettre des options directement à Zlib, qui prend en charge la compression assistée par dictionnaire. Voici quelques bundles qui prennent en charge l'utilisation de dictionnaires:

Notez que les dictionnaires disponibles pour une version donnée d'une ressource peuvent utiliser l'une des versions précédentes d'une ressource. Vous devez donc analyser le trafic utilisateur et planifier votre stratégie en conséquence. Essayez de trouver un équilibre et générez des ressources qui profitent au maximum d'utilisateurs connus. Les fournisseurs CDN testent actuellement la compression de dictionnaires partagés. Aucune implémentation n'est encore disponible pour un usage public, mais cela devrait changer.

Essayez-la

L'intégration de la compression par dictionnaire partagé avec les fonctionnalités de compression existantes du navigateur peut améliorer sensiblement les performances de chargement des sites Web qui affichent fréquemment du code de production mis à jour et reçoivent un trafic important de la part de visiteurs connus. Si vous souhaitez effectuer une compression de dictionnaire partagée, deux possibilités s'offrent à vous:

  1. Si vous souhaitez tester vous-même la compression par dictionnaire partagé pour comprendre son fonctionnement, vous pouvez activer la fonctionnalité expérimentale Transport par dictionnaire par compression sur la page chrome://flags.
  2. Si vous souhaitez tester cette fonctionnalité sur votre site Web de production et découvrir comment la compression de dictionnaire partagée pourrait être bénéfique aux utilisateurs réels, inscrivez-vous à la phase d'évaluation afin d'obtenir un jeton, puis renseignez-vous sur le fonctionnement des phases d'évaluation.

Conclusion

Nous nous réjouissons de cette avancée majeure dans le domaine de la technologie de compression sur le Web et de l'accélération de la création d'applications existantes que les gens utilisent tous les jours. Nous vous encourageons à l'essayer et surtout, n'hésitez pas à nous donner votre avis ! Si vous trouvez un bug, signalez-le sur crbug.com. Pour accéder à des ressources et à des outils supplémentaires, rendez-vous sur use-as-dictionary.com. Enfin, si vous souhaitez en savoir plus sur son fonctionnement, consultez cet article explicatif.