Revenir au contenu principal
Data Engineering

Mise à l'échelle automatique de Lakebase Postgres

Une plongée au cœur de la mise à l'échelle de Postgres en temps réel

par Carlota Soto

  • Exigences architecturales de la mise à l'échelle automatique
  • Quand augmenter ou réduire la capacité
  • Comment ajuster la capacité sans arrêter PostgreSQL

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.

image9.png

À 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.

L'exigence architecturale

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 :

  • La couche de calcul exécute Postgres et traite les requêtes. Elle utilise la RAM et le NVMe local pour un accès à faible latence, et ne possède aucun état durable.
  • La couche de stockage assure la durabilité et l'historique. Le WAL est répliqué par des safekeepers s'exécutant sur des SSD, les pageservers (également sur SSD) reconstruisent les versions des pages, et le stockage d'objets conserve le registre immuable à long terme. (Cet article de blog se concentre sur le calcul, mais nous avons rédigé une analyse approfondie de la partie stockage si cela vous intéresse également.)

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.

image8.png

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.

Partie I : L'algorithme

Les trois signaux de mise à l'échelle automatique

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 :

  1. Charge CPU : cpuGoalCU
  2. Utilisation de la mémoire : memGoalCU
  3. Taille de l'ensemble de travail du cache de calcul : lfcGoalCU

La 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) :

CPU (cpuGoalCU)

Le CPU est le plus simple des trois signaux. L'algorithme surveille de près la charge du processeur :

  • Toutes les cinq secondes, l'autoscaler-agent lit la charge CPU moyenne sur une minute de la VM.
  • L'objectif de CPU vise à maintenir cette charge à un niveau inférieur ou égal à 90 % de la capacité CPU disponible.
  • Lorsque la charge dépasse cette cible, 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.

Mémoire (memGoalCU)

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 :

  • Toutes les cinq secondes, l'autoscaler-agent lit les métriques de mémoire globales de la VM.
  • Toutes les 100 millisecondes, le vm-monitor vérifie la mémoire utilisée par Postgres.

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 cache de calcul (lfcGoalCU)

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.

Zoom sur : comment nous estimons l'ensemble de travail de Postgres

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 :

  1. Hache l'identifiant de la page.
  2. Utilise les premiers bits du hachage pour sélectionner un registre.
  3. Compte les zéros non significatifs (leading zeroes) dans les bits restants.
  4. Met à jour le registre sélectionné si cette observation dépasse sa valeur précédente.

La distribution de ces valeurs de registre fournit une estimation du nombre de pages distinctes observées.

image10.png

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.

Ajouter le temps à 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.

image6.png

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 :

  • Les pages distinctes consultées au cours de la dernière minute
  • Les pages distinctes consultées au cours des cinq dernières minutes
  • Les pages distinctes consultées au cours de la dernière heure

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 ?

Choisir la fenêtre temporelle de l'ensemble de travail

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.

image5.png

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.

image4.png

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.

image1.png

Projeter la croissance du cache

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.

image2.png

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.

Partie II : Redimensionner la ressource de calcul en cours d'exécution

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 :

  1. L'autoscaler-agent s'exécute sur chaque nœud Kubernetes. Il collecte les métriques des VM Postgres sur ce nœud, calcule les tailles cibles et lance la mise à l'échelle.
  2. Le vm-monitor s'exécute à l'intérieur de chaque VM. Il surveille de près la mémoire de Postgres, valide les demandes de réduction d'échelle (downscaling) et redimensionne le cache de calcul.
  3. Un planificateur (scheduler) Kubernetes modifié maintient la vue globale des ressources disponibles. Chaque augmentation d'échelle (upscale) doit être approuvée par le planificateur avant que la mémoire ne soit engagée.
  4. NeonVM applique le changement. Il s'agit d'une ressource et d'un contrôleur Kubernetes personnalisés, conçus avec QEMU et KVM, qui peuvent ajouter ou supprimer du CPU et de la mémoire d'une VM en cours d'exécution. (Remarque : l'architecture de Lakebase Postgres a débuté dans Neon et le nom de la ressource/du contrôleur reste le même).
image3.png

Mise à l'échelle ascendante

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 :

  1. L'autoscaler-agent calcule la nouvelle cible à partir des objectifs de CPU, de mémoire et d'ensemble de travail.
  2. Le planificateur Kubernetes vérifie si le nœud peut satisfaire la demande sans surallouer la mémoire.
  3. Une fois approuvé, l'autoscaler-agent met à jour la ressource NeonVM.
  4. Le contrôleur NeonVM ajoute du CPU et de la mémoire à la VM en cours d'exécution.
  5. Le vm-monitor étend le cache de calcul pour utiliser la nouvelle capacité.

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.

Mise à l'échelle descendante

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.

En résumé

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 :

  • 100 millisecondes : le vm-monitor vérifie la mémoire de Postgres pour détecter les allocations rapides
  • 5 secondes : l'autoscaler-agent lit le CPU et la mémoire globale
  • 20 secondes : l'autoscaler-agent évalue les estimations de l'ensemble de travail sur des fenêtres allant de une à 60 minutes

C'est ainsi qu'une base de données de production peut changer de taille plus de 32 000 fois par mois.

image7.png

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.

Lancez-le

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

Recevez les derniers articles dans votre boîte mail

Abonnez-vous à notre blog et recevez les derniers articles directement dans votre boîte mail.