Comment effectuer une montée de version sur une base de données de plusieurs téraoctets, utilisée par plusieurs dizaines de millions d’utilisateurs ?
Doctolib est un service qui est devenu primordial pour des millions de patients et des centaines de milliers de praticiens du monde médical, en France et en Allemagne.
Avec plus de 50 millions de visites mensuelles l’infrastructure de Doctolib s’appuie sur des technologies libres et open-source comme le moteur de base de données PostgreSQL.
L’application Doctolib utilise PostgreSQL comme moteur de base de données transactionnel principal. Cette architecture s’articule autour d’un serveur primaire et de plusieurs secondaires en lecture seule. La volumétrie de notre base de données est de plusieurs téraoctets de données (l’équivalent de 8 millions de photos sur Instagram).
Afin de disposer des dernières fonctionnalités comme la réplication logique et de performances accrues sur la parallélisation des requêtes disponibles depuis la version 10 de PostgreSQL nous devions procéder à une mise à jour du moteur et ce sur plusieurs serveurs tout en respectant une certaine fenêtre de maintenance et donc un temps d’indisponibilité réduit au maximum, pour limiter les impacts négatifs dans les hôpitaux et les cabinets médicaux.
Fort de plus de 20 années de développement, PostgreSQL et sa communauté fournissent l’ensemble des outils et procédures afin de pouvoir réaliser les mises à jour d’une version majeur à l’autre (9.6 vers 11.6 dans notre cas).
Dans le cadre de cette opération de mise à jour, trois questions sont apparues :
- Combien de temps précisément prendra la mise à jour sur le serveur primaire ainsi que sur les serveurs secondaires ?
- Comment nous assurer que cette mise à jour n’entraîne pas de régressions d’un point de vue performance applicative ni de régressions fonctionnelles ?
- Comment on s’assure de pouvoir facilement faire un retour-arrière en cas de problème durant la fenêtre de maintenance ?
Notre équipe infrastructure a un crédo lié à chacune de nos opérations pouvant impacter la qualité et la disponibilité du service : “Réduire le risque en éliminant les inconnues”.
C’est encore une fois autour de cette idée que nous avons construit notre stratégie de migration vers la nouvelle version de PostgreSQL.
Notre démarche pour préparer la migration
Mesurer la fenêtre de maintenance nécessaire
Définir la durée nécessaire que la mise à jour prendrait était important pour avoir une idée de la fenêtre de maintenance requise et pouvoir prévenir les praticiens et patients de l’indisponibilité de service. C’est pourquoi nous avons cloné notre serveur de base de données principal avec l’ensemble des data sur une autre instance et testé la méthode pg_upgrade.
Cette méthode s’est avérée concluante sur le serveur primaire puisqu’en seulement 15 secondes l’opération d’upgrade était complétée.
Dans le cas d’une mise à jour en utilisant l’utilitaire pg_upgrade il est indispensable d’exécuter le script analyze_new cluster.sh généré pendant la phase de mise à jour car les statistiques des tables ne sont pas copiées entre les deux versions. Ce script va effectuer un vacuum analyze en trois étapes pour recalculer les statistiques des tables et ainsi les obtenir le plus rapidement possible.
La phase d’analyse des tables a nécessité 20 minutes.
Concernant les serveurs secondaires nous avons opté pour la méthode de copie via l’utilitaire rsync en mode “link”. Celle-ci nous a permis en quelques secondes seulement de mettre à jour chaque serveur et de conserver les données, nous évitant ainsi une reconstruction qui aurait été longue et impactante.
Tests de performances et de non régression
Nous assurer que la nouvelle version n’introduisait pas de problèmes de performances était crucial avant toute mise en production. Dans ce cadre nous avons réalisé un tir de performance sur notre environnement de pré-production.
Pour chaque changement ayant un fort impact sur notre architecture en place nous réalisons ces tirs à l’aide de Gatling et de plusieurs scénarios qui reproduisent une charge équivalente à ce que nous observons en production (430k requests per minute).
Pour nous assurer que cette nouvelle version n’apporte pas de régressions fonctionnelles nous avons déployé celle-ci une semaine à l’avance sur notre environnement de staging. Cet environnement est largement utilisé par l’ensemble des équipes à Doctolib pour recetter les nouvelles features et évolutions de l’application.
Stratégie de retour-arrière
Opérer un changement majeur sur le système au coeur de l’architecture de Doctolib nécessitait obligatoirement d’envisager le pire (une corruption des données sur le serveur maître ou une perte de synchronisation de l’ensemble des réplicas par exemple) et donc d’avoir un plan B au cas où les choses tourneraient mal pendant l’opération de maintenance.
La solution la plus sécurisante pour nous a été de mettre en place plusieurs serveurs secondaires en réplication avec le serveur maître et de volontairement ne pas effectuer la mise à jour sur ceux-ci durant la phase de maintenance pour avoir des serveurs de secours prêts à prendre le relai.
En cas de problème nous aurions promu un de ces serveurs en tant que nouveau maître, branché les autres serveurs secondaires sur celui-ci et reconfiguré l’application pour les utiliser.
Cette stratégie nous donnait pleine confiance pour aborder l’opération de maintenance avec sérénité.
La mise à jour de notre infrastructure de base de données a été un succès, nous avons réussi à réaliser celle-ci en respectant la fenêtre de maintenance initialement annoncée. Nous n’avons pas rencontré de perturbations ou d’inconnues, et nous avions une idée précise de combien de temps chaque phase nécessitait.
L’application de notre crédo, même si ce n’est pas une balle en argent, nous a toujours permis d’anticiper au maximum les risques, en limitant les inconnues.
Cette technique nous permet aujourd’hui de conserver un excellent uptime sur la disponibilité de notre service.