Comment ChatGPT tourne sur UN SEUL serveur de base de données ??

cocadmin www.youtube.com

Résumé Bref

Ce résumé explique comment Chajpt, avec 800 millions d'utilisateurs, gère son infrastructure de base de données avec un seul serveur principal PostgreSQL. Les points clés incluent :

  • L'optimisation des requêtes en écriture pour éviter la surcharge de la base de données.
  • L'implémentation de limites de débit (rate limiting) à plusieurs niveaux pour contrôler le flux de requêtes.
  • L'utilisation de répliques en lecture seule et d'une réplication en cascade pour gérer les requêtes de lecture sans surcharger la base de données principale.

Introduction

OpenAI a connu une croissance rapide, passant de zéro à 100 millions d'utilisateurs en un an, puis ajoutant 100 millions d'utilisateurs supplémentaires en une semaine lors du lancement d'Imagè. Cette croissance a nécessité une infrastructure robuste pour gérer les pics d'utilisation, atteignant jusqu'à un million de nouveaux utilisateurs par heure.

Défis de l'infrastructure

Les serveurs applicatifs peuvent être facilement mis à l'échelle horizontalement, mais la base de données relationnelle PostgreSQL pose des défis. La mise à l'échelle horizontale des bases de données relationnelles est complexe en raison des problèmes de concurrence et de corruption potentielle des données. La seule solution initiale était la mise à l'échelle verticale, c'est-à-dire l'augmentation des ressources d'un seul serveur, ce qui a des limites. Les pics d'utilisation peuvent entraîner des ralentissements, des expirations de requêtes et une surcharge de la base de données.

Solutions initiales envisagées

Le "sharding", ou partitionnement des données sur plusieurs serveurs, était une solution potentielle, mais nécessitait une refonte complète de l'application, ce qui aurait pris des mois. Au lieu de cela, ils ont choisi d'optimiser PostgreSQL en réduisant les requêtes en écriture, qui sont les principales causes de ralentissement en raison du MVCC (Multiversion Concurrency Control).

Optimisation des requêtes en écriture

L'équipe a optimisé le code pour réduire les requêtes en écriture inutiles et a implémenté des "lazy writes" (écritures paresseuses) en regroupant les requêtes en lots pour des mises à jour plus efficaces. Ils ont également simplifié les requêtes complexes en les divisant en plusieurs requêtes plus petites et plus rapides.

Limitation du débit et mise en cache

Des limites de débit (rate limiting) ont été mises en place à plusieurs niveaux (load balancer, application, pooler) pour éviter de surcharger la base de données. Un système de cache a été ajouté pour réduire le nombre de requêtes envoyées à la base de données en stockant les données fréquemment consultées en mémoire vive.

Priorisation des requêtes et bases de données alternatives

Un système de priorité a été mis en place pour assurer que les fonctionnalités essentielles continuent de fonctionner même en cas de pics de charge. Les nouvelles fonctionnalités nécessitant beaucoup d'écritures ont été déportées vers une base de données non relationnelle, Cosmo DB, pour faciliter la mise à l'échelle.

Haute disponibilité et répliques en lecture seule

Pour assurer la haute disponibilité, une deuxième machine identique a été ajoutée pour prendre le relais en cas de panne de la base de données principale. Des répliques en lecture seule ont été créées pour servir les requêtes de lecture, libérant ainsi la base de données principale des charges de lecture.

Pooler de connexion

Un pooler de connexion a été utilisé pour gérer les connexions entre les serveurs applicatifs et la base de données, réduisant ainsi la charge sur la base de données principale.

Gestion des schémas de base de données

Les modifications de schéma de base de données sont strictement contrôlées pour éviter les réécritures complètes de tables, qui peuvent paralyser la base de données. Les changements sont limités à 5 secondes et les ajouts de colonnes sont soumis à des limites de débit strictes.

Réplication en cascade

Pour gérer le grand nombre de requêtes en lecture, 50 bases de données en lecture seule ont été ajoutées. Pour éviter de surcharger la base de données principale avec la synchronisation, une réplication en cascade a été mise en place, où la base de données principale se synchronise avec quelques serveurs, qui à leur tour se synchronisent avec d'autres, créant ainsi une cascade de réplication.

Summarize any YouTube video in seconds

Get summaries like this one for any video, in your language, with the free Summ app.

Download on the Apple Store
Get it on Google Play

Transcription

YouTube Transcript Generator

0:00 Comment ça se fait que Chajpt qui a 800 millions d'utilisateurs n'a que un seul serveur de base de données ? À la base, Open a grossit presque de 0 jusqu'à 100 millions d'utilisateur en juste 1 an. Et comme c'est une croissance qu'on a jamais vu avant et ben ils sont partis d'une infrastructure qui était relativement petite. Le souci c'est qu'au fur et à mesure qu'il rajoute des fonctionnalités, par exemple quand ils ont lancé Imagè pour pouvoir générer des images, ils ont une croissance encore plus abusée où ils ont eu jusqu'à 100 millions de nouveaux utilisateurs en une semaine. Ils avaient même des pics à 1 million de nouveaux utilisateurs par heure. Donc c'est vraiment une échelle abusée. Donc pour gérer ça, normalement, il faut vraiment une infra de fou. Donc pour l'application ChatJPT, les serveurs applicatifs, normalement c'est pas trop trop un problème parce que tant que ta carte de crédit est passe, tu peux juste ajouter des serveurs et ça pose pas trop de souci parce que c'est des problèmes qui sont stateless, ils ont pas d'état, c'est-à-dire que que ta requête elle arrive sur le serveur numéro 12 ou sur le serveur numéro 260 pour l'utilateur

0:46 ça change rien. C'est le même code qui tourne sur les deux serveurs. Donc on appelle ça du scaling horizontal. Le souci c'est que ces serveurs derrière ils communiquent avec une base de données pour pouvoir enregistrer tes données, enregistrer ce que tu fais, récupérer tes informations et cetera. Et dans cet article, il nous explique qu'ils utilisent Postgl qui est une base de données relationnell. Et les bases de données relationnelles, tu peux pas vraiment les scaler horizontalement comme ça à l'infini. Parce que imagine, tu as plusieurs bases de données et que tu as des centaines de millions d'utilisateurs, ben c'est sûr que tu vas avoir plusieurs requêtes qui vont vouloir modifier la même donnée et qui vont arriver sur deux serveurs différents. Et dans ce cas-là, comment est-ce que tu fais ? parce que celui qui arrive en deuxième, il écrase ce qu'a fait le premier et dans ce cas-là, tu vas avoir une base de données qui peut potentiellement être corrompue. Ou la deuxième solution, ça serait que quand tu fais la première requête, bah tu bloques la base de données, ce qui fait que tu peux pas continuer à faire de modification en parallèle et donc tu ralentis énormément ta base de données. Et bloquer ta base de données, c'est la dernière chose que tu veux faire quand tu as des millions d'utilateur. Donc la seule solution qui reste, c'est juste le

1:31 scaling vertical, c'est-à-dire avoir une machine qui a un CPU plus balaise, qui a plus de mémoire et qui a des disques durs plus rapides. Le premier problème avec ça, c'est que évidemment, il y a une limite au bout d'un moment, tu peux pas juste rajouter de la mémoire et des CPU. Et deuxièmement, quand il y a des pics d'usage, ce qui leur arrive quand ils sortent une nouvelle fonctionnalité, au départ, ta base de données, elle fonctionne normalement parce qu'elle à peu près à la bonne taille. Il y a un pic d'utilisation qui arrive et donc là, ça commence à ralentir. Les requettes prennent de plus en plus de temps. Au bout d'un certain temps, il y a des requêtes qui sont tellement lentes qu'elles commencent à expirer. Et quand elles sont expiré et ben les serveurs explicatifs derrière, ils vont réessayer et donc ça va rajouter encore plus de requettes. Et donc même si c'était un petit pic qui était vraiment juste temporaire et ben tu as complètement tout pété ton application. elle peut jamais revenir dans un état normal parce qu'il y a trop de requêtes qui réessayent réessayent en permanence et la base de données peut jamais récupérer. Donc d'habitude quand ça ça arrive, on met en place ce qu'on appelle du shareding. C'estàdire qu'on va découper nos données en morceaux. Par

2:17 exemple, si on a 100 million d'utilisateurs, on va mettre 100 millions d'utilisateurs sur un serveur, 100 millions sur un autre, 100 millions sur un autre. Et si on garde des serveurs de la même taille, ça fait qu'on peut supporter 10 fois plus d'utilisateurs maintenant. Le problème c'est que ils ont pas eu le temps de se poser, de dire "OK, on va faire une migration, on va ajouter des machines et cetera parce que c'est pas aussi simple que juste allumer neuf nouvelles machines. Il faut vraiment refaire toute l'application pour qu'elle soit consciente que bah il y a certains utilisateurs qui sont dans certains serveurs. Donc juste ça, ça aurait pris des mois à devoir tout recoder parce que tant que tu gères les utilisateurs un à la fois, ça va, il y a pas trop de problème. Mais si tu as des fonctionnalités qui ont besoin de récupérer un groupe d'utilisateurs, des choses comme ça, bah là tu commences à devoir aller chercher dans différents serveurs. C'est c'est le bordel, c'est trop compliqué. Enfin, c'est faisable mais ça prend vraiment beaucoup de temps avant de tout refaire." Donc à la place, ils sont dit "OK, avant qu'on parte sur un truc de fou comme ça, si on soulève le capot de PostG SQL, qu'est-ce qui fait vraiment que la base de données au bout d'un moment est ralentie ?" Ils ont

3:02 réalisé que dans Postgré, c'est principalement les requêtes en écriture qui font ralentir la base de données. Et ça, c'est à cause du MVCC pour Multiversion Concurrency Control. Et ce système, c'est la façon dont sont géré les requêtes en écriture dans la base de données. Quand Poser reçoit une requête qui doit modifier une ligne dans la base de données, ce qu'il va faire c'est d'abord créer une copie de cette ligne, ensuite faire les modifications qui est à faire, terminer toute la transaction, le reste des requêtes qui est à faire et une fois que tout est fini, il va marquer l'ancienne ligne comme étant à effacer. Ce qui fait que pendant ce temps-là, pendant qu'on est en train de faire notre requête d'écriture, même si ça prend un peu de temps, bah il y a plein de requêtes en lecture qui peuvent continuer à être faites sur l'ancienne version. Et dès que la nouvelle version est prête, boum, les requêtes en lecture peuvent venir récupérer la nouvelle version. Et donc ce système, c'est exactement pour ça que Postgré est très très bon en lecture parce que c'est jamais bloqué. Mais c'est aussi pour ça que les écritures sont beaucoup plus lentes parce qu'il faut recopier la ligne entière même si on veut changer

3:48 juste une seule colonne. Et en plus de ça, il faut pouvoir de temps en temps repasser à travers toute la base de données pour pouvoir enlever nettoyer les anciennes versions. Et ça bah ça consomme beaucoup de ressources. Donc ils sont dit OK ce qu'on va faire avant de commencer à faire un truc de ouf on va déjà optimiser et faire en sorte qu'on va faire le moins de requêtes en écriture possible. Donc première chose, on va dans le code, on va essayer de voir toutes les requêtes en écriture qu'on fait qu'on avait pas vraiment besoin de faire ou qui était des bugs, des choses comme ça. Donc ça déjà boum, ça libère un petit peu notre base de données. Ensuite, là où on peut, on va faire des lazy right. C'està-dire que on va faire comme des batchs. C'estàd que si on reçoit une requête en écriture, on va peut-être pas la faire tout de suite, on va peut-être la mettre en ce cas ou quelque chose comme ça. Et quand on en a un certain nombre, par exemple, on en a 50, on en a 100, et ben là boum, on va faire une seule grosse requête à la base de données. Ce qui fait que au lieu d'avoir 50 petites actions et de devoir nettoyer 50 fois derrière, on a juste une grosse action et ça c'est plus efficace. Ensuite, ils vont optimiser

4:33 leur requête. Donc évidemment, c'est déjà pas mal optimisée, mais ils vont essayer de simplifier les requêtes et de pas envoyer une requête qui est trop compliquée, qui prend trop de temps dans la base de données. Et donc s'ils ont besoin de faire une requête qui est compliquée, ils ont remarqué qu'il va aller mieux la découper en plusieurs requêtes plus simples, plus rapide à faire pour la base de données. Et ensuite, une fois qu'on a récupéré tout ça dans l'application et bah on peut remixer pour pouvoir avoir ce qu'on a besoin à la base. Donc ça prend peut-être un peu plus de ressources au niveau de l'application mais au niveau de l'application c'est moins grave parce queon peut scaler horizontalement alors que la base de données, on veut lui éviter le plus d'efforts possible. Ensuite, ils ont rajouté le plus de rate limit possible. C'estàd qu'à tous les niveaux, que ce soit au niveau du load balancer qui reçoit les requêtes des utilisateurs, que ce soit au niveau de l'application qui va envoyer des requêtes à la base de données ou même au niveau du pouleur qui est entre la base de données et l'application, on va parler juste après. Et ben on va mettre en place plein de limites pour que même

5:19 si il y a des gros pics où il y a un bug qui fait que on doit envoyer plein de requettes, on va jamais envoyer trop de requettes à la base de données. Parce que comme on sait, si on envoie vraiment trop de requêtes d'un coup, bah la donnée va jamais pouvoir tout encaisser et va se faire des doses. Donc vaut mieux les limiter avant même d'en arriver là. Et dans la liste des optimisations, ils ont aussi rajouté du cash. Pour pouvoir avoir moins de requêtes à envoyer à la base de données. On va avoir un serveur qui va garder une petite partie de ce qu' a dans la base donné mais en mémoire vive. Comme ça, c'est beaucoup plus rapide et donc on a beaucoup moins de requet et donc ça va venir libérer pas mal notre base de données sans qu'on ait besoin de modifier notre application. Une autre chose aussi qui les a aidé à éviter pas mal de problèmes, c'est d'ajouter un système de priorité aux requêtes. Donc tout ce qui est vraiment indispensable au fonctionnement de l'application, ça va avoir une priorité qui est plus élevée. Et tout ce qui est des fonctionnalités un petit peu moins importantes ou des nouvelles fonctionnalités, elles vont avoir une moins grosse priorité. Ce qui fait que

6:05 si on fait un déploiement et qu'il y a un bug et que ça génère beaucoup de requettes, et ben ça a moins grosse priorité que les fonctionnalités qui sont déjà là qui sont importantes. Et pareil, si jamais il y a un pic et qu'on peut vraiment pas souvenir à tout toutes les requêtes, et ben au moins celles qui sont vraiment importantes, elles vont être faites. Mais celles qui sont moins importantes, moins utilisées, peut-être qu'elles vont arrêter de fonctionner ou fonctionner moins vite ou chose comme ça, mais au moins le produit, les fonctionnalités principales de chatt continue à fonctionner pour tout le monde. Ensuite, pour toutes les nouvelles fonctionnalités qui ont vraiment des gros besoins en écriture, et ben il les ont déporté sur un autre système de base de données qui s'appelle Cosmo DB qui est une base de données non relationnelle. C'est un peu comme Mongo DB ou document DB sur AWS et qui est hébergé géré par Azure et donc ça c'est plus facile à scaler. Mais ça c'est vraiment juste pour les nouvelles fonctionnalités où ils ont pas le choix. Le principal reste quand même sur Postg. Juste au passage si tu es développeur freelance et tu vas prendre des devops pour pouvoir valoriser ton profit. Je fais une formation deux trois fois par an avec un petit groupe où on apprend

6:51 ensemble les outils devop en demande. Donc si ça t'intéresse, je te mets un petit lien dans la description. Maintenant ils ont bien optimisé tout ça. Il reste quand même un petit problème un gros problème c'est que quand tu as une seule base de données et ben c'est un single point of fire. C'estàdire que si cette base de donné elle tombe, boum bah là qu'il y a plus rien qui marche. Donc ce qu'ils ont fait c'est qu'ils ont rajouté une deuxième machine qui est identique et qui va se synchroniser avec la première. Mais cette deuxième base, elle va pas être utilisée par les applications. Elle reste là en gros en secours. Et si la première elle bug, il y a un problème matériel qui fait que voilà la machine elle déconne et ben automatiquement cette deuxième machine, elle va venir prendre le relais. Ce qui fait qu'on va avoir une toute petite indisponibilité le temps qu'on réalise que la première base de données est KO, mais normalement en quelques secondes ça vient prendre le relais. Et ensuite ce qu'ils ont fait c'est qu'ils ont rajouté des répliques en lecture seul. Donc ça va être des nouveaux serveurs qui vont se synchroniser avec la base de données

7:36 primaire. Et ces serveursl ils vont servir seulement les requêtes en lecture. Et l'avantage de ça, c'est que comme ils ont pas besoin de se synchroniser entre eux, ils sont juste synchronisés avec une seule source de vérité, ben on peut rajouter presque autant qu'on veut. On verra juste après pourquoi presque. Et donc ça ça va énormément libérer notre base de données primaire parce que maintenant elle va recevoir que les requêtes en écriture. Maintenant on commence à être pas trop mal mais il reste un petit souci, c'est qu'ils ont toujours une base de données primaires mais ils peuvent avoir des milliers des dizaines de milliers de serveurs applicatifs devant qui tabassent notre base de données. Et chacun de ces serveurs doit se connecter à la base de données au cas où ils ont des requêtes en écriture à faire. Et donc toutes ces connexions, elles consomment du CPU et consomment de la mémoire et ça charge de notre base de donné pour rien. Enfin pas pour rien mais si on pouvait éviter ça serait mieux. Et donc pour l'éviter, ils ont utilisé ce qu'on appelle un. C'est quelque chose qu'on va mettre juste devant notre base de données et qui va recevoir les connexions. Ce qui fait que c'est le pouleur qui va gérer les

8:21 connexions avec tous les serveurs applicatifs. Après, on peut rajouter pas mal de couleurs en fonction de le plus on rajoute de machines, le plus on peut rajouter de couleurs. On peut aussi les mettre plus proches des machines pour que les connexions soient plus rapides et cetera. Et donc du coup, la base de données, elle reçoit juste quelques-unes des connexions, même s'il y a 100000 serveurs derrière qui se connent. Donc là, en terme infra, c'est bon, on est on est solide. Par contre, en terme de schéma de base de données, il y a des trucs où faut commencer à faire attention parce que quand on change une table, par exemple, il y a une colonne, c'est des chiffres et on veut changer, on veut que ça soit des chaînes de caractères. Tempose gré, ça ça déclenche une réécriture complète de la table et ça, on sait que c'est mort. Surtout quand tu as des centaines de millions d'utilisateurs, tu peux pas réécrire le table de zéro alors qu'on sait que les écritures, c'est ce qui de plus l en ampost gris. Donc, chez Open les changements de schéma de base donné sont interdits. Tu as pas le droit, tu peux pas. Physiquement, tu peux pas. Si tu fais un changement, la base donnée, elle explose. Il y a quand même certains petits changements que tu peux faire.

9:07 par exemple, tu peux ajouter ou supprimer une colonne parce que ça ça déclenche pas une réécriture complète de la table. Donc ce qui fait que si tu veux faire une modification, tu peux quand même et ça va être en plusieurs étapes. Donc si par exemple tu veux passer de chiffre à texte et ben tu vas devoir ajouter une nouvelle colonne qui va être du texte et qui va copier euh la ce qu' a dans la première et ensuite tu feras une suppression de l'ancienne colonne et donc du coup tu fais ta modification en deux parties comme ça. Par contre c'est vraiment des petits changements. Ils ont limité à 5 secondes les changements de schéma. Si le migration de ton schéma de base de données prend plus que 5 secondes, c'est de c'est refusé, tu pourras pas le faire. tu trouves une autre solution, débrouille-toi. Et ils ont aussi ajouté un R limit, c'estàdire que si tu ajoutes par exemple une nouvelle colonne et cette nouvelle colonne bah comme elle est vide, bah tu dois venir copier des données dedans, tu peux pas d'un coup venir remplir cette colonne parce que ça va faire des écritures et encore une fois ça va éclater la base de données. Donc tu peux faire des écritures dedans

9:53 mais tu as un R limite très très strict. Donc parfois, il y a des changements qui font dans la base de données qui prennent plus d'une semaine à euh réécrire dans les nouvelles colonnes de la base de données parce qu'ils veulent vraiment minimiser au maximum le nombre d'écritures par seconde qu'ils font dans la base. Donc en faisant tout ça, ils ont réglé le problème d'écriture. Ils disent même qu'ils ont encore pas mal de marges pour pouvoir grossir. L'avantage, c'est que comme maintenant ils ont 800 millions d'utilisateurs, ils savent qu'ils peuvent pas faire x 100. Donc s'ils voit qu'ils ont de la marge pour faire x 3 x 4, ça va, ils sont tranquilles. Mais pour les requêtes en lecture, bah ça ça continue à tabasser parce que dans la plupart des applications, le nombre de requêtes en lecture, c'est 10 fois peut-être 100 fois le nombre de requêtes en écriture que tu fais. Et donc pour ça, ils ont rajouté 50 bases en lecture seul qui se synchronisent avec la première juste pour pouvoir absorber le nombre de requêtes en lecture. Et donc 50 ça leur suffit mais ça génère un nouveau

10:38 problème, c'est que notre base de données primaire principale, s'il faut qu'elle se synchronise avec 50 autres serveurs, ben ça veut dire qu'elle doit débiter au niveau du réseau, au niveau de du CPU, ça consomme des ressources aussi pour pouvoir se synchroniser avec tout le monde. Et donc encore une fois, si on peut éviter à cette base de pouvoir faire ce travail là et ben on va le faire. Et l'astuce qu'ils ont trouvé, c'est de faire une réplication en cascade. C'est-à-dire que la base de donnée principale, elle va juste se synchroniser avec une ou deux autres machines en lecture seule. Et ensuite ces deux autres machines, elles vont se synchroniser elles-mêmes avec deux trois autres machines qui vont elles-mêmes se synchroniser avec deux trois autres machines, ce qui fait une espèce de cascade comme ça. Ce qui fait qu'au bout d'un moment, toutes les bases neées vont arriver dans le même état, mais la base neée principale, elle est pas obligée de débiter à fond à 50 machines. Elle fait juste copier à une ou deux machines et puis tranquille.

Share

Résumés de vidéos YouTube