Après un test de votre site sur Yslow vous retrouvez une recommandation qui vous propose de désactiver les ETags. Une recherche rapide vous mène sur les pages de l’équipe performance de Yahoo! qui vous disent la même chose.
Qu’est ce qu’un ETag ?
Un ETag sert au serveur web à identifier une ressource et sa version. La valeur d’un ETag est libre, la seule contrainte est que l’ETag soit unique pour une URL donnée. Deux versions d’un même document auront donc deux ETags différents ; dans le temps la valeur d’un ETag ne devrait pas changer si le document lui même n’évolue pas. Cet ETag est renvoyé par défaut à chaque fois que le serveur web renvoie un contenu. On obtient donc quelque chose comme suit dans les entêtes d’une réponse HTTP 1.1 :
HTTP/1.1 200 OK Date: Sun, 08 Jun 2008 13:53:40 GMT Last-Modified: Mon, 04 May 2008 19:43:54 GMT Etag: "3a4ea77d-baa-f3a45280"
Le navigateur s’en sert pour identifier les versions des documents qu’il met en cache. Quand on lui demandera de nouveau la même ressource, il fournira cet identifiant dans une entête de la requête HTTP. On parle alors d’une requête conditionnelle.
GET /test.html HTTP/1.1 Host: www.example.org If-Modified-Since: Mon, 05 May 2008 19:43:54 GMT If-None-Match: "3a4ea77d-baa-f3a45280"
Le serveur compare l’ETag reçu par le navigateur avec celui de la version actuelle du document demandé. Si les deux ne correspondent pas c’est que le document a été modifié depuis le dernier accès et on le renvoie normalement. Si les deux sont identique alors le serveur web peut utiliser le code de retour 304
et demander au navigateur d’utiliser sa copie en cache. On évite alors de télécharger de nouveau le document, inutilement. C’est intéressant autant pour le serveur que pour le client :
HTTP/1.1 304 Not Modified Etag: "3a4ea77d-baa-f3a45280"
D’autres détails (presque) inutiles
En réalité c’est même un peu plus complet que cela puisque le navigateur peut retenir plusieurs versions du même document dans son cache et les renvoie toutes, séparées par des virgules. De son côté le serveur peut aussi avoir plusieurs formats pour un même document. Il va donc comparer toutes les dernières versions avec les identifiants proposés par le navigateur, et retourner un 304 pour la première qui correspond, en spécifiant bien pour que ETag il renvoie ce statut.
On peut aller plus loin en utilisant des identifiants dits forts et des identifiants dits faibles. Ces derniers sont préfixés par W/
et ne peuvent être utilisés que pour des requêtes GET et HEAD. Un identifiant faible est un identifiant qui peut correspondre à plusieurs représentations différentes d’un même document, ou à plusieurs versions d’une même représentation.
On trouve même la possibilité pour le navigateur d’utiliser un If-None-Match:*
, qui ne va effectuer la requête que si la ressource ciblée n’existe pas encore. Soit que la représentation adaptée à cette requête particulière n’existe pas (version du navigateur par exemple), soit quelle n’a pas encore été générée et est générée à la demande.
Sur des requêtes de type POST ou PUT (en fait sur tout sauf GET et HEAD), la réponse n’est pas 304 (not modified) mais 412 (precondition failed). Sur un PUT le If-None-Match:*
peut d’ailleurs gérer les problématiques de concurrence d’accès : la requête ne sera effectuée que la première fois, par la suite la ressource sera déjà crée et un statut 412 sera renvoyé.
Les problèmes d’ETag
En soit ce fonctionnement ne pose aucun problème. Il est supporté par l’essentiel des navigateurs et des serveurs. Quand un des intervenants ne connait pas les ETags ils sont ignorés et c’est le comportement Last-Modified
/ If-Modified-Since
qui prend la main.
Il n’y a que deux problèmes potentiels : deux versions d’une même ressource avec le même ETag, ou deux ETags différents pour une même version d’une ressource. Dans le premier cas le navigateur risque de réutiliser son cache alors qu’il existe une mise à jour. Dans le second cas, celui qui nous préoccupe, le navigateur risque de re-télécharger un document qu’il a déjà en cache.
C’est ce dernier cas qui arrive parfois sur certains sites, et qui impacte les performances à cause de la bande passante utilisée inutilement. Cela arrive dès qu’il y a plusieurs serveurs derrière un même site web. Apache utilise par défaut un identifiant basé sur la date de dernière modification et l’inode du fichier. L’inode est garantit comme unique pour un même système de fichier.
S’il y a plusieurs serveurs, chacun a son propre système de fichier. L’inode sera différent sur chaque serveur, et le client risque de recevoir des ETag différents pour la même ressources. Pour peu que la requête du navigateur ne soit pas toujours gérée par le même serveur, tout le système de cache basé sur l’ETag sera mis en échec.
Sur certaines anciennes versions de lighttpd, des serveurs 32bits et des serveurs 64bits peuvent aussi donner des ETags différents bien que l’inode n’intervienne pas dans le calcul.
Désactiver les ETags
Conclusion simple et rapide de l’équipe performance de Yahoo! : désactiver les Etags. En l’absence des ETags c’est en effet le mécanisme habituel de date de modification qui est utilisé pour générer les 304 et les caches. Ce système n’est pas parfait mais pour des ressources qui ne sont pas amenées à changer plusieurs fois par seconde il n’amène aucun problème.
En suivant les recommandations habituelles on ne modifie justement presque jamais une ressource statique, on créé la nouvelle version avec une nouvelle URL et on laisse l’ancienne telle quelle. Les ressources dynamiques, elles, à cause des personnalisations et des mises à jours très fréquentes, n’utilisent de toutes façons que rarement les 304 et ces systèmes de validation de cache. Quand elles le font c’est que les mises à jour ne sont pas trop fréquentes ; la limitation imposées par la résolution à la seconde des dates de modification n’est alors pas gênante.
Bref, on ne perd pas grand chose et on évite des problèmes dont on sait qu’ils vont survenir.
Sous Apache c’est fait avec FileETag none
. Pour Microsoft IIS il y a toute une procédure.
Réactiver les ETags ?
Déjà, tant que vous avez un seul serveur derrière votre site web, n’hésitez pas : activez les ETags même si Yslow vous dit le contraire. Vous avez tout à y gagner et rien à y perdre.
Ensuite, activer tout de même les ETags n’est pas forcément une mauvaise idée. Le problème n’est pas sur la fonctionnalité elle-même, mais juste sur la façon par défaut pour Apache et IIS de générer ces identifiants. Bref, en général un cumul entre la date de modification et la taille du fichier suffit. Dans ce cas vous pouvez demander à Apache d’utiliser FileETag MTime Size
.
Enfin, ne changez pas et ne supprimez pas le calcul des ETags sur les sites qui utilisent DAV via le mod_dav_fs. Ce dernier risque de ne plus fonctionner de manière optimale.
Je ne suis pas d’accord avec la conclusion du billet. Surtout lorsque l’on voit les billets précédents et les gains de performance minimes.
Même si un site est hébergé sur un seul serveur, les Etags ne servent à rien.
Pour les images/scripts et css : on positionne une date de mise en cache dans le futur. On sort ensuite des versions à chaque nouvelle mise en production. Résultat : on économise des connexions inutiles (même si elles sont légères). 10 images, c’est 10 connexions de gagnées!
Pour les fichiers statiques, c’est plus compliqué mais en y réfléchissant, on s’aperçoit que des pages entièrement dynamiques peuvent devenir « statiques » pendant 10 min, 1 heure voire 24 heures.
Je pense que l’on peut vraiment gagner en performances en désactivant les ETags. Et je l’ai mis en pratique sur mon site.
Hum, tu mélanges deux concepts AMHA.
Le fait d’activer les Etag (ou pas), ne change rien à la possibilité de gérer des expirations (infinies pour les ressources statiques, courtes pour certaines ressources dynamiques). Ca ne rentre nullement en conflit avec ta manière de gérer les ressources (qui est la bonne manière de faire d’ailleurs).
Par contre ça jouera sur les quelques ressources pour lesquelles tu ne peux pas mettre une expiration ou pour lesquelles tu dois mettre une expiration courte (genre 24h). Pour ces ressources là, le navigateur tentera une revalidation (soit systématique soit seulement à la fin de l’expiration explicite). Et là l’ETag rentre en jeu.
Encore une fois ça n’entre nullement en conflit avec l’idée de mettre des expirations longues et de gérer le versionement par un changement d’url (et non un changement du contenu). Ca vient en plus.
Maintenant je suis d’accord pour dire que l’utilité est faible. Mon propos dans la conclusion est plus pour prendre le contrepied de la communication de Yahoo!, qui laisse entendre que les Etags sont mauvais par principe, alors que c’est juste certains systèmes de génération par défaut qui posent problèmes, et que si on utilise plusieurs serveurs.
Si l’on place une expiration pour toutes les pages et tous les objets, les ETags ne servent plus. Ce n’est pas génant mais autant les désactiver. Gérer soi même le cache est bien plus productif et permet de se poser les bonnes questions.
L’équipe Yahoo! Performance tient les mêmes propos que toi dans ce billet, à savoir que les ETags doivent être désactivés si il y a plusieurs serveurs HTTP pour gérer le site. Il y a d’ailleurs beaucoup de commentaires à propos de cette directive dans YSlow.
Sisi, même avec une expiration, les Etags servent. Si tu as une expiration de 24h, à la fin des 24h les navigateurs vont revalider le cache avec une requête conditionnelle, et là ils utiliseront les ETags.
Quant à gérer le cache soit-même c’est encore une autre question. Sur les scripts, le serveur web n’enverra de toutes façons pas d’ETags. On ne parle donc que des ETags générés automatiquement par le serveur pour des fichiers statiques, ou éventuellement des pages statiques générées.
Un exemple : Imagines que ton système de publication génère et met à jour un fichier RSS statique à chaque nouvel article.
– Tu as une expiration explicite courte (voire pas du tout car l’intérêt est finalement faible dans ce cas précis), de type une heure. Ton client RSS aura besoin des requêtes conditionnelles pour revalider son cache, la plupart le font.
– Si tu laisses faire la simple date de dernière modification tu prends le risques du cas où plusieurs articles arrivent la même seconde, ce qui arrivera certainement à terme, et où un client récupère le fichier entre les deux secondes.
– Reste donc la possibilité de configurer le serveur web pour qu’il génère des ETags avec date+taille afin de dissocier les différentes versions de ce flux RSS.
L’exemple n’est pas si idiot parce que des systèmes à génération régulière de fichiers statiques ça arrive souvent sur les sites à fort traffic. Ce sont des ressources dynamiques (le contenu change régulèrement) donc il n’y a pas d’expiration explicite ou une expiration explicite courte. La présence d’ETag est plus que pertinente.
—
Pour Yahoo! c’est justement ce que je reproche. Ils ne font que soulever le problème des ETags générés à partir des inode dans un cas multi-serveurs. Rien d’autre ne leur pose problème.
Leur comm externe est mal tournée et laisse croire que ce sont les Etags en eux même qui sont négatifs, alors que ce n’est pas du tout le cas.
Je ne peux malheureusement pas trop m’étendre sur Yahoo!, mais je t’assure que leur recommandation est uniquement liée à la question des inode et que si on écarte ce détail il n’y a rien contre les ETags.
Ok, tu m’as convaincu Eric, ce n’est pas si mal que ca.
L’Etag calculé sur la date+taille a l’air en effet une bonne solution supplémentaire en fin d’expiration.
Que se passe-t-il par contre lorsque le fichier est expiré et que le navigateur fait une demande pour savoir si le fichier a été modifié via les ETags? L’expiration est prolongée?
Par contre un (contre) exemple simple ou il faudrait désactiver les ETags :
Un client consulte une page qui finalement ne va pas changer de toute la semaine.
Délai d’expiration de 24h. Le client va consulter cette page plusieurs fois par jour, toutes les heures.
Cas Etags désactivés :
Le jour 1, le client va télécharger le fichier qu’il n’a pas en cache. Toutes les autres consultations du document vont se faire depuis le cache.
Jour 2, le client va retélécharger le document une fois dans la journée.
Ainsi de suite pour les 7 jours, donc cette requête et 7 téléchargements complets.
Cas ETags activés :
Le jour 1, le client va télécharger le fichier qu’il n’a pas en cache. Toutes les autres consultations du document vont se faire depuis le cache.
Le jour 2, le document a expiré et les ETags rentrent en jeu. A chaque nouvelle consultation, une requête de demande de modif va être faite par le client.
Au final, un seul téléchargement du fichier complet, mais 24*6=144 requêtes de demande de modification.
Cet exemple juste pour bien être sûr que j’ai compris le principe, corrige moi si c’est faux.
La demande de revalidation GET reçoit exactement les mêmes entêtes qu’une demande classique. Seuls changent le contenu (qui n’est pas renvoyé) et le statut http (304 au lieu de 200)
Donc si ton serveur est configuré pour toujours envoyer une expiration explicite de 24h, à la première revalidation il recevra une nouvelle expiration de 24h et ne fera plus appel au serveur pour 24h supplémentaire.
Pour être plus complet, en HTTP 1,0 les revalidations de cache se font avec les dates de dernière modification. Ca pose quelques problèmes avec les auto-négociations et les mises à jour trop fréquentes donc en HTTP 1,1 on a rajouté les ETags par dessus.
Qu’on les active ou pas, le fait que le navigateur demandera une revalidation et le schéma global de ce qu’il se passe ne change pas. La seule différence c’est que le serveur aura moyen d’être plus exact quand il répond si le document a changé ou pas.
Dans ton exemple :
– ETags désactivés : en fait le document ne sera pas retéléchargés le jour 2, la revalidation du cache se fera à partir de la date de dernière modification et un 304 sera renvoyé de toutes façons (avec la nouvelle expiration de 24h).
– ETags activés : le jour 2 une nouvelle expiration de 24h sera renvoyée en même temps que le code 304, donc on n’aura bien qu’un téléchargement
Merci Eric pour tes précisions!
Il n’y a donc pas de raison de désactiver les ETags et les deux techniques sont complémentaires.
Je vais revoir mes fonctions de mise en cache pour les fichiers html/xml et prendre en compte les ETags.
Salut,
Merci pour cette explication détaillée sur les etags.
J’ai tout de même un doute concernant la technique date de modification + taille du fichier. Dans le cas où l’on a plusieurs serveurs avec des systèmes de fichiers séparés, la date de dernière modification ne sera pas nécessairement la même : par exemple si les nouveaux fichiers sont déployés sur les serveurs, la copie n’aura pas forcément lieu à la même seconde. Et du coup les Etags seront différents. Est-ce que je me trompe ?
Tiens, la remarque est intéressante, comment synchronises tu tes serveurs ?
Pour l’instant je n’ai quasiment que vu des rsync, qui savent aussi synchroniser les dates de création/modification. Si tu fais des copies brutes ou si tu n’as pas les droits pour synchroniser les dates, effectivement tu auras des problèmes. Mais dans ce cas le if-modified-since ne fonctionnera pas non plus et tu te retrouves sans mécanisme de revalidation possible.
Personnellement je n’ai pas ce problème 😀
Je ne savais pas que rsync pouvait aussi synchroniser les dates de dernière modification.
Après je pensais par exemple à un update depuis un subversion, pratique dans le cas du code (on peut facilement faire des rollbacks), qui pourrait comporter quelques éléments statiques (images liées au thème par exemple).
Maintenant je n’avais pas pensé à rsync qui semble plus intéressant.
Pour le contenu statique, par exemple les images uploadées par les utilisateurs (photos de profil etc…) on voit soit des systèmes de fichier réseau (NFS, autres), soit des serveurs web distincts spécialisés dans la délivrance du contenu statique. Dans ces deux cas, le Etag n’est plus un problème puisqu’on a une seule version du fichier. Est-ce que tu vois d’autres architectures où le Etag pourrait poser problème ?
Donc visiblement, le problème que je soulève a une portée qui reste limitée.
Salut Eric,
J’ai une petite question pour toi concernant la désactivation des Etags :
Au boulot nous avons 2 serveurs en frontend synchronisés via rsync, qui ne synchronise pas les dates de dernière modification. Je me retrouve donc dans le cas ou les Etags générés ne servent pas à grand chose. Nous avons donc ajouté dans le http.conf la directive pour désactiver les Etags. Comment se fait-il que Yslow me donne toujours une note de F et m’affiche un Etag dans les entêtes HTTP ? Préconises-tu de revoir la manière dont sont synchronisés les 2 serveurs et de laisser les Etags activés ? (Nous avons aussi activé Expires pour les images et les CSS et les JS).
j’ai oublié de préciser que la version du serveur Apache est en 1.3 et qu’on dirait que FileEtag est arrivé avec la version 2.
C’est aussi du Apache 1.3
« FileEtag None » devrait fonctionner.
Vérifies que la directive est dans la bonne section « vhost », dans la bonne section « directory », que Apache est relancé, etc.
Pour ceux qui voudraient quand même désactiver les ETags et qui n’ont pas la main sur leur config Apache, il reste la possibilité de les desactiver via .htaccess:
http://www.askapache.com/htaccess/apache-speed-etags.html