Une plongée au cœur de la mise à l'échelle de Postgres en temps réel
par Carlota Soto
Choisir la taille d'une instance de base de données avant de connaître la charge de travail est une ancienne méthode de conception. Le processus est généralement bancal et donne l'impression de gaspiller beaucoup de ressources de calcul, surtout maintenant que le calcul devient un luxe.
Lakebase Postgres élimine complètement cette étape de dimensionnement grâce à la mise à l'échelle automatique. La réactivité de la mise à l'échelle automatique provient du redimensionnement à chaud des VM et d'un algorithme qui suit le CPU, la mémoire et l'ensemble de travail (working set) de la base de données.

À quoi ressemble la mise à l'échelle automatique pour un échantillon arbitraire de bases de données Lakebase Postgres. Notez qu'il ne s'agit que d'une heure.
Postgres traditionnel s'exécute comme un processus avec état (stateful) lié à une machine et à ses disques ; remplacer ou redimensionner cette machine est une opération de base de données car la machine gère à la fois l'exécution et l'état durable. Mais l'architecture de Lakebase Postgres sépare ces responsabilités :
Un nœud de calcul peut donc démarrer, s'arrêter, se déplacer ou changer de taille sans déplacer la base de données sous-jacente. C'est une base essentielle.

Maintenant, lorsqu'il s'agit d'implémenter la mise à l'échelle automatique, il y a deux aspects à prendre en compte : d'abord, il faut déterminer quand ajuster la capacité à la hausse ou à la baisse, et ensuite, comment le faire sans arrêter Postgres.
Voyons ces deux aspects dans l'ordre.
Pour déduire quand redimensionner, l'algorithme de mise à l'échelle automatique de Lakebase Postgres suit trois signaux, chaque signal produisant sa propre taille de calcul cible :
cpuGoalCUmemGoalCUlfcGoalCULa cible de mise à l'échelle finale est la plus grande des trois, limitée aux tailles de calcul minimale et maximale configurées par l'utilisateur pour cette base de données (les limites de mise à l'échelle automatique) :
Le CPU est le plus simple des trois signaux. L'algorithme surveille de près la charge du processeur :
cpuGoalCU augmente. Lorsque la charge soutenue diminue, l'objectif diminue également.L'utilisation d'une moyenne sur une minute permet de filtrer les fluctuations très courtes tout en répondant aux variations significatives de la demande. L'intervalle d'interrogation de cinq secondes permet au système de mettre à jour la cible à mesure que cette moyenne évolue.
Cependant, le CPU seul ne suffit pas pour mettre à l'échelle Postgres correctement. Une requête en attente de données via le réseau peut afficher une faible utilisation du CPU tout en ayant de mauvaises performances. L'algorithme doit également prendre en compte la pression sur la mémoire et le cache.
La mémoire présente un mode de défaillance différent de celui du CPU. Si la demande dépasse brièvement le CPU disponible, les requêtes ralentissent ; mais si Postgres alloue plus de mémoire que la VM n'en possède, le noyau peut arrêter des processus. L'autoscaler a donc besoin d'un signal beaucoup plus rapide que le CPU pour détecter l'épuisement de la mémoire.
Le système surveille donc la mémoire à deux fréquences :
L'objectif de mémoire maintient l'utilisation en dessous de 75 % de la RAM allouée. Cette marge de manœuvre donne au système l'espace nécessaire pour répondre aux nouvelles allocations et laisse de la mémoire pour le système d'exploitation invité et les autres processus.
Le vm-monitor vérifie également chaque proposition de réduction d'échelle (downscale). La mémoire ne peut pas être retirée si cela laissait les processus en cours d'exécution sans espace suffisant.
Un peu d'histoire : Cette approche par interrogation a remplacé une conception antérieure basée sur l'événement cgroup memory.high. Le franchissement de memory.high obligeait Linux à récupérer de la mémoire et à limiter les processus au sein du cgroup. L'interrogation s'est avérée plus prévisible et stable tout en offrant au système une visibilité de 100 millisecondes sur la mémoire de Postgres.
Le troisième signal mesure si les données actives de la charge de travail s'intègrent à proximité de Postgres. Voici l'explication générale :
Lakebase Postgres sépare le stockage et le calcul ; lorsqu'une page n'est pas disponible localement, le calcul la demande au pageserver ; la page renvoyée est mise en cache pour les lectures ultérieures. Le cache de calcul, que nous appelions initialement Local File Cache ou (LFC), est un cache sur disque dimensionné pour s'adapter au cache de pages du noyau. Il agit comme une extension redimensionnable des tampons partagés (shared buffers) de Postgres. Lorsqu'une ressource de calcul augmente, le vm-monitor étend le cache pour utiliser une partie de la mémoire ajoutée.
Pour de nombreuses charges de travail OLTP, les performances changent radicalement dès que l'ensemble de travail (working set) tient dans la mémoire locale. Cela révèle un angle mort dans la mise à l'échelle automatique basée uniquement sur le CPU : les échecs de cache (cache misses) laissent les requêtes en attente de requêtes réseau, ce qui réduit l'utilisation du CPU. Le système peut donc constater une faible pression sur le CPU au moment précis où un cache plus grand améliorerait les performances. Ainsi, dans Lakebase Postgres, il existe un troisième signal de mise à l'échelle automatique qui estime directement l'ensemble de travail de Postgres.
C'est la partie la plus intéressante de l'algorithme, voyons donc comment fonctionne cette estimation.
L'ensemble de travail d'une charge de travail est l'ensemble des pages de base de données et d'index auxquelles elle accède de manière répétée sur une période donnée. Compter précisément chaque page à des fins de mise à l'échelle automatique nécessiterait trop de mémoire. La méthode classique pour résoudre ce problème consiste donc à s'appuyer sur HyperLogLog, un estimateur probabiliste de cardinalité capable d'estimer le nombre d'éléments distincts dans un ensemble à l'aide d'une quantité de mémoire petite et fixe.
Pour chaque accès à une page Postgres, une implémentation standard de HyperLogLog :
La distribution de ces valeurs de registre fournit une estimation du nombre de pages distinctes observées.

Cependant, l'utilisation simple de HyperLogLog pour la mise à l'échelle automatique pose un problème : un HyperLogLog standard ne fait que croître. Une fois qu'un registre a observé une valeur, il ne peut pas dire quel élément l'a produite ni quand cet élément a été vu pour la dernière fois.
Cela permet de répondre efficacement à la question : « À combien de pages distinctes cette ressource de calcul a-t-elle accédé depuis le démarrage de Postgres ? » Mais la mise à l'échelle automatique nécessite une réponse différente, plus proche de : « Combien de pages distinctes appartiennent à la charge de travail en cours d'exécution ? »
Sans limite de temps, une ancienne importation ou une requête analytique resterait dans l'estimation et maintiendrait la ressource de calcul surdimensionnée bien après la fin de ce travail. Nous avons donc modifié ce que stockent les registres HyperLogLog.
Voici comment les choses fonctionnent réellement dans Lakebase Postgres :
Au lieu de définir un bit lorsqu'un hachage est observé, l'estimateur stocke l'horodatage actuel à cette position. Pour estimer la cardinalité depuis l'instant T, il traite les positions mises à jour après T comme définies et les positions plus anciennes comme non définies.

HyperLogLog modifié dans la mise à l'échelle automatique de Lakebase Postgres.
Cela produit une estimation pour toute fenêtre se terminant au moment présent, y compris :
Pour en revenir à l'algorithme, voici comment fonctionne concrètement la granularité : toutes les 20 secondes, l'autoscaler-agent collecte des estimations de l'ensemble de travail (working set) pour des fenêtres allant de une à 60 minutes.
Mais l'histoire ne s'arrête pas là. Comme vous le remarquez sûrement, il s'agit d'une large fenêtre temporelle. Comment la choisit-on concrètement ?
Le problème est le suivant : il n'existe pas de fenêtre universelle décrivant l'ensemble de travail actuel d'une base de données. Si nous choisissons une fenêtre courte, le moteur de mise à l'échelle automatique réagit rapidement à la fin d'une charge de travail, mais il viderait le cache de manière trop agressive entre les pics d'activité. Si nous choisissons une fenêtre longue, l'algorithme protégerait le cache, mais il maintiendrait également de la mémoire allouée à des tâches qui ne sont plus en cours d'exécution.
L'algorithme résout ce problème en observant l'évolution de l'ensemble de travail au fil du temps. Par exemple : pour une charge de travail stable, le nombre estimé de pages augmente d'abord, puis se stabilise. Prolonger la fenêtre ajoute du temps, mais peu de nouvelles pages sont ajoutées, car le même ensemble de travail est consulté de manière répétée.

Considérons maintenant une charge de travail lourde qui s'est terminée récemment. Les fenêtres courtes ne contiennent que la charge de travail actuelle, plus légère ; mais dès que la fenêtre remonte suffisamment loin dans le passé pour inclure la charge de travail précédente, l'estimation fait un bond. L'algorithme recherche ce bond, qui marque la fin du plateau actuel.

En résumé :
L'implémentation commence sa recherche après cinq minutes. Cela évite que la ressource de calcul ne se réduise immédiatement lors d'une courte pause pour ensuite croître à nouveau lors du pic suivant. Mais si l'algorithme ne trouve pas d'augmentation nette, il utilise l'estimation sur 60 minutes – c'est le résultat attendu pour une charge de travail stable dont l'ensemble de travail reste actif tout au long de l'heure.

Il reste un dernier élément. Mesurer l'ensemble de travail actuel intervient un peu trop tard : supposons qu'une charge de travail commence à analyser un nouvel ensemble de pages. Si le cache de calcul ne s'étend qu'après la lecture de ces pages, les premières pages peuvent déjà avoir été évincées pour faire de la place aux suivantes. Le cache doit alors récupérer à nouveau certaines de ces mêmes données.
L'algorithme effectue donc également une projection de la croissance de l'ensemble de travail. Il examine comment l'estimation augmente d'une durée à l'autre et alloue suffisamment de cache pour l'ensemble de travail attendu lors du prochain intervalle de contrôle.
Comme les métriques du cache sont récupérées toutes les 20 secondes, la projection ne couvre qu'une fraction de minute. Des projections plus longues réagiraient plus tôt, mais elles amplifieraient également les pics temporaires et feraient osciller la ressource de calcul.

La taille projetée devient (enfin !) lfcGoalCU. Et l'objectif algorithmique est de faire tenir l'ensemble de travail dans la partie de la mémoire disponible pour le cache de calcul, jusqu'à 75 % de la RAM de la ressource de calcul.
Pour résumer, la cible de mise à l'échelle était :
Ces trois signaux indiquent au système la taille à cibler. Appliquer cette taille signifie modifier le CPU et la mémoire sur une VM en cours d'exécution sans interrompre Postgres.
Chaque instance Postgres dans Lakebase Postgres s'exécute dans sa propre machine virtuelle au sein d'un cluster Kubernetes. Nous utilisons des VM car elles offrent une frontière d'isolation forte et, contrairement à une allocation de conteneurs classique, permettent d'ajouter ou de retirer du CPU et de la mémoire à un invité en cours d'exécution.
Quatre composants coordonnent chaque redimensionnement de calcul :

Comme nous venons de le voir, la mise à l'échelle ascendante se produit lorsque l'un des trois objectifs nécessite plus de ressources de calcul que ce dont la VM dispose actuellement. Une augmentation d'échelle suit cette séquence :
Le planificateur est l'unique source de vérité pour l'allocation. Il voit à la fois la planification Kubernetes ordinaire et les demandes de mise à l'échelle automatique. Sans cette coordination, le planificateur pourrait placer une nouvelle charge de travail sur un nœud au moment même où l'autoscaler attribue la mémoire restante à une VM Postgres.
Si un nœud est trop encombré pour s'étendre sur place, NeonVM peut migrer à chaud (live-migrate) la VM vers un autre nœud. La VM conserve son adresse IP, de sorte que les connexions existantes restent ouvertes. Les ressources de calcul de Lakebase Postgres ont peu d'état local durable à déplacer, la migration concerne donc principalement la mémoire de la VM et l'état d'exécution.
Une réduction d'échelle utilise exactement les mêmes composants, avec une vérification supplémentaire à l'intérieur de la VM. Le vm-monitor confirme que le retrait de mémoire en laissera toujours assez pour Postgres et le reste du système invité. Si ce n'est pas le cas, la réduction d'échelle n'a pas lieu.
Remarque : La mise à l'échelle descendante compte tout autant que la mise à l'échelle ascendante. Certains systèmes de mise à l'échelle automatique sont rapides pour ajouter de la capacité mais lents à la restituer, laissant les bases de données surdimensionnées bien après la fin d'un pic d'activité. Lakebase Postgres traite les deux directions de la même manière. L'objectif est de suivre la charge de travail d'aussi près que possible, instant après instant, afin que vous cessiez de payer pour de la capacité dès que vous n'en avez plus besoin.
Lakebase Postgres surveille la charge de travail en cours d'exécution et redimensionne les ressources de calcul pour s'y adapter en temps réel. L'architecture Lakebase rend cela possible : le stockage étant découplé et durable de manière autonome, le calcul est libre de se déplacer sans se soucier des données.
Le système qui en résulte s'adapte dans les deux sens, sur une base de données active, sans interrompre les connexions. Plus important encore, il va au-delà du signal évident : le suivi du seul CPU manquerait une charge de travail bloquée par des défauts de cache (cache misses). L'algorithme suit donc également la pression sur la mémoire et une estimation de l'ensemble de travail tenant compte du temps.
La boucle finale s'exécute sur trois échelles de temps :
C'est ainsi qu'une base de données de production peut changer de taille plus de 32 000 fois par mois.

Alors que les ressources de calcul deviennent de plus en plus coûteuses et disputées, payer pour un pic que vous atteignez rarement est un modèle de conception qui pourrait bientôt ne plus être viable. La mise à l'échelle automatique prépare Postgres pour des charges de travail où le gaspillage de ressources de calcul n'est pas une option.
Demandez à votre agent de déployer Lakebase Postgres et de mettre sa mise à l'échelle automatique à l'épreuve. Commencer ici.
Lakebase Postgres peut être utilisé comme une base de données autonome, et vous pouvez également l'intégrer au reste de la plateforme Databricks Data + AI : la gouvernance Unity Catalog, les analyses lakehouse, les notebooks et les workflows d'IA.
(Cet article de blog a été traduit à l'aide d'outils basés sur l'intelligence artificielle) Article original
Abonnez-vous à notre blog et recevez les derniers articles directement dans votre boîte mail.