Transcript
00:00:00Combien peut réellement vous coûter un seul octet ?
00:00:02Eh bien, à l'échelle de Cloudflare, gaspiller un seul octet par entrée
00:00:04leur coûte plus de 250 gigaoctets de mémoire
00:00:06dans l'ensemble de leur parc,
00:00:08et ils ont récemment réduit de moitié cette mémoire par entrée,
00:00:11libérant ainsi 100 téraoctets de mémoire.
00:00:13Par les temps qui courent, cela fait beaucoup d'argent.
00:00:15Ils y sont parvenus grâce à cinq modifications assez simples
00:00:17apportées à leur système de cache,
00:00:18tout en rendant le DNS plus rapide en même temps.
00:00:21Voyons donc en détail comment ils ont fait.
00:00:27Vous avez peut-être déjà entendu parler de 1.1.1.1.
00:00:30C'est le résolveur DNS public de Cloudflare,
00:00:32et il fonctionne en prenant le domaine que vous souhaitez visiter,
00:00:34comme betterstack.com,
00:00:35pour découvrir quelle est la véritable adresse IP de ce domaine.
00:00:39L'élément qui effectue ce travail
00:00:40s'appelle Big Pineapple, il est écrit en Rust,
00:00:42et comme vous pouvez l'imaginer,
00:00:43c'est un logiciel extrêmement sollicité,
00:00:46qui stocke plus de 250 milliards d'entrées de cache DNS à tout moment.
00:00:50Cela garantit que si quelqu'un souhaite se rendre sur betterstack.com
00:00:52deux secondes après quelqu'un d'autre,
00:00:53il n'a pas besoin de parcourir toute la hiérarchie DNS à nouveau,
00:00:56il garde simplement la réponse en mémoire et la renvoie directement.
00:00:59Mais comme je l'ai mentionné en introduction,
00:01:01250 milliards d'entrées de cache signifient qu'un octet gaspillé par entrée
00:01:04coûte environ 250 gigaoctets de RAM,
00:01:07et c'est pourquoi ces cinq changements ont eu un si grand impact.
00:01:10Premièrement, penchons-nous sur le coût de la capacité.
00:01:12Voici à quoi ressemblait une entrée de cache auparavant.
00:01:14Un horodatage, une durée de vie (TTL), des compteurs de succès,
00:01:16puis un ensemble de vecteurs.
00:01:17Un vecteur d'enregistrements de réponse,
00:01:18un vecteur d'enregistrements d'autorité,
00:01:20un vecteur d'enregistrements additionnels,
00:01:21un vecteur d'erreurs,
00:01:22et bien d'autres encore.
00:01:23Pour ceux d'entre vous qui ne connaissent pas Rust,
00:01:25un vecteur est simplement le type de données par défaut pour une liste extensible,
00:01:27et il est représenté par trois éléments en mémoire.
00:01:29Un pointeur, une longueur et une capacité.
00:01:31C'est l'espace réservé pour la croissance,
00:01:32afin de ne pas avoir à réallouer à chaque fois qu'on y ajoute un élément.
00:01:35Ce qui est très utile si l'élément grandit effectivement.
00:01:38Mais ils ont examiné de vraies entrées de cache DNS,
00:01:40et ils ont réalisé que tout était simplement écrit dans le cache
00:01:42et n'était plus jamais modifié.
00:01:44Elles étaient seulement lues,
00:01:45de sorte que ces vecteurs ne grandissaient jamais.
00:01:47Cela signifie qu'il y a de l'espace tas suralloué,
00:01:49qui reste là à être gaspillé.
00:01:50Il y a un vecteur avec une capacité pour huit éléments,
00:01:52mais seulement cinq stockés,
00:01:53ce qui laisse trois emplacements inutilisés,
00:01:55et le champ de capacité lui-même est un usize,
00:01:57ce qui représente huit octets de mémoire,
00:01:58dont ils n'ont tout simplement pas besoin.
00:02:00La solution à ce problème était incroyablement simple.
00:02:02Il suffit de remplacer un vecteur par un Box,
00:02:04car la taille d'un Box est fixée à la taille qu'il avait lors de sa création,
00:02:07il n'a donc pas besoin de champ de capacité,
00:02:08et il stocke exactement ce qui s'y trouve.
00:02:10Il n'a pas besoin d'allouer de capacité supplémentaire pour l'avenir.
00:02:13Ils ont également réalisé qu'ils pouvaient appliquer cette même économie
00:02:15aux champs de chaînes de caractères,
00:02:16car ils sont essentiellement constitués d'un vecteur de U8,
00:02:18afin que ce texte puisse croître ou diminuer,
00:02:20mais si vous n'en avez pas besoin,
00:02:21vous pouvez simplement utiliser une tranche de chaîne Box (Box),
00:02:23c'est-à-dire que cette chaîne ne va pas changer.
00:02:26Au total, sur une entrée de cache,
00:02:27il y avait huit champs de vecteurs et de chaînes,
00:02:29donc les remplacer par un Box a permis d'économiser huit octets par champ,
00:02:31soit 64 octets par entrée,
00:02:33tout en éliminant l'espace tas excédentaire
00:02:35qu'un vecteur réserve pour une croissance future,
00:02:36ce qui signifie que les économies combinées ont atteint plus de 15 téraoctets
00:02:39lorsqu'elles sont portées à 250 milliards d'entrées de cache.
00:02:42Tout cela grâce à un simple changement de type de données.
00:02:45Pour notre prochain changement cependant,
00:02:46ils ont examiné certaines de ces listes,
00:02:48et se sont simplement demandés : en avons-nous vraiment besoin ?
00:02:50Une réponse DNS contient trois types d'entrées,
00:02:52réponse, autorité et additionnel,
00:02:54et comme nous l'avons vu plus tôt,
00:02:55celles-ci étaient mises en cache telles quelles,
00:02:57sous forme de trois listes séparées,
00:02:58et même si nous avons supprimé le champ de capacité lors du premier changement,
00:03:01chaque liste est toujours composée d'un pointeur et d'une longueur,
00:03:03soit huit octets plus huit octets multipliés par trois,
00:03:05ce qui fait 48 octets au total,
00:03:07et trois allocations de tas séparées.
00:03:09Mais en examinant comment ces listes
00:03:10étaient réellement utilisées,
00:03:12ils ont réalisé qu'elles étaient toujours lues ensemble,
00:03:13toujours écrites ensemble,
00:03:14et qu'elles sont toujours dans le même ordre exact.
00:03:16Donc, au lieu de trois listes,
00:03:17ils pouvaient les traiter comme une seule liste avec deux séparateurs.
00:03:20Les trois listes deviennent donc une seule liste d'enregistrements,
00:03:22plus deux petits décalages (offsets) indiquant que les réponses se terminent ici,
00:03:25et que l'autorité se termine là,
00:03:26et comme une réponse DNS n'aura jamais quatre milliards d'enregistrements,
00:03:29un entier non signé de 16 bits suffira pour les décalages,
00:03:33ce qui ne fait que deux octets chacun.
00:03:34Cela signifie qu'au total,
00:03:35ils ont remplacé deux en-têtes complets de 16 octets par deux nombres de 2 octets,
00:03:39soit 28 octets économisés par entrée,
00:03:41ce qui permet également à Rust de supprimer un peu d'espacement superflu entre les champs,
00:03:44rendant la structure (struct) plus petite que la simple somme des champs supprimés.
00:03:47Ils ont même poussé ce concept plus loin,
00:03:49en fusionnant plusieurs champs booléens en un seul drapeau de bits (bit flag).
00:03:52Passons au changement numéro trois :
00:03:53et si l'on arrêtait de répéter le même nom deux fois ?
00:03:55Chaque enregistrement DNS possède un propriétaire,
00:03:57qui est simplement le nom de domaine auquel cet enregistrement appartient.
00:04:00Ainsi, lorsque vous interrogez betterstack.com et que vous récupérez vos enregistrements,
00:04:03chacun d'eux porte la mention betterstack.com.
00:04:05Mais cette information est en quelque sorte redondante.
00:04:08C'est vous qui avez posé la question,
00:04:09le système connaît donc déjà le nom de domaine.
00:04:11Et Cloudflare stockait déjà ce domaine de requête comme clé de cache,
00:04:14alors pourquoi ont-ils besoin de le stocker à nouveau comme propriétaire ?
00:04:17Eh bien, ce n'est pas le cas.
00:04:17Cloudflare a donc simplement transformé ce champ en un nom de type Box optionnel,
00:04:20où si le propriétaire correspond à la requête,
00:04:22on stocke None,
00:04:23mais s'il est réellement différent,
00:04:24ce qui peut arriver dans quelques cas comme les chaînes CNAME,
00:04:27on stocke le propriétaire comme avant.
00:04:29En procédant ainsi, dans les cas normaux où ils correspondent,
00:04:32on économise toute une allocation de tas par enregistrement,
00:04:34cette économie consistait donc simplement à examiner les données présentes dans leur cache
00:04:37et à réaliser qu'il y avait des valeurs en double.
00:04:39Pour l'économie numéro quatre, intéressons-nous à l'adresse IP de 144 octets.
00:04:43Leurs données d'enregistrement formaient une énumération (enum) Rust pour les enregistrements A, AAAA, TXT,
00:04:47SVCB et NAPTR, un seul type couvrant l'ensemble.
00:04:51Mais voici le problème avec les enums.
00:04:52Elles sont toujours aussi grandes que leur variante la plus volumineuse.
00:04:54Chaque valeur de ce type occupe la même quantité d'espace,
00:04:57qu'elle en ait besoin ou non.
00:04:58Dans ce cas, NAPTR est la plus grande valeur, occupant 136 octets,
00:05:03et si l'on ajoute le tag et le remplissage à l'énumération, on arrive à 144 octets.
00:05:07Si l'on compare cela aux besoins d'un enregistrement A, qui est juste une simple adresse IPv4,
00:05:12cela ne nécessiterait que quatre octets.
00:05:13Cela signifie que chaque enregistrement A de ce cache résidait dans une boîte de 144 octets,
00:05:17n'en utilisant que quatre,
00:05:19et les enregistrements A et AAAA constituent la majeure partie du trafic réel.
00:05:22Dans le benchmark de Cloudflare, il y a 56 % d'enregistrements A et 25 % d'AAAA, donc la majorité du cache n'était que du remplissage.
00:05:29La solution a été notre chère boîte.
00:05:32Ils ont mis les grandes variantes dans des boîtes et ont laissé A et AAAA en ligne, car c'est petit et courant,
00:05:36ainsi text, SVCB et NAPTR se trouvent désormais derrière un pointeur,
00:05:40ce qui fait que l'énumération n'a plus besoin d'être plus grande que la plus grande restante, à savoir cette adresse IPv6 de 16 octets.
00:05:45Au total, pour un enregistrement A ou AAAA, cela représente une économie de 120 octets chacun.
00:05:50Mais ce changement comporte un compromis.
00:05:52Quand on met une variante en boîte, ses données passent d'une entrée de cache
00:05:55pour se retrouver dans sa propre région du tas, ailleurs,
00:05:58et cela engendre deux nouveaux coûts.
00:06:00Le premier est l'allocateur.
00:06:02Cloudflare utilise Jemalloc, et Jemalloc ne fournit pas le nombre exact d'octets demandés,
00:06:06il groupe les allocations dans des blocs de taille fixe et arrondit à la valeur supérieure.
00:06:10Ainsi, un enregistrement text demandant 32 octets atterrit dans un bloc de 32 octets sans rien gaspiller,
00:06:15mais un enregistrement MX demandant 40 octets est arrondi à 48 octets, perdant silencieusement 8 octets.
00:06:21Le deuxième coût est la localité.
00:06:22Avant la mise en boîte, toutes les données d'enregistrement d'une entrée résidaient dans un bloc de mémoire contigu,
00:06:27et après, chacune se trouve ailleurs, ce qui implique de suivre un pointeur pour la lire,
00:06:31et si ce pointeur se trouve loin du reste de l'entrée,
00:06:34votre processeur doit aller chercher une toute nouvelle ligne de cache rien que pour la lire.
00:06:37Bien que la mise en boîte ait résolu le problème du remplissage, elle en a créé un nouveau,
00:06:41ce qui a poussé Cloudflare à se demander : et si on ne les stockait pas du tout sous forme de types Rust ?
00:06:45C'est le changement numéro cinq.
00:06:46Cloudflare l'a décrit comme un juste milieu : garder le reste de l'entrée de cache comme des champs structurés normaux,
00:06:50mais prendre les données d'enregistrement elles-mêmes et les stocker sous forme de bruts.
00:06:54Ainsi, au lieu d'une énumération ou d'une boîte par enregistrement, toute l'entrée reçoit un unique tableau d'octets,
00:07:00chaque enregistrement étant écrit avec un préfixe de longueur de 2 octets, suivi de ses données.
00:07:03Cela annule les deux coûts dont nous venons de parler.
00:07:06Toutes ces allocations en boîte distinctes fusionnent en une seule pour l'ensemble des données d'enregistrement,
00:07:10il n'y a donc plus d'arrondi par enregistrement vers un bloc Jemalloc,
00:07:13et tout est de nouveau regroupé de manière contiguë, ce qui permet de récupérer la localité de cache perdue.
00:07:18En prime, cela accélère également les recherches.
00:07:21Auparavant, lors de chaque consultation, un enregistrement analysé résidait en mémoire,
00:07:25et il fallait le sérialiser champ par champ au format réseau DNS avant de pouvoir l'envoyer.
00:07:30Maintenant, il est déjà au format réseau DNS, donc la plupart des types sont copiés directement du tampon
00:07:35vers le message sortant.
00:07:37Les seuls qui ont encore besoin d'analyse sont ceux contenant des noms de domaine,
00:07:40à savoir CNAME, NS, MX et SOA,
00:07:43et cela uniquement parce que la compression des noms DNS oblige à réécrire ces noms de toute façon.
00:07:47Ce changement final réduit la mémoire et supprime beaucoup de travail du chemin critique.
00:07:51Voilà donc 5 modifications de bas niveau apportées au cache,
00:07:54qui, combinées, réduisent l'empreinte par entrée de 953 octets à 420,
00:08:00soit 56 % de moins, et la mémoire allouée par entrée est passée de 1,1 kilo-octet
00:08:05à 461 octets, soit une réduction de 58 %.
00:08:09De plus, leur débit d'insertion a augmenté de 43 %,
00:08:12passant de 625 000 entrées par seconde à 893 000,
00:08:17et les recherches sont devenues 19 % plus rapides, passant de 828 nanosecondes à 670.
00:08:23Ils ont déployé ces changements en production cette année,
00:08:25et leur mémoire résidente P99 est passée de 9,3 gigaoctets à 5,3,
00:08:29soit une baisse de 43 % sur le trafic réel,
00:08:32ce qui représente à l'échelle du parc environ 100 téraoctets de mémoire libérée,
00:08:35soit l'équivalent de la RAM de 130 serveurs de génération 13.
00:08:38Ils prévoient maintenant d'utiliser cet espace supplémentaire pour avoir un cache plus grand,
00:08:41accélérant encore les choses.
00:08:43J'aime beaucoup cet article de blog car il met en lumière un choix auquel nous serons confrontés.
00:08:46Devrions-nous nous poser avant de construire tout cela pour concevoir toutes les optimisations,
00:08:50ou est-ce que cela aurait été une optimisation prématurée ?
00:08:52J'imagine qu'à l'époque, en 2018, lors de sa création,
00:08:55le coût de la RAM pour Cloudflare n'était pas aussi important qu'aujourd'hui.
00:08:59L'article complet est une excellente lecture, je vous mets le lien en description.
00:09:02Dites-moi ce que vous en pensez dans les commentaires,
00:09:03abonnez-vous pendant que vous y êtes,
00:09:04et comme toujours, à la prochaine.
00:09:05À la prochaine.
Community Posts
No posts yet. Be the first to write about this video!
Write about this video